W wielu przypadkach aplikacja tworzy pliki, do których inne aplikacje nie muszą mieć dostępu lub nie powinny go mieć. System udostępnia te lokalizacje do przechowywania takich plików specyficznych dla aplikacji:
Katalogi pamięci wewnętrznej: obejmują one zarówno miejsce do przechowywania trwałych plików, jak i miejsce 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 nowszym są one szyfrowane. Te cechy sprawiają, że te lokalizacje są dobrym miejscem do przechowywania danych wrażliwych, do których dostęp ma tylko Twoja aplikacja.
Katalogi pamięci zewnętrznej: obejmują one zarówno miejsce do przechowywania trwałych plików, jak i miejsce 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 w nich przechowywane 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 przeznaczonej dla aplikacji zostaną usunięte. Z tego powodu nie należy używać tego miejsca do przechowywania niczego, co użytkownik chce zachować niezależnie od aplikacji. Jeśli na przykład aplikacja umożliwia robienie zdjęć, użytkownik oczekuje, że będzie mógł uzyskać 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 kolejnych sekcjach opisujemy, 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 w pamięci wewnętrznej katalogi, w których aplikacja może organizować swoje pliki. Jeden katalog jest przeznaczony na trwałe pliki aplikacji, a drugi zawiera pliki aplikacji zapisane w pamięci podręcznej. Aplikacja nie wymaga żadnych uprawnień systemowych do odczytywania i zapisywania 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 aplikacja zapisze pliki w pamięci wewnętrznej, 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 możesz uzyskać dostęp za pomocą właściwości filesDir
obiektu kontekstu. Framework udostępnia kilka metod, które pomagają uzyskać dostęp do plików w tym katalogu i je w nim przechowywać.
Dostęp do plików i ich przechowywanie
Do uzyskiwania dostępu do plików i ich przechowywania możesz używać interfejsu File
API.
Aby utrzymać wydajność aplikacji, nie otwieraj i nie zamykaj tego samego pliku wielokrotnie.
Poniższy 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ć
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 umożliwić innym aplikacjom 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
.
Dostęp do pliku za pomocą strumienia
Aby odczytać plik jako strumień, użyj tego kodu: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żesz uzyskać, wywołując funkcję
fileList()
, jak pokazano w tym fragmencie kodu:
Kotlin
var files: Array<String> = context.fileList()
Java
Array<String> files = context.fileList();
Tworzenie zagnieżdżonych katalogów
Możesz też utworzyć zagnieżdżone katalogi lub otworzyć katalog wewnętrzny, wywołując getDir()
w kodzie opartym na Kotlinie lub przekazując katalog główny i nazwę nowego katalogu do konstruktora File
w kodzie opartym na Javie:
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 musisz 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 masowych specyficznych dla 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 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 z pamięci podręcznej
Chociaż Android czasami sam usuwa pliki pamięci podręcznej, nie należy polegać na tym, że system wyczyści te pliki za Ciebie. 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()
w obiekcieFile
reprezentującym plik:Kotlin
cacheFile.delete()
Java
cacheFile.delete();
Metoda
deleteFile()
kontekstu aplikacji, która przekazuje 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 w pamięci zewnętrznej katalogi, w których aplikacja może porządkować pliki przydatne dla użytkownika tylko w jej obrębie. Jeden katalog jest przeznaczony na trwałe pliki aplikacji, a drugi zawiera pliki aplikacji zapisane w pamięci podręcznej.
Na Androidzie 4.4 (poziom API 19) lub nowszym aplikacja nie musi prosić o żadne uprawnienia związane z pamięcią, aby uzyskać dostęp do katalogów specyficznych dla 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 innych aplikacji, o ile ma odpowiednie uprawnienia do przechowywania danych. Aby dać użytkownikom większą kontrolę nad plikami i ograniczyć ich liczbę, aplikacje kierowane na Androida 10 (interfejs API na poziomie 29) i nowsze wersje mają domyślnie ograniczony dostęp do pamięci zewnętrznej, czyli ograniczony dostęp do miejsca na dane. Gdy jest włączony dostęp do pamięci w zakresie aplikacji, aplikacje nie mogą uzyskiwać dostępu do katalogów należących do innych aplikacji.
Sprawdź, czy masz dostępne miejsce na dane.
Pamięć zewnętrzna znajduje się na woluminie fizycznym, który użytkownik może usunąć. Zanim spróbujesz odczytać z pamięci zewnętrznej dane specyficzne dla aplikacji lub zapisać w niej takie dane, sprawdź, czy wolumin jest dostępny.
Stan woluminu 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ą w pamięci zewnętrznej. 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 można używać 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ć wirtualny wolumin do testowania logiki dostępności pamięci zewnętrznej:
adb shell sm set-virtual-disk true
Wybierz fizyczną lokalizację pamięci
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ą, więc musisz wybrać, który z nich ma być używany do przechowywania danych aplikacji.
Aby uzyskać dostęp do różnych lokalizacji, zadzwoń pod numer
ContextCompat.getExternalFilesDirs()
.
Jak widać w fragmencie kodu, pierwszy element zwróconej tablicy jest uznawany za 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 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ć 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 specyficzny dla aplikacji do pamięci podręcznej w pamięci zewnętrznej, uzyskaj odniesienie 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 Twoja aplikacja działa z plikami multimedialnymi, które są przydatne dla użytkownika tylko w jej obrębie, najlepiej przechowywać je w katalogach specyficznych dla 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, 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 przekazać null
do getExternalFilesDir()
. Zwraca to główny katalog aplikacji w pamięci zewnętrznej.
Sprawdzanie wolnego miejsca
Wielu użytkowników nie ma na urządzeniach dużo miejsca na dane, więc aplikacja powinna oszczędnie wykorzystywać przestrzeń dyskową.
Jeśli wiesz z wyprzedzeniem, ile danych przechowujesz, możesz sprawdzić, ile miejsca na dane urządzenie może udostępnić Twojej aplikacji, wywołując funkcję getAllocatableBytes()
.
Wartość zwracana przez 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żna 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 wszystkich plików pamięci podręcznej z urządzenia.
Ten 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 przechowywanymi przez aplikację na jego urządzeniu. Tę niestandardową aktywność „zarządzanie miejscem” deklarujesz za pomocą atrybutu android:manageSpaceActivity
w pliku manifestu. Aplikacje do zarządzania plikami mogą wywoływać tę aktywność nawet wtedy, gdy Twoja aplikacja jej nie eksportuje, czyli gdy aktywność ma ustawioną wartość 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
. Ten zamiar wyświetla użytkownikowi prompt. W razie potrzeby w tym komunikacie może być wyświetlana ilość wolnego miejsca na urządzeniu. Aby wyświetlić te informacje w przystępnej formie, użyj wyniku tego obliczenia:
StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()
Proś 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.