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

Często aplikacja tworzy pliki, do których inne aplikacje nie potrzebują lub nie powinny mieć dostępu. System udostępnia następujące lokalizacje do przechowywania plików związanych z konkretnymi aplikacjami:

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

  • Katalogi pamięci zewnętrznej: te katalogi zawierają zarówno dedykowaną lokalizację do przechowywania plików stałych, 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 odpowiednie uprawnienia, pliki zapisane w tych katalogach są przeznaczone do użytku wyłącznie przez aplikację. Jeśli zamierzasz tworzyć pliki, z których powinny mieć dostęp inne aplikacje, aplikacja powinna zapisywać je w pamięci współdzielonej w pamięci zewnętrznej.

Gdy użytkownik odinstaluje Twoją aplikację, pliki zapisane w pamięci aplikacji zostaną usunięte. Z tego powodu nie należy korzystać z tej pamięci do zapisywania plików niezależnie od aplikacji, które mogą być przechowywane niezależnie od aplikacji. Jeśli na przykład aplikacja umożliwia użytkownikom robienie zdjęć, użytkownik oczekuje, że będzie mógł uzyskać dostęp do zdjęć nawet po jej odinstalowaniu. Z tego względu należy korzystać z pamięci współdzielonej, aby zapisywać pliki tego typu w odpowiedniej kolekcji multimediów.

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

Dostęp z pamięci wewnętrznej

Dla 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 drugi zawiera pliki aplikacji zapisane w pamięci podręcznej. Aplikacja nie wymaga żadnych uprawnień systemowych do odczytu i zapisu w plikach w tych katalogach.

Inne aplikacje nie mogą uzyskać dostępu do plików przechowywanych w pamięci wewnętrznej. Dzięki temu pamięć wewnętrzna to dobre miejsce do przechowywania danych aplikacji, do których inne aplikacje nie powinny mieć dostępu.

Pamiętaj jednak, że te katalogi są zwykle małe. Przed zapisaniem plików aplikacji w pamięci wewnętrznej aplikacja powinna 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 możesz uzyskać 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 i ich przechowywanie w tym katalogu.

uzyskiwać dostęp do plików i przechowywać je

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

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

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 FileProvider z atrybutem FLAG_GRANT_READ_URI_PERMISSION.

Uzyskiwanie dostępu do pliku przy użyciu 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żna 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 metodę getDir() w kodzie opartym na Kotlinie lub przekazując katalog główny i nową nazwę katalogu do konstruktora File w kodzie w języku Java:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

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

Tworzenie plików pamięci podręcznej

Jeśli chcesz przechowywać dane wrażliwe tylko tymczasowo, użyj wyznaczonego przez aplikację katalogu pamięci podręcznej aplikacji w pamięci wewnętrznej. Tak jak w przypadku całej pamięci aplikacji, pliki przechowywane w tym katalogu są usuwane, gdy użytkownik odinstaluje aplikację, chociaż pliki znajdujące się 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);

Usuń pliki z pamięci podręcznej

Mimo że Android czasami sam usuwa pliki pamięci podręcznej, nie należy polegać na tym, że system nie robi tego za Ciebie. Zawsze przechowuj pliki 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() określająca kontekst 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ąco dużo miejsca na pliki 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ą wartościowe dla użytkownika, tylko w Twojej aplikacji. Jeden katalog jest przeznaczony na pliki trwałe aplikacji, a drugi zawiera pliki aplikacji zapisane 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 miejscem na dane, aby uzyskać dostęp do katalogów aplikacji w pamięci zewnętrznej. Pliki zapisane w tych katalogach są usuwane podczas odinstalowania aplikacji.

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub starszym aplikacja ma dostęp do plików należących do innych aplikacji, o ile ma odpowiednie uprawnienia do przechowywania danych. Aby zapewnić użytkownikom większą kontrolę nad plikami i zadbać o porządek w plikach, aplikacje kierowane na Androida 10 (poziom interfejsu API 29) i nowszym otrzymują domyślnie ograniczony dostęp do pamięci zewnętrznej (czyli ograniczonego miejsca na dane). Gdy ograniczone miejsce na dane jest włączone, aplikacje nie mają dostępu do katalogów aplikacji, które należą do innych aplikacji.

Sprawdzanie, czy miejsce na dane jest dostępne

Pamięć zewnętrzna znajduje się na woluminie fizycznym, który użytkownik może usunąć, dlatego przed próbą odczytania danych aplikacji lub ich zapisania w pamięci zewnętrznej upewnij się, że wolumin jest dostępny.

Możesz zapytać 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 to MEDIA_MOUNTED_READ_ONLY, możesz tylko odczytywać te pliki.

Na przykład do określania dostępności miejsca na dane są przydatne 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 użyj tego polecenia, aby włączyć wolumin wirtualny na potrzeby testowania logiki dostępności pamięci zewnętrznej:

adb shell sm set-virtual-disk true

Wybierz fizyczną lokalizację pamięci masowej

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ą. Musisz więc wybrać, który z nich ma być używany do obsługi pamięci aplikacji.

Aby uzyskać dostęp do różnych lokalizacji, wywołaj ContextCompat.getExternalFilesDirs(). Jak widać we fragmencie kodu, pierwszy element zwróconej tablicy jest uważany za podstawowy wolumin zewnętrznej pamięci masowej. Używaj go, 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 wielokrotnie tego samego pliku.

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

Kotlin

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

Java

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

Tworzenie plików pamięci podręcznej

Aby dodać plik aplikacji do pamięci podręcznej w pamięci zewnętrznej, pobierz odwołanie 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 katalogu zewnętrznej pamięci podręcznej, użyj metody delete() na 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 ramach aplikacji, najlepiej przechowywać je w katalogach aplikacji w pamięci zewnętrznej, jak widać 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 z podanych stałych interfejsu API, takich jak DIRECTORY_PICTURES. Nazwy katalogów zapewniają prawidłowe traktowanie plików przez system. Jeśli żadna ze wstępnie zdefiniowanych nazw podkatalogów nie odpowiada Twoim plikom, możesz przekazać null do getExternalFilesDir(). Spowoduje to zwrócenie katalogu głównego aplikacji w pamięci zewnętrznej.

Zapytanie o wolne miejsce

Wielu użytkowników nie ma zbyt dużo miejsca na swoich urządzeniach, dlatego aplikacja powinna je wykorzystywać rozsądnie.

Jeśli wiesz z wyprzedzeniem, ile danych przechowujesz, możesz sprawdzić, ile miejsca może zapewnić urządzenie na potrzeby aplikacji, wywołując metodę getAllocatableBytes(). Wartość zwrócona przez funkcję getAllocatableBytes() może być większa niż bieżąca ilość wolnego miejsca na urządzeniu. System rozpoznał 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 z niego wszystkich plików 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);
}

Tworzenie aktywności związanej z zarządzaniem miejscem na dane

Aplikacja może zadeklarować i utworzyć niestandardową aktywność, która po uruchomieniu pozwala użytkownikowi zarządzać danymi przechowywanymi na jego urządzeniu. Tę niestandardową aktywność typu „zarządzanie przestrzenią” możesz zadeklarować za pomocą atrybutu android:manageSpaceActivity w pliku manifestu. Aplikacje menedżera plików mogą wywołać tę aktywność nawet wtedy, gdy aplikacja jej nie eksportuje, czyli gdy działanie android:exported jest ustawione na false.

Poproś użytkownika 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ę zawierającą działanie ACTION_MANAGE_STORAGE. Ta intencja wyświetla prompt dla użytkownika. W razie potrzeby może też pokazywać ilość wolnego miejsca na urządzeniu. Aby wyświetlić takie informacje, skorzystaj z tego 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 ze 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