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

W wielu przypadkach aplikacja tworzy pliki, do których inne aplikacje nie potrzebują lub nie powinny mieć dostępu. System udostępnia te lokalizacje, w których można przechowywać pliki specyficzne dla aplikacji:

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

  • Katalogi pamięci zewnętrznej: zawierają one zarówno dedykowaną lokalizację do przechowywania trwałych plików, jak i drugą lokalizację do przechowywania danych w pamięci podręcznej. Chociaż inna aplikacja może uzyskać dostęp do tych katalogów, o ile ma ona odpowiednie uprawnienia, pliki w tych katalogach są przeznaczone do użytku wyłącznie przez Twoją aplikację. Jeśli zamierzasz tworzyć pliki, do których inne aplikacje powinny mieć do nich dostęp, powinna ona przechowywać je w pamięci współdzielonej w pamięci zewnętrznej.

Gdy użytkownik odinstaluje Twoją aplikację, pliki zapisane w pamięci aplikacji są usuwane. Dlatego nie należy korzystać z tego miejsca na dane, aby zapisywać wszystko, co użytkownik powinien przechowywać niezależnie od aplikacji. Jeśli na przykład aplikacja umożliwia użytkownikom robienie zdjęć, użytkownik powinien spodziewać się, że po odinstalowaniu aplikacji będzie mieć do nich dostęp. Zamiast tego lepiej będzie zapisywać pliki tego typu w pamięci współdzielonej w odpowiedniej kolekcji multimediów.

W sekcjach poniżej dowiesz się, jak przechowywać pliki w katalogach aplikacji i uzyskiwać do nich dostęp.

Dostęp z pamięci wewnętrznej

W przypadku każdej aplikacji system udostępnia w pamięci wewnętrznej katalogi, w których aplikacja może porządkować pliki. Jeden katalog jest przeznaczony na pliki trwałe aplikacji, a inny zawiera pliki aplikacji w pamięci podręcznej. Twoja aplikacja nie wymaga żadnych uprawnień systemowych do odczytu i zapisu plików w tych katalogach.

Inne aplikacje nie mają 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órego nie powinny mieć dostępu inne aplikacje.

Pamiętaj jednak, że te katalogi są zazwyczaj małe. Zanim zapiszesz pliki aplikacji w pamięci wewnętrznej, powinna ona wysłać zapytanie o wolne miejsce na urządzeniu.

Dostęp do plików trwałych

Zwykłe, trwałe pliki aplikacji znajdują się w katalogu, do którego masz dostęp za pomocą właściwości filesDir obiektu kontekstu. Platforma udostępnia kilka metod, które ułatwiają dostęp do plików w tym katalogu i ich przechowywanie.

Dostęp do plików i ich przechowywanie

Aby uzyskiwać dostęp do plików i przechowywać je, możesz używać interfejsu API File.

Aby utrzymać wydajność aplikacji, nie otwieraj i nie zamykaj tego samego pliku kilka razy.

Ten fragment kodu pokazuje, jak korzystać z interfejsu API File:

Kotlin

val file = File(context.filesDir, filename)

Java

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

Przechowywanie pliku za pomocą strumienia

Zamiast korzystać z interfejsu API File, możesz wywołać metodę 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 zezwolić innym aplikacjom na dostęp do plików przechowywanych w tym katalogu w pamięci wewnętrznej, użyj atrybutu FileProvider z atrybutem FLAG_GRANT_READ_URI_PERMISSION.

Dostęp do pliku za pomocą strumienia

Aby odczytać plik jako strumień, użyj narzędzia 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świetl listę plików

Tablicę zawierającą nazwy wszystkich plików w katalogu filesDir możesz uzyskać, wywołując metodę fileList(), jak pokazano w tym fragmencie kodu:

Kotlin

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

Java

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

Tworzenie katalogów zagnieżdżonych

Możesz też utworzyć katalogi zagnieżdżone lub otworzyć katalog wewnętrzny, wywołując getDir() w kodzie opartym na Kotlin lub przekazując katalog główny i nową nazwę katalogu do konstruktora File w kodzie opartym na języku Java:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

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

Utwórz pliki pamięci podręcznej

Jeśli chcesz przechowywać dane wrażliwe tylko tymczasowo, do zapisania ich danych użyj wyznaczonego katalogu pamięci podręcznej aplikacji w pamięci wewnętrznej. Tak jak w przypadku wszystkich aplikacji, pliki przechowywane w tym katalogu są usuwane, gdy użytkownik odinstaluje aplikację, chociaż pliki w tym katalogu mogą zostać usunięte wcześniej.

Aby utworzyć plik w pamięci podręcznej, wywołaj metodę 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 context i interfejsu API File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

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

Usuń pliki z pamięci podręcznej

Mimo że Android czasem sam usuwa pliki z pamięci podręcznej, nie należy polegać na jego czyszczeniu. Pliki z pamięci podręcznej aplikacji należy zawsze przechowywać 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, przekazująca nazwę pliku:

    Kotlin

    context.deleteFile(cacheFileName)
    

    Java

    context.deleteFile(cacheFileName);
    

Dostęp z pamięci zewnętrznej

Jeśli w pamięci wewnętrznej nie ma wystarczającej ilości miejsca do przechowywania plików aplikacji, rozważ użycie pamięci zewnętrznej. System udostępnia w pamięci zewnętrznej katalogi, w których aplikacja może porządkować pliki mające wartość tylko dla użytkownika. Jeden katalog jest przeznaczony na trwałe pliki aplikacji, a drugi zawiera pliki aplikacji przechowywane w pamięci podręcznej.

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

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub niższym aplikacja ma dostęp do plików innych aplikacji należących do innych aplikacji, o ile ma ona odpowiednie uprawnienia do korzystania z pamięci. Aby zapewnić użytkownikom większą kontrolę nad plikami i ułatwić im pracę, aplikacje kierowane na Androida 10 (poziom interfejsu API 29) i nowsze wersje domyślnie otrzymują dostęp w zakresie do pamięci zewnętrznej, czyli ograniczonego miejsca na dane. Gdy jest włączona pamięć o zakresie, aplikacje nie mają dostępu do katalogów aplikacji należących do innych aplikacji.

Sprawdzanie, czy miejsce na dane jest dostępne

Ponieważ pamięć zewnętrzna znajduje się na woluminie fizycznym, który użytkownik może usunąć, sprawdź, czy jest dostępny, zanim spróbujesz z niego odczytać dane aplikacji lub zapisać w niej dane aplikacji.

Możesz przesłać zapytanie o stan woluminu, wywołując metodę Environment.getExternalStorageState(). Jeśli zwrócony stan to MEDIA_MOUNTED, możesz odczytywać i zapisywać pliki aplikacji w pamięci zewnętrznej. Jeśli jest to MEDIA_MOUNTED_READ_ONLY, możesz tylko odczytać te pliki.

Do określenia dostępności miejsca na dane przydatne mogą być na przykład te metody:

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 włącz wolumin wirtualny do testowania logiki dostępności pamięci zewnętrznej za pomocą tego polecenia:

adb shell sm set-virtual-disk true

Wybierz fizyczną lokalizację pamięci masowej

Czasami urządzenie, które przydziela partycję z pamięci wewnętrznej, ponieważ pamięć zewnętrzna ma też gniazdo kart SD. Oznacza to, że urządzenie ma wiele woluminów fizycznych, które mogą zawierać pamięć zewnętrzną. Musisz więc wybrać, którego z nich chcesz używać do obsługi pamięci aplikacji.

Aby uzyskać dostęp do różnych lokalizacji, zadzwoń pod numer ContextCompat.getExternalFilesDirs(). Jak widać we fragmencie kodu, pierwszy element zwróconej tablicy jest traktowany jako 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 plików trwałych

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

Aby utrzymać wydajność aplikacji, nie otwieraj i nie zamykaj tego samego pliku kilka razy.

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);

Utwórz pliki pamięci podręcznej

Aby dodać plik aplikacji do pamięci podręcznej w pamięci zewnętrznej, skieruj się do externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

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

Usuń pliki 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 ten plik:

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Treści multimedialne

Jeśli Twoja aplikacja obsługuje pliki multimedialne, które są wartościowe dla użytkownika tylko w samej aplikacji, najlepiej przechowywać je w specjalnych katalogach 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 dostarczanych przez stałe interfejsy API, np. DIRECTORY_PICTURES. Te nazwy katalogów zapewniają, że system będzie prawidłowo traktował pliki. Jeśli żadna ze wstępnie zdefiniowanych nazw podkatalogów nie pasuje do Twoich plików, możesz przekazać null do getExternalFilesDir(). Zwraca on główny katalog aplikacji w pamięci zewnętrznej.

Zapytanie o wolne miejsce

Wielu użytkowników ma niewiele miejsca na dane na swoich urządzeniach, więc aplikacja powinna wykorzystywać je w przemyślany sposób.

Jeśli z wyprzedzeniem wiesz, ile danych przechowujesz, możesz zadzwonić do getAllocatableBytes(), aby sprawdzić, ile miejsca urządzenie może zapewnić aplikacji. Zwracana wartość 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 masz wystarczająco dużo miejsca, aby zapisać dane aplikacji, wywołaj allocateBytes(). W przeciwnym razie aplikacja może poprosić użytkownika o usunięcie niektórych plików z urządzenia lub wszystkich plików z pamięci podręcznej.

Ten fragment kodu pokazuje, jak aplikacja może wysyłać zapytania o wolne miejsce 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);
}

Utwórz aktywność związaną z zarządzaniem miejscem na dane

Aplikacja może zadeklarować i utworzyć niestandardową aktywność, która po uruchomieniu umożliwia użytkownikowi zarządzanie danymi przechowywanymi na jego urządzeniu. To niestandardowe działanie związane z zarządzaniem pokojem deklarujesz za pomocą atrybutu android:manageSpaceActivity w pliku manifestu. Aplikacje do zarządzania plikami mogą wywoływać tę czynność nawet wtedy, gdy aplikacja jej nie eksportuje, czyli gdy aktywność android:exported ma wartość false.

Poproś użytkownika o usunięcie niektórych plików z urządzenia

Aby poprosić użytkownika o wybranie na urządzeniu plików do usunięcia, wywołaj intencję zawierającą działanie ACTION_MANAGE_STORAGE. Ta intencja wyświetla użytkownikowi prośbę. W razie potrzeby może pojawić się informacja o ilości wolnego miejsca na urządzeniu. Aby wyświetlić te łatwe w użyciu informacje, użyj wyniku takiego obliczenia:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Poproś użytkownika o usunięcie wszystkich plików z pamięci podręcznej

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

Dodatkowe materiały

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

Filmy