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

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

  • Katalogi pamięci wewnętrznej: te katalogi zawierają 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 (poziom interfejsu API 29) i nowszych lokalizacje są zaszyfrowane. Te cechy sprawiają, że te lokalizacje są dobrym miejscem do przechowywania danych poufnych, do których dostęp ma tylko Twoja aplikacja.

  • Katalogi zewnętrznego miejsca na dane: te katalogi zawierają zarówno dedykowane miejsce na trwałe pliki, jak i inne miejsce na dane 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 tylko do użytku Twojej aplikacji. Jeśli chcesz tworzyć pliki, do których inne aplikacje będą miały dostęp, przechowuj je w sekcji 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 używać tego miejsca do przechowywania do zapisywania czegokolwiek, co użytkownik chce zachować niezależnie od aplikacji. Jeśli na przykład Twoja aplikacja umożliwia użytkownikom robienie zdjęć, użytkownik powinien mieć do nich dostęp nawet po odinstalowaniu aplikacji. Dlatego do zapisywania takich plików w odpowiedniej kolekcji multimediów lepiej jest używać współdzielonego miejsca na dane.

W sekcjach poniżej opisujemy, jak przechowywać pliki i uzyskać 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 do trwałych plików aplikacji, a drugi do plików z pamięci podręcznej aplikacji. 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 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 niewielkie. 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 stał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. Framework udostępnia kilka metod umożliwiających dostęp do plików w tym katalogu i ich przechowywanie.

Dostęp do plików i ich przechowywanie

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

Aby zachować wydajność aplikacji, nie otwieraj i 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 wartością FLAG_GRANT_READ_URI_PERMISSION.

Uzyskiwanie dostępu do pliku przy użyciu 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

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ć zagnieżdżone katalogi lub otworzyć katalog wewnętrzny, wywołując funkcję getDir() w kodzie Kotlina lub przekazując katalog główny i nazwę nowego katalogu do konstruktora File w kodzie 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 dane wrażliwe mają być przechowywane tylko tymczasowo, zapisz je w katalogu pamięci podręcznej aplikacji w pamięci wewnętrznej. Podobnie jak w przypadku wszystkich pamięci specyficznych dla 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 funkcję 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 pamięci podręcznej

Chociaż Android czasami samodzielnie usuwa pliki pamięci podręcznej, nie należy polegać na tym, że system sam je usunie. Pliki pamięci podręcznej aplikacji powinny zawsze znajdować się 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 przekazujesz nazwę pliku:

    Kotlin

    context.deleteFile(cacheFileName)

    Java

    context.deleteFile(cacheFileName);

Dostęp z pamięci zewnętrznej

Jeśli w pamięci wewnętrznej jest za mało miejsca na pliki związane z aplikacją, 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 ramach aplikacji. Jeden katalog jest przeznaczony dla trwałych plików aplikacji, a drugi zawiera pliki z buforu 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 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 Twoja aplikacja może uzyskać dostęp do plików należących do innych aplikacji, o ile ma odpowiednie uprawnienia do przechowywania. Aby zapewnić użytkownikom większą kontrolę nad plikami i ograniczyć ich nagromadzenie, aplikacje kierowane na Androida 10 (poziom interfejsu API 29) lub nowszego mają domyślnie ograniczony dostęp do pamięci zewnętrznej, czyli ograniczony dostęp do 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.

Sprawdź, czy jest dostępne miejsce na dane

Ponieważ pamięć zewnętrzna znajduje się na fizycznym woluminie, który użytkownik może usunąć, przed odczytaniem danych aplikacji z pamieci zewnętrznej lub zapisaniem danych aplikacji do niej sprawdź, czy wolumin jest dostępny.

Stan głośności możesz sprawdzić, wywołując funkcję Environment.getExternalStorageState(). Jeśli zwrócony stan to MEDIA_MOUNTED, możesz odczytywać i zapisywać pliki związane z aplikacją na zewnętrznym urządzeniu pamięci masowej. Jeśli jest to MEDIA_MOUNTED_READ_ONLY, możesz tylko odczytać 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 do testowania logiki dostępności pamięci zewnętrznej:

adb shell sm set-virtual-disk true

Wybierz fizyczną lokalizację miejsca na dane

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 kilka fizycznych woluminów, które mogą zawierać zewnętrzną pamięć masową, więc musisz wybrać, którego z nich użyć do przechowywania danych aplikacji.

Aby uzyskać dostęp do różnych lokalizacji, wywołaj ContextCompat.getExternalFilesDirs(). Jak widać w fragmentie kodu, pierwszy element zwracanej tablicy jest uważany za podstawowy wolumin pamięci zewnętrznej. 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 stałych

Aby uzyskać dostęp do plików aplikacji z zewnętrznego miejsca na dane, zadzwoń pod numer getExternalFilesDir().

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

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 związany z aplikacją do pamięci podręcznej w pamięci zewnętrznej, uzyskaj odwołanie do externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

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

Usuń pliki pamięci podręcznej

Aby usunąć plik z katalogu zewnętrznej pamięci podręcznej, użyj metody delete() w obiekcie File reprezentującym 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 podanych przez stałe interfejsu API, np. DIRECTORY_PICTURES. Te nazwy katalogów zapewniają prawidłowe traktowanie plików przez system. Jeśli żadna z wstępnie zdefiniowanych nazw podkatalogów nie pasuje do Twoich plików, możesz zamiast tego podać parametr null w parametrze 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 będziesz przechowywać, możesz sprawdzić, ile miejsca na urządzeniu może być udostępnione Twojej aplikacji, wywołując funkcję getAllocatableBytes(). Wartość zwrócona przez funkcję getAllocatableBytes() może być większa niż bieżąca ilość wolnego miejsca na urządzeniu. Dzieje się tak, ponieważ system wykrył pliki, które może usunąć z katalogów pamięci podręcznej innych aplikacji.

Jeśli jest wystarczająco dużo miejsca na dane aplikacji, zadzwoń pod numer 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 deklarować i tworzyć niestandardową aktywność, która po uruchomieniu umożliwia użytkownikowi zarządzanie danymi przechowywanymi przez aplikację na jego urządzeniu. Deklarujesz tę niestandardową aktywność „zarządzanie pokojem” za pomocą atrybutu android:manageSpaceActivity w pliku manifestu. Aplikacje menedżera plików mogą wywoływać tę aktywność nawet wtedy, gdy aplikacja nie eksportuje aktywności, czyli gdy aktywność ustawia 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ę zawierającą działanie ACTION_MANAGE_STORAGE. Ten zamiar wyświetla użytkownikowi prompt. 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