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

W wielu przypadkach 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 w pamięci podręcznej. System uniemożliwia innym aplikacjom dostęp do tych lokalizacji, a w Androidzie 10 (poziom interfejsu API 29) i nowszych są one szyfrowane. 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 aplikację, pliki zapisane w pamięci konkretnej 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 następnych sekcjach opisujemy, jak przechowywać pliki w katalogach aplikacji i do nich zaglądać.

Dostęp z pamięci wewnętrznej

System udostępnia w pamięci wewnętrznej katalogi, w których aplikacja może porządkować swoje pliki. Jeden katalog jest przeznaczony do trwałych plików aplikacji, a drugi do plików w 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 jest dobrym miejscem na dane aplikacji, do których nie powinny mieć dostępu inne aplikacje.

Pamiętaj jednak, że te katalogi są zwykle niewielkie. Zanim aplikacja zapisze pliki dotyczące aplikacji w pamięci wewnętrznej, powinna wykonać 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 używać interfejsu File API możesz wywołać interfejs openFileOutput(), aby uzyskać FileOutputStream, który zapisuje dane do pliku w katalogu filesDir.

Ten fragment kodu pokazuje, jak zapisać tekst do 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.

Otwieranie 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 folderze filesDir, wywołaj funkcję 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 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 poufne mają być przechowywane tylko tymczasowo, zapisz je w katalogu pamięci podręcznej aplikacji w pamięci wewnętrznej. Podobnie jak w przypadku wszystkich innych 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() obiektu File reprezentującego plik:

    Kotlin

    cacheFile.delete()

    Java

    cacheFile.delete();
  • Metoda deleteFile() w kontekście aplikacji, która przekazuje 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 w wersji 4.4 (poziom API 19) lub nowszej 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 liczbę, aplikacje kierowane na Androida 10 (interfejs API na poziomie 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 należących 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.

Aby określić dostępność miejsca na dane, możesz użyć na przykład tych metod:

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ą. Musisz wybrać, którego z nich chcesz użyć do przechowywania danych aplikacji.

Aby uzyskać dostęp do różnych lokalizacji, zadzwoń pod numer ContextCompat.getExternalFilesDirs(). Jak widać w tym fragmencie kodu, pierwszy element zwracanej tablicy jest uważany za podstawowy wolumin pamięci zewnętrznej. Użyj tego wolumenu, 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);

Usuwanie plików pamięci podręcznej

Aby usunąć plik z zewnętrznego katalogu 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 działa z plikami multimedialnymi, które są przydatne dla użytkownika tylko w aplikacji, 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;
}

Pamiętaj, 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(). Zwraca katalog główny aplikacji w pamięci zewnętrznej.

Zapytanie o wolne miejsce

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

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ść zwracana przez funkcję getAllocatableBytes() może być większa niż obecna 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 wszystkich plików pamięci podręcznej z urządzenia.

Ten fragment kodu pokazuje, jak aplikacja może sprawdzić 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ę, która zawiera działanie ACTION_MANAGE_STORAGE. Ten zamiar wyświetla użytkownikowi prompt. W razie potrzeby ten komunikat może wyświetlać ilość wolnego miejsca na urządzeniu. Aby wyświetlić te informacje w przyjazny dla użytkownika sposób, użyj wyniku tej kalkulacji:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Poproś użytkownika o usunięcie wszystkich plików 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ę, która zawiera działanie intencyjne 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