Во многих случаях приложение создает файлы, к которым другим приложениям не нужен или не должен предоставляться доступ. Для хранения таких файлов приложений в системе предусмотрены следующие каталоги:
Каталоги во внутренней памяти. К ним относятся как каталоги для хранения постоянных файлов, так и каталоги для хранения кешированных данных. Система запрещает другим приложениям доступ к этим папкам, а в Android 10 (уровень API 29) и более поздних версиях они зашифрованы. Поэтому в них удобно хранить конфиденциальные данные, доступ к которым должен быть только у вашего приложения.
Каталоги внешнего хранилища. Включают специальное место для хранения постоянных файлов и ещё одно – для кешированных данных. Хотя другие приложения могут получить доступ к этим каталогам, если у них есть соответствующие разрешения, файлы, хранящиеся в этих каталогах, предназначены для использования только вашим приложением. Если вы специально намереваетесь создать файлы, к которым должны иметь доступ другие приложения, ваше приложение должно хранить эти файлы в общем хранилище внешнего хранилища.
Когда пользователь удаляет приложение, файлы, сохраненные в хранилище приложения, удаляются. Поэтому не используйте это хранилище для сохранения данных, которые должны оставаться доступными независимо от приложения. Например, если ваше приложение позволяет делать фотографии, пользователь ожидает, что они будут доступны даже после удаления приложения. В этом случае для сохранения таких файлов следует использовать общее хранилище и соответствующую коллекцию медиафайлов.
В следующих разделах описывается, как хранить файлы в каталогах приложений и получать к ним доступ.
Доступ из внутреннего хранилища
Для каждого приложения система предоставляет каталоги во внутреннем хранилище, где приложение может упорядочивать свои файлы. Один каталог предназначен для постоянных файлов приложения, а другой – для кешированных. Для чтения и записи файлов в этих каталогах приложению не требуются системные разрешения.
Другие приложения не могут получить доступ к файлам, хранящимся во внутренней памяти. Поэтому во внутренней памяти удобно хранить данные приложения, которые не должны быть доступны другим приложениям.
Однако помните, что такие каталоги обычно невелики. Прежде чем записывать файлы приложения во внутреннее хранилище, приложение должно запросить информацию о свободном месте на устройстве.
Доступ к постоянным файлам
Обычные постоянные файлы приложения находятся в каталоге, доступ к которому можно получить с помощью свойства filesDir объекта контекста. Фреймворк предоставляет несколько методов, которые помогают получать доступ к файлам в этом каталоге и сохранять их.
Доступ к файлам и их хранение
Для доступа к файлам и их хранения можно использовать File API.
Чтобы не снижать производительность приложения, не открывайте и не закрывайте один и тот же файл несколько раз.
В приведенном ниже фрагменте кода показано, как использовать File API:
Kotlin
val file = File(context.filesDir, filename)
Java
File file = new File(context.getFilesDir(), filename);
Как сохранить файл с помощью потока
В качестве альтернативы API File вы можете вызвать openFileOutput(), чтобы получить FileOutputStream, который записывает данные в файл в каталоге filesDir.
В следующем фрагменте кода показано, как записать текст в файл:
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()); }
Чтобы разрешить другим приложениям доступ к файлам, хранящимся в этом каталоге во внутренней памяти, используйте FileProvider с атрибутом FLAG_GRANT_READ_URI_PERMISSION.
Как получить доступ к файлу с помощью потока
Чтобы прочитать файл как поток, используйте 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(); }
Посмотреть список файлов
Вы можете получить массив, содержащий названия всех файлов в каталоге filesDir, вызвав метод fileList(), как показано в следующем фрагменте кода:
Kotlin
var files: Array<String> = context.fileList()
Java
Array<String> files = context.fileList();
Создание вложенных каталогов
Вы также можете создавать вложенные каталоги или открывать внутренние каталоги, вызывая getDir() в коде на основе Kotlin или передавая корневой каталог и новое имя каталога в конструктор File в коде на основе Java:
Kotlin
context.getDir(dirName, Context.MODE_PRIVATE)
Java
File directory = context.getFilesDir(); File file = new File(directory, filename);
Создание кеш-файлов
Если вам нужно сохранить конфиденциальные данные только временно, используйте для этого каталог кеша приложения во внутренней памяти. Как и в случае с любым хранилищем приложения, файлы, хранящиеся в этом каталоге, удаляются, когда пользователь удаляет приложение, хотя файлы в этом каталоге могут быть удалены раньше.
Чтобы создать кешированный файл, вызовите File.createTempFile():
Kotlin
File.createTempFile(filename, null, context.cacheDir)
Java
File.createTempFile(filename, null, context.getCacheDir());
Приложение получает доступ к файлу в этом каталоге с помощью свойства cacheDir объекта контекста и API File:
Kotlin
val cacheFile = File(context.cacheDir, filename)
Java
File cacheFile = new File(context.getCacheDir(), filename);
Удалить кешированные файлы
Хотя Android иногда удаляет кешированные файлы самостоятельно, не стоит полагаться на систему в этом вопросе. Файлы кеша приложения всегда должны храниться во внутренней памяти.
Чтобы удалить файл из каталога кеша во внутреннем хранилище, используйте один из следующих методов:
Метод
delete()объектаFile, представляющего файл.Kotlin
cacheFile.delete()
Java
cacheFile.delete();
Метод
deleteFile()контекста приложения, передающий название файла:Kotlin
context.deleteFile(cacheFileName)
Java
context.deleteFile(cacheFileName);
Доступ к внешнему хранилищу
Если во внутренней памяти недостаточно места для хранения файлов приложения, используйте внешнее хранилище. В системе есть каталоги внешнего хранилища, в которых приложение может упорядочивать файлы, полезные только в нем. Один каталог предназначен для постоянных файлов приложения, а другой – для кешированных.
В Android 4.4 (уровень API 19) и более поздних версиях приложениям не нужно запрашивать разрешения на доступ к хранилищу, чтобы использовать собственные каталоги на внешнем накопителе. Файлы, хранящиеся в этих каталогах, удаляются при удалении приложения.
На устройствах с Android 9 (уровень API 28) или более ранней версией ваше приложение может получать доступ к файлам других приложений, если у него есть соответствующие разрешения на доступ к хранилищу. Чтобы пользователи могли лучше контролировать свои файлы, а в хранилище не накапливался мусор, приложениям для Android 10 (уровня API 29) и более поздних версий по умолчанию предоставляется ограниченный доступ к внешнему хранилищу, или областям хранения данных. Если области хранения данных включены, приложения не могут получить доступ к каталогам других приложений.
Проверьте, доступно ли хранилище
Поскольку внешнее хранилище находится на физическом томе, который пользователь может удалить, перед попыткой чтения или записи данных приложения во внешнее хранилище убедитесь, что том доступен.
Чтобы узнать состояние тома, вызовите метод Environment.getExternalStorageState().
Если возвращено значение MEDIA_MOUNTED, вы можете читать и записывать файлы приложения во внешнем хранилище. Если это MEDIA_MOUNTED_READ_ONLY, вы можете только читать эти файлы.
Например, следующие методы позволяют определить доступность хранилища:
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); }
На устройствах без съемного внешнего хранилища используйте следующую команду, чтобы включить виртуальный том для тестирования логики доступности внешнего хранилища:
adb shell sm set-virtual-disk true
Выберите физическое место хранения
Иногда устройство, которое выделяет раздел своей внутренней памяти в качестве внешнего хранилища, также имеет слот для SD-карты. Это означает, что на устройстве есть несколько физических томов, которые могут содержать внешнее хранилище, поэтому вам нужно выбрать, какой из них использовать для хранилища вашего приложения.
Чтобы получить доступ к разным местоположениям, вызовите ContextCompat.getExternalFilesDirs().
Как показано во фрагменте кода, первый элемент в возвращенном массиве считается основным внешним хранилищем. Используйте этот том, если он не заполнен и доступен.
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];
Доступ к постоянным файлам
Чтобы получить доступ к файлам приложения из внешнего хранилища, вызовите метод getExternalFilesDir().
Чтобы не снижать производительность приложения, не открывайте и не закрывайте один и тот же файл несколько раз.
В следующем фрагменте кода показано, как вызвать getExternalFilesDir():
Kotlin
val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)
Java
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
Создание кеш-файлов
Чтобы добавить файл приложения в кеш внешнего хранилища, получите ссылку на externalCacheDir:
Kotlin
val externalCacheFile = File(context.externalCacheDir, filename)
Java
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
Удалить кешированные файлы
Чтобы удалить файл из внешнего каталога кеша, используйте метод delete() для объекта File, который представляет файл:
Kotlin
externalCacheFile.delete()
Java
externalCacheFile.delete();
Файлы мультимедиа
Если ваше приложение работает с медиафайлами, которые представляют ценность для пользователя только в вашем приложении, лучше всего хранить их в каталогах, предназначенных для конкретных приложений, во внешнем хранилище, как показано в следующем фрагменте кода:
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; }
Важно использовать названия каталогов, предоставляемые константами API, например DIRECTORY_PICTURES.
Это необходимо, чтобы система правильно обрабатывала файлы.
Если ни одно из предопределенных названий подкаталогов не подходит для ваших файлов, вы можете передать null в getExternalFilesDir(). Этот метод возвращает корневой каталог приложения во внешнем хранилище.
Запрос свободного места
У многих пользователей на устройствах мало свободного места, поэтому ваше приложение должно использовать его рационально.
Если вы заранее знаете, сколько данных будете хранить, то можете узнать, сколько места устройство может предоставить вашему приложению, вызвав getAllocatableBytes().
Возвращаемое значение getAllocatableBytes() может быть больше, чем текущий объем свободного места на устройстве. Это связано с тем, что система обнаружила файлы, которые можно удалить из каталогов кеша других приложений.
Если места достаточно, вызовите метод allocateBytes().
В противном случае приложение может попросить пользователя удалить некоторые файлы или очистить кеш на устройстве.
В следующем фрагменте кода показано, как приложение может запрашивать информацию о свободном месте на устройстве:
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); }
Как создать действие по управлению хранилищем
Вы можете объявить и создать в приложении специальное действие, которое позволит пользователю управлять данными, хранящимися на его устройстве. Вы объявляете это настраиваемое действие по управлению хранилищем с помощью атрибута android:manageSpaceActivity в файле манифеста. Приложения для управления файлами могут вызывать это действие, даже если ваше приложение не экспортирует его, то есть когда для действия задано значение false атрибута android:exported.
Попросите пользователя удалить некоторые файлы с устройства.
Чтобы попросить пользователя выбрать файлы на устройстве для удаления, вызовите намерение, включающее действие ACTION_MANAGE_STORAGE. Это намерение показывает пользователю запрос. При желании в этом запросе можно указать объем свободного места на устройстве. Чтобы показать эту информацию в удобном для пользователя виде, используйте результат следующего вычисления:
StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()
Попросите пользователя удалить все кешированные файлы.
Вы также можете попросить пользователя очистить кеш всех приложений на устройстве. Для этого вызовите намерение, которое включает действие ACTION_CLEAR_APP_CACHE.
Дополнительные ресурсы
Дополнительную информацию о сохранении файлов в хранилище устройства можно найти в следующих ресурсах.