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

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

  • 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 na Androidzie 10 (API na poziomie 29) i nowszych wersjach te lokalizacje są szyfrowane. Dzięki tym cechom te lokalizacje są dobrym miejscem do przechowywania danych wrażliwych, do których dostęp może mieć 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 pamięci współdzielonej w pamięci zewnętrznej.

Gdy użytkownik odinstaluje aplikację, pliki zapisane w pamięci aplikacji zostaną usunięte. Z tego powodu nie należy używać tej pamięci do zapisywania niczego, co według użytkownika powinno być zachowane niezależnie od aplikacji. Jeśli na przykład aplikacja umożliwia użytkownikom robienie zdjęć, użytkownik oczekuje, że będzie mieć do nich dostęp 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 sekcjach poniżej opisano, jak przechowywać pliki w katalogach 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 porządkować swoje 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 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órych inne aplikacje nie powinny mieć dostępu.

Pamiętaj jednak, że te katalogi są zwykle niewielkie. Zanim zapiszesz pliki aplikacji w pamięci wewnętrznej, aplikacja 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 masz dostęp za pomocą właściwości filesDir obiektu kontekstu. Platforma udostępnia kilka metod, które pomogą Ci uzyskać dostęp do plików w tym katalogu i je przechowywać.

Uzyskiwanie dostępu do plików i ich przechowywanie

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

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

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

Kotlin

val file = File(context.filesDir, filename)

Java

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

Przechowywanie pliku za pomocą strumienia

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

Poniższy 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 elementu FileProvider z atrybutem FLAG_GRANT_READ_URI_PERMISSION.

Uzyskiwanie dostępu 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

Możesz uzyskać tablicę zawierającą nazwy wszystkich plików w katalogu filesDir, wywołując 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 funkcję getDir() w kodzie opartym na języku 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);

Tworzenie plików pamięci podręcznej

Jeśli chcesz 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 wszystkich pamięci aplikacji, pliki przechowywane w tym katalogu są usuwane, gdy użytkownik odinstaluje aplikację, ale mogą zostać usunięte wcześniej.

Aby utworzyć plik w 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 pamięci podręcznej, nie należy polegać na systemie w kwestii czyszczenia tych plików. Pliki 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() obiektu File reprezentującego 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 pamięć wewnętrzna nie zapewnia wystarczającej ilości 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 porządkować 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 aplikacji zapisane w pamięci podręcznej.

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

Na urządzeniach z Androidem 9 (API na poziomie 28) lub starszym aplikacja może uzyskiwać dostęp do plików należących do innych aplikacji, pod warunkiem że ma odpowiednie uprawnienia dostępu do pamięci. Aby dać użytkownikom większą kontrolę nad plikami i ograniczyć bałagan, aplikacje kierowane na Androida 10 (poziom API 29) i nowsze mają domyślnie ograniczony dostęp do pamięci zewnętrznej, czyli ograniczony dostęp do miejsca na dane. Gdy jest włączona, aplikacje nie mają dostępu do katalogów należących 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ą odczytu lub zapisu danych aplikacji w pamięci zewnętrznej sprawdź, czy wolumin jest dostępny.

Stan woluminu możesz sprawdzić, wywołując 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 odczytywać te pliki.

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

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 lokalizacji sklepu stacjonarnego

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

Aby uzyskać dostęp do różnych lokalizacji, wywołaj metodę ContextCompat.getExternalFilesDirs(). Jak widać we fragmencie kodu, pierwszy element w zwróconej tablicy jest uważany za główny 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 aplikacji z pamięci zewnętrznej, wywołaj getExternalFilesDir().

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

Poniższy 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 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);

Usuwanie plików z pamięci podręcznej

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

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Treści multimedialne

Jeśli aplikacja działa z plikami multimedialnymi, które są przydatne dla użytkownika tylko w niej, najlepiej przechowywać je w 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 podanych przez stałe interfejsu API, takie jak DIRECTORY_PICTURES. Te nazwy katalogów zapewniają prawidłowe traktowanie plików przez system. Jeśli żadna ze wstępnie zdefiniowanych nazw podkatalogów nie pasuje do Twoich plików, możesz przekazać null do getExternalFilesDir(). Zwraca katalog główny aplikacji w pamięci zewnętrznej.

Sprawdź wolne miejsce

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

Jeśli wiesz z wyprzedzeniem, ile danych przechowujesz, możesz sprawdzić, ile miejsca na urządzeniu może zapewnić Twojej aplikacji, wywołując funkcję getAllocatableBytes(). Wartość zwrotu środków w wysokości getAllocatableBytes() może być większa niż 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 lub wszystkich plików z pamięci podręcznej z urządzenia.

Poniższy 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 związanej z zarządzaniem miejscem na dane

Aplikacja może zadeklarować i utworzyć aktywność niestandardową, która po uruchomieniu umożliwia użytkownikowi zarządzanie danymi zapisanymi przez aplikację na jego urządzeniu. Tę niestandardową aktywność „zarządzania miejscem” deklarujesz za pomocą atrybutu android:manageSpaceActivity w pliku manifestu. Menedżery plików mogą wywołać tę aktywność, nawet jeśli aplikacja jej nie eksportuje, czyli gdy aktywność ustawia android:exported 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ę, która zawiera działanie ACTION_MANAGE_STORAGE. Ta intencja wyświetla użytkownikowi prompt. W razie potrzeby może on wyświetlać ilość wolnego miejsca na urządzeniu. Aby wyświetlić te informacje w przyjazny dla użytkownika sposób, użyj wyniku 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 wszystkich aplikacji na urządzeniu. Aby to zrobić, wywołaj intencję, która zawiera 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