2024. április 26., péntek

Gyorskeresés

Rendkívül basic storage kezelés Androidon

Írta: | Kulcsszavak: android . java . programozás . storage . példa

[ ÚJ BEJEGYZÉS ]

Szokásos példaprogram (mint a kamerás), hogy másnak ne kelljen napokig keresnie a megoldást, ha valamit file-ba akar lerakni az Androidra írt programjában. Főleg annak apropóján, hogy rengeteg régebbi leírás van a neten, ami a Scoped Storage előtti időkben, illetve Legacy storage-t kérve a manifest-ben még működött; azonban 2021 tavasztóll a Google külön engedélyhez köti a Legacy storage használatát, szóval nincs többet hozzáférés a /sdcard0/-on levő Documents, Camera, stb. könyvtárakhoz. A Barcodenote meg Legacy storage-t használt, mert amikor először feltöltöttem, még a fene se tudta, mi az, de azzal legalább működött :D Szóval muszáj volt átírni, ha nem akartam, hogy kiszórják a Play-ből.

A legacy storage helyett (mondjuk Android 4.4-től, szóval nem tegnap óta) van Storage Access, Framework, Scoped Storage, Documentprovider, stb. Ezek jól korlátozzák az alkalmazások tárhelyhez való hozzáférését, cserébe jó bonyolultak, a ember fejlesztő meg annyit akar, hogy lerakja a programja cuccait egy file-ba.
Alapvetően a Scoped Storage lényege, hogy az alkalmazás helyből csak a saját könyvtárához férhet hozzá, ami a /storage/emulated/0/Android/data/com.alkalmazásneve/files -en belül (vagy az alkalmazás könyvtárában Doocuments, Pictures, stb. is létrehozható) van. Ezt a gyári filekezelőből az eszköz tárhelyét kinyitva az Android nevezetű mappában találjuk. (Egyes gyártók flekezelőiben el van rejtve.) A /storage/emulated/0 pedig a sdcard0, vagy /data néven ismeretes partíció az Android eszközön. (Fixme, ha rosszul írtam :) ). Az alkalmazás saját könyvtárához legalább normálisan hozzá lehet férni, így a legegyszerűbb esethez (adatot akar tárolni egy alkalmazás) megfelel. Persze ha mindenképpen az eszköz Documents könyvtárát akarjuk használni, azt is lehet, de inkább a rendszer filekezelőjét Intent-ként használva, vagy DocumentProvider-en keresztül, egy jó jegyzettöb appban meg az a lényeg, hogy nincs külön mentés, stb., a jegyzetet azonnal írja file-ba. (Viszont a program beállításait nem érdemes plain text-ben tárolni, arra van a SharedPreferences, ami kifejezetten frankón használható.)

Szóval a lényeg, a példaprogram alant (GitLab-on meg itt, Android studio projektként feltöltve ) :

Manifest.xml - nincs benne android:requestLegacyExternalStorage="true" :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.storageexample">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

MainActivity.java
package com.example.storageexample;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Date;


public class MainActivity extends AppCompatActivity {

File file;
TextView tv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv = (TextView) findViewById(R.id.TV);

//Check permissions, request if none
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
else {
//Call the main routine if already have
access_storage_file();
}


}

//Main routine
public void access_storage_file() {
//Variable declaration
String file_name="testfile.txt";
String appdirpath= String.valueOf(getExternalFilesDir(null)); //Directory of application's files
String datestring=String.valueOf(Calendar.getInstance().getTime()); //Current time

//Display app directory path + write current time to file
tv.setText("Application files directory is : " + appdirpath + "\n\n\n");
tv.append("Current time is " + datestring);
tv.append("\nTrying to write current time to : " + file_name + "\n\n");
//You can call the write method with a file name and a string variable containing your data
writefile(file_name,datestring);

//Read back what was written
tv.append("\nTrying to read file : " + file_name + "\n");
//The reader returns a string, read from the given filename
String read=readfile(file_name);

//Display the content of the test file
tv.append(read);

}



//File writer method, doesn't care about if file exists, or media is writeable. Parameters are two strings, filename and data to write
public void writefile(String filename,String data) {
//Assign the file
file = new File(getExternalFilesDir(null), filename);
try {
//Open output stream and create the writer
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
//Write the data
pw.println(data);
//Flush data, close the writer and the file
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
Toast.makeText(getApplicationContext(),filename + " cannot be written", Toast.LENGTH_LONG).show();
//Error handling here
e.printStackTrace();
} catch (IOException e) {
//Error handling here
Toast.makeText(getApplicationContext(),"I/O error on " + filename, Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}



//Reader method
public String readfile(String filename) {
String data = "";
//Assign file
file = new File(getExternalFilesDir(null), filename);
//Does not read if file exists
if (file.exists() ) {
try {
//Initialize the reader, and read the file line by line to the data variable until end of file
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
data = data + line;
}
//Close the reader if EOF
br.close();
} catch (
IOException e) {
Toast.makeText(getApplicationContext(),filename + " cannot be read :(", Toast.LENGTH_LONG).show();
//Error handling here
}
}
//If file cannot be found, make a toast
else { Toast.makeText(getApplicationContext(),"File" + String.valueOf(getExternalFilesDir(null) + filename) + " does not exist", Toast.LENGTH_LONG).show(); }
return (data); //Return the contents
}


//This is called when the permission request is finished, e.g. the user responded
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// If the permissions are not granted, exit
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
Toast.makeText(getApplicationContext(),"Storage access permission not granted :(", Toast.LENGTH_LONG).show();
finish();
}

//If the permissions are OK,
if (isReadStoragePermissionGranted() && (isWriteStoragePermissionGranted())) {
//call the main routine
access_storage_file();

}

}

//Check if storage read permission is granted. If not, request is
public boolean isReadStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 3);
return false;
}
} else {
//Permission is automatic over SDK 23
return true;
}
}

//Check if storage read permission is granted. If not, request is
public boolean isWriteStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
ActivityCompat.requestPermissions(this, new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 2);
return false;
}
} else { //Permission is automatic over SDK 23
return true;
}
}

}

Activity_main.xml (ebben csak annyi a lényeg, hogy legyen valahol egy TextView, aminek TV az id-ja) :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/TV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.23"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.155" />

</androidx.constraintlayout.widget.ConstraintLayout>


Így fog kinézni, ha elindítjuk

Annyit csinál, hogy a TextView-re kiírja az app könyvtárának útvonalát, oda létrehoz egy file-t, amibe kiírja az aktuális időpontot, majd visszaolvassa, és kiírja a TextView-re. Ha nincs engedély a usertől a storage hozzáféréshez, akkor megkéri. A writefile és readfile rutinok használhatóak írásra és olvasásra (rém buta mind a kettő, a writefile felülírja az egész file-t, a read meg csak azt nézi, hogy létezik-e, de amúgy agyatlanul stringet olvas, akármi van a file-ban).

A writefile-nak két paramétere van : filenév (string), és a kiírandó adat (szintén string).
A readfile stringként adja vissza a paraméterként megadott nevű file-ban levő adatot (ha az létezik).
A isReadStoragePermissionGranted() és isWriteStoragePermissionGranted() függvények pedig boolean értékként igaz vagy hamis értéket adnak, ha van/nincs hozzáférés a háttértárhoz.

A szépséghibája annyi, hogy nem akartam sokat szívni a jogosultságkéréssel (meg nem is igazán tudom, hogy hogyan lehet szépen megtenni :DDD ), és az egésznek van egy olyan baja, hogy a program első futásakor még nincsenek meg a jogok, mert a permission request aszinkron folyamat, szóval a usernek feljön egy ablak, hogy ad-e jogot valamihez, közben a program fut tovább, és el is hasal pl. a filekezelés :D A legegyszerűbbparasztabb megoldás lett a nyerő : a példa lényegi része ki van szervezve az access_storage_file() eljárásba, és a MainActivity csak annyit néz, hogy van-e jog a storage-hoz. Ha van, meghívja az access_storage_file() -t, ha nincs, akkor kér, és az OnRequestPermissionResult() -ból - ami akkor fut le, ha a jogosultságkérés befejeződött, így vagy úgy - hívja meg a access_storage_file() . Különben sem érdemes a program érdemi részét a MainActivitiy-be tenni :K .

Amúgy nem olyan nehéz az alapszintű filekezelés Androidon, pl. ilyesmit, hogy file, vagy könyvtár létezik-e, a példában látható file.exists is visszaad, stb.

Remélhetőleg segítség lesz ez is valakinek :)

És mivel én sem a levegőből szedtem ezeket :
Storage kezelés a developers.android.com -on (ez kivételesen egész értelmes) : http://developer.android.com/guide/topics/data/data-storage.html
Leírás a file-ba írás elvégzéséhez : http://stackoverflow.com/questions/3551821/android-write-to-sd-card-folder
Innen meg az olvasást néztem : https://stackoverflow.com/questions/12421814/how-can-i-read-a-text-file-in-android

Copyright © 2000-2024 PROHARDVER Informatikai Kft.