Dostęp do plików związanych z aplikacją

W wielu przypadkach aplikacja tworzy pliki, do których inne aplikacje nie muszą mieć dostępu lub nie powinny go mieć. System udostępnia te lokalizacje do przechowywania takich plików specyficznych dla aplikacji:

  • Katalogi pamięci wewnętrznej: te katalogi obejmują zarówno dedykowaną lokalizację do przechowywania trwałych plików, jak i inną lokalizację do przechowywania danych z pamięci podręcznej. System uniemożliwia innym aplikacjom dostęp do tych lokalizacji, a w Androidzie 10 (poziom interfejsu API 29) i nowszych wersjach są one szyfrowane. Dzięki tym cechom te lokalizacje są dobrym miejscem do przechowywania danych wrażliwych, do których dostęp ma tylko Twoja aplikacja.

  • Katalogi pamięci zewnętrznej: te katalogi obejmują zarówno dedykowaną lokalizację do przechowywania trwałych plików, jak i inną lokalizację do przechowywania danych z pamięci podręcznej. Chociaż inna aplikacja może uzyskać dostęp do tych katalogów jeśli ma odpowiednie uprawnienia, pliki przechowywane w tych katalogach są przeznaczone do użytku tylko przez Twoją aplikację. Jeśli chcesz utworzyć pliki do których inne aplikacje powinny mieć dostęp, Twoja aplikacja powinna przechowywać je w części pamięci zewnętrznej przeznaczonej na dane współdzielone zamiast tego.

Gdy użytkownik odinstaluje Twoją aplikację, pliki zapisane w pamięci specyficznej dla aplikacji zostaną usunięte. Z tego powodu nie należy używać tej pamięci do zapisywania niczego, co użytkownik chce zachować niezależnie od Twojej aplikacji. Jeśli na przykład Twoja aplikacja umożliwia użytkownikom robienie zdjęć, użytkownik będzie oczekiwać, że będzie mógł uzyskać dostęp do tych zdjęć nawet po odinstalowaniu aplikacji. Dlatego do zapisywania tego typu plików w odpowiedniej kolekcji multimediów należy używać pamięci współdzielonej.

W kolejnych sekcjach opisujemy, jak przechowywać pliki w katalogach specyficznych dla aplikacji i jak uzyskiwać do nich dostęp.

Dostęp z pamięci wewnętrznej

W przypadku każdej aplikacji system udostępnia katalogi w pamięci wewnętrznej, w których aplikacja może organizować swoje pliki. Jeden katalog jest przeznaczony na trwałe pliki aplikacji, a drugi zawiera pliki z pamięci podręcznej aplikacji. Aby odczytywać i zapisywać pliki w tych katalogach, aplikacja nie wymaga żadnych uprawnień systemowych.

Inne aplikacje nie mogą uzyskiwać dostępu do plików przechowywanych w pamięci wewnętrznej. Dzięki temu pamięć wewnętrzna jest dobrym miejscem na dane aplikacji, do których inne aplikacje nie powinny mieć dostępu.

Pamiętaj jednak, że te katalogi są zwykle małe. Zanim aplikacja zapisze pliki specyficzne dla aplikacji w pamięci wewnętrznej, powinna sprawdzić ilość wolnego miejsca na urządzeniu.

Dostęp do trwałych plików

Zwykłe, trwałe pliki aplikacji znajdują się w katalogu, do którego możesz uzyskać dostęp za pomocą filesDir właściwości obiektu kontekstu. Framework udostępnia kilka metod, które pomagają uzyskiwać dostęp do plików w tym katalogu i je przechowywać.

Dostęp do plików i ich przechowywanie

Do uzyskiwania dostępu do plików i ich przechowywania możesz używać interfejsu API File.

Aby zachować wydajność aplikacji, nie otwieraj i nie zamykaj tego samego pliku wielokrotnie.

Ten fragment kodu pokazuje, jak używać interfejsu API File:

Kotlin

val file = File(context.filesDir, filename)

Java

File file = new File(context.getFilesDir(), filename);

Przechowywanie pliku za pomocą strumienia

Zamiast używać interfejsu API File możesz wywołać openFileOutput() , aby uzyskać FileOutputStream, który zapisuje dane w pliku w katalogu filesDir.

Ten fragment kodu pokazuje, jak zapisać tekst w pliku:

Kotlin

val filename = "myfile"
val fileContents = "Hello world!"
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
        it.write(fileContents.toByteArray())
}

Java

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

Aby umożliwić innym aplikacjom dostęp do plików przechowywanych w tym katalogu w pamięci wewnętrznej, użyj FileProvider z atrybutem FLAG_GRANT_READ_URI_PERMISSION.

Dostęp do pliku za pomocą strumienia

Aby odczytać plik jako strumień, użyj openFileInput():

Kotlin

context.openFileInput(filename).bufferedReader().useLines { lines ->
    lines.fold("") { some, text ->
        "$some\n$text"
    }
}

Java

FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

Wyświetlanie listy plików

Aby uzyskać tablicę zawierającą nazwy wszystkich plików w katalogu filesDir wywołaj fileList(), jak pokazano w tym fragmencie kodu:

Kotlin

var files: Array<String> = context.fileList()

Java

Array<String> files = context.fileList();

Tworzenie zagnieżdżonych katalogów

Możesz też tworzyć zagnieżdżone katalogi lub otwierać katalog wewnętrzny, wywołując getDir() w kodzie opartym na Kotlinie lub przekazując katalog główny i nazwę nowego katalogu do konstruktora File w kodzie opartym na Javie:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

File directory = context.getFilesDir();
File file = new File(directory, filename);

Tworzenie plików z pamięci podręcznej

Jeśli musisz przechowywać dane wrażliwe tylko tymczasowo, zapisz je w wyznaczonym katalogu pamięci podręcznej aplikacji w pamięci wewnętrznej. Podobnie jak w przypadku całej pamięci specyficznej dla aplikacji, pliki przechowywane w tym katalogu są usuwane, gdy użytkownik odinstaluje Twoją aplikację, chociaż pliki w tym katalogu mogą zostać usunięte wcześniej.

Aby utworzyć plik z pamięci podręcznej, wywołaj File.createTempFile():

Kotlin

File.createTempFile(filename, null, context.cacheDir)

Java

File.createTempFile(filename, null, context.getCacheDir());

Aplikacja uzyskuje dostęp do pliku w tym katalogu za pomocą właściwości cacheDir obiektu kontekstu i interfejsu API File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

File cacheFile = new File(context.getCacheDir(), filename);

Usuwanie plików z pamięci podręcznej

Chociaż Android czasami sam usuwa pliki z pamięci podręcznej, nie należy polegać na tym, że system zwolni miejsce, usuwając te pliki za Ciebie. Zawsze należy utrzymywać pliki z pamięci podręcznej aplikacji w pamięci wewnętrznej.

Aby usunąć plik z katalogu pamięci podręcznej w pamięci wewnętrznej, użyj jednej z tych metod:

  • Metoda delete() w obiekcie File który reprezentuje plik:

    Kotlin

    cacheFile.delete()

    Java

    cacheFile.delete();
  • Metoda deleteFile() kontekstu aplikacji, do której przekazywana jest nazwa pliku:

    Kotlin

    context.deleteFile(cacheFileName)

    Java

    context.deleteFile(cacheFileName);

Dostęp z pamięci zewnętrznej

Jeśli pamięć wewnętrzna nie zapewnia wystarczającej ilości miejsca na pliki specyficzne dla aplikacji, rozważ użycie pamięci zewnętrznej. System udostępnia katalogi w pamięci zewnętrznej, w których aplikacja może organizować pliki, które są przydatne dla użytkownika tylko w Twojej aplikacji. Jeden katalog jest przeznaczony na trwałe pliki aplikacji, a drugi zawiera pliki z pamięci podręcznej aplikacji.

W Androidzie 4.4 (poziom interfejsu API 19) lub nowszym aplikacja nie musi prosić o żadne uprawnienia związane z pamięcią, aby uzyskać dostęp do katalogów specyficznych dla aplikacji w pamięci zewnętrznej. Pliki przechowywane w tych katalogach są usuwane po odinstalowaniu aplikacji.

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym aplikacja może uzyskiwać dostęp do plików specyficznych dla aplikacji należących do innych aplikacji, pod warunkiem że ma odpowiednie uprawnienia do pamięci. Aby dać użytkownikom większą kontrolę nad plikami i ograniczyć ich bałagan, aplikacje kierowane na Androida 10 (poziom interfejsu API 29) i nowsze domyślnie mają ograniczony dostęp do pamięci zewnętrznej, czyli pamięci o ograniczonym zakresie. Gdy włączona jest pamięć ograniczona, aplikacje nie mogą uzyskiwać dostępu do katalogów specyficznych dla aplikacji należących do innych aplikacji.

Sprawdzanie, czy pamięć jest dostępna

Pamięć zewnętrzna znajduje się na woluminie fizycznym, który użytkownik może usunąć. Zanim spróbujesz odczytać dane specyficzne dla aplikacji z pamięci zewnętrznej lub zapisać w niej dane specyficzne dla aplikacji, sprawdź, czy wolumin jest dostępny.

Stan woluminu możesz sprawdzić, wywołując Environment.getExternalStorageState(). Jeśli zwrócony stan to MEDIA_MOUNTED, to możesz odczytywać i zapisywać pliki specyficzne dla aplikacji w pamięci zewnętrznej. Jeśli jest to MEDIA_MOUNTED_READ_ONLY, możesz tylko odczytywać te pliki.

Na przykład te metody są przydatne do określania dostępności pamięci:

Kotlin

// Checks if a volume containing external storage is available
// for read and write.
fun isExternalStorageWritable(): Boolean {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

// Checks if a volume containing external storage is available to at least read.
fun isExternalStorageReadable(): Boolean {
     return Environment.getExternalStorageState() in
        setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
}

Java

// Checks if a volume containing external storage is available
// for read and write.
private boolean isExternalStorageWritable() {
    return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}

// Checks if a volume containing external storage is available to at least read.
private boolean isExternalStorageReadable() {
     return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
            Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}

Na urządzeniach bez wymiennej pamięci zewnętrznej użyj tego polecenia, aby włączyć wolumin wirtualny do testowania logiki dostępności pamięci zewnętrznej:

adb shell sm set-virtual-disk true

Wybieranie fizycznej lokalizacji pamięci

Czasami urządzenie, które przydziela partycję pamięci wewnętrznej jako pamięć zewnętrzną, ma też gniazdo na kartę SD. Oznacza to, że urządzenie ma wiele woluminów fizycznych, które mogą zawierać pamięć zewnętrzną, więc musisz wybrać, którego z nich użyć do przechowywania danych specyficznych dla aplikacji.

Aby uzyskać dostęp do różnych lokalizacji, wywołaj ContextCompat.getExternalFilesDirs(). Jak pokazano w tym fragmencie kodu, pierwszy element zwróconej tablicy jest uważany za podstawowy wolumin pamięci zewnętrznej. Używaj tego woluminu, chyba że jest pełny lub niedostępny.

Kotlin

val externalStorageVolumes: Array<out File> =
        ContextCompat.getExternalFilesDirs(applicationContext, null)
val primaryExternalStorage = externalStorageVolumes[0]

Java

File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];

Dostęp do trwałych plików

Aby uzyskać dostęp do plików specyficznych dla aplikacji z pamięci zewnętrznej, wywołaj getExternalFilesDir().

Aby zachować wydajność aplikacji, nie otwieraj i nie zamykaj tego samego pliku wielokrotnie.

Ten fragment kodu pokazuje, jak wywołać getExternalFilesDir():

Kotlin

val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)

Java

File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

Tworzenie plików z pamięci podręcznej

Aby dodać plik specyficzny dla aplikacji do pamięci podręcznej w pamięci zewnętrznej, uzyskaj odniesienie do externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

File externalCacheFile = new File(context.getExternalCacheDir(), filename);

Usuwanie plików z pamięci podręcznej

Aby usunąć plik z zewnętrznego katalogu pamięci podręcznej, użyj metody delete() w obiekcie File, który reprezentuje plik:

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Treści multimedialne

Jeśli Twoja aplikacja działa z plikami multimedialnymi, które są przydatne dla użytkownika tylko w Twojej aplikacji, najlepiej przechowywać je w katalogach specyficznych dla aplikacji w pamięci zewnętrznej, jak pokazano w tym fragmencie kodu:

Kotlin

fun getAppSpecificAlbumStorageDir(context: Context, albumName: String): File? {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    val file = File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName)
    if (!file?.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created")
    }
    return file
}

Java

@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (file == null || !file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

Ważne jest, aby używać nazw katalogów podanych przez stałe interfejsu API, takie jak DIRECTORY_PICTURES. Te nazwy katalogów zapewniają, że system będzie prawidłowo traktować pliki. Jeśli żadna z predefiniowanych nazw podkatalogów nie pasuje do Twoich plików, możesz zamiast tego przekazać null do getExternalFilesDir(). Spowoduje to zwrócenie głównego katalogu specyficznego dla aplikacji w pamięci zewnętrznej.

Sprawdzanie ilości wolnego miejsca

Wielu użytkowników ma mało miejsca na urządzeniach, dlatego Twoja aplikacja powinna oszczędnie korzystać z miejsca.

Jeśli znasz ilość przechowywanych danych, możesz sprawdzić, ile miejsca urządzenie może udostępnić Twojej aplikacji, wywołując getAllocatableBytes(). Wartość zwracana przez getAllocatableBytes() może być większa niż bieżąca ilość wolnego miejsca na urządzeniu. Dzieje się tak, ponieważ system zidentyfikował pliki, które może usunąć z katalogów pamięci podręcznej innych aplikacji.

Jeśli jest wystarczająco dużo miejsca na zapisanie danych aplikacji, wywołaj allocateBytes(). W przeciwnym razie aplikacja może poprosić użytkownika o usunięcie niektórych plików z urządzenia lub usunięcie wszystkich plików z pamięci podręcznej plików z urządzenia.

Ten fragment kodu pokazuje, jak aplikacja może sprawdzić ilość wolnego miejsca na urządzeniu:

Kotlin

// App needs 10 MB within internal storage.
const val NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

val storageManager = applicationContext.getSystemService<StorageManager>()!!
val appSpecificInternalDirUuid: UUID = storageManager.getUuidForPath(filesDir)
val availableBytes: Long =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid)
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
        appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP)
} else {
    val storageIntent = Intent().apply {
        // To request that the user remove all app cache files instead, set
        // "action" to ACTION_CLEAR_APP_CACHE.
        action = ACTION_MANAGE_STORAGE
    }
}

Java

// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

StorageManager storageManager =
        getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
            appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
    // To request that the user remove all app cache files instead, set
    // "action" to ACTION_CLEAR_APP_CACHE.
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
}

Tworzenie aktywności zarządzania pamięcią

Aplikacja może zadeklarować i utworzyć niestandardową aktywność, która po uruchomieniu umożliwia użytkownikowi zarządzanie danymi przechowywanymi przez aplikację na urządzeniu użytkownika. Tę niestandardową aktywność "zarządzania miejscem" deklarujesz za pomocą android:manageSpaceActivity atrybutu w pliku manifestu. Aplikacje menedżera plików mogą wywoływać tę aktywność nawet wtedy, gdy Twoja aplikacja nie eksportuje aktywności, czyli gdy aktywność ma ustawioną wartość android:exported na false.

Prośba o usunięcie niektórych plików z urządzenia

Aby poprosić użytkownika o wybranie plików na urządzeniu do usunięcia, wywołaj intencję która zawiera ACTION_MANAGE_STORAGE działanie. Ta intencja wyświetla użytkownikowi prośbę. W razie potrzeby ta prośba może pokazywać ilość wolnego miejsca na urządzeniu. Aby wyświetlić te przyjazne dla użytkownika informacje, użyj wyniku tego obliczenia:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Prośba o usunięcie wszystkich plików z pamięci podręcznej

Możesz też poprosić użytkownika o wyczyszczenie plików z pamięci podręcznej wszystkich aplikacji na urządzeniu. Aby to zrobić, wywołaj intencję, która zawiera ACTION_CLEAR_APP_CACHE działanie intencji.

Dodatkowe materiały

Więcej informacji o zapisywaniu plików w pamięci urządzenia znajdziesz w tych materiałach.

Filmy