Во многих случаях ваше приложение создаёт файлы, к которым другим приложениям не нужен или не должен быть доступ. Система предоставляет следующие места для хранения таких файлов , специфичных для приложения :
Каталоги внутреннего хранилища: Эти каталоги включают в себя как выделенное место для хранения постоянных файлов, так и ещё одно место для хранения данных кэша. Система блокирует доступ других приложений к этим каталогам, а в Android 10 (уровень API 29) и выше эти каталоги зашифрованы. Благодаря этим характеристикам эти каталоги отлично подходят для хранения конфиденциальных данных, доступ к которым имеет только само приложение.
Каталоги внешнего хранилища: эти каталоги включают в себя как выделенное место для хранения постоянных файлов, так и ещё одно место для хранения данных кэша. Хотя другое приложение может получить доступ к этим каталогам при наличии у него соответствующих разрешений, файлы, хранящиеся в этих каталогах, предназначены только для использования вашим приложением. Если вы намерены создавать файлы, к которым другие приложения должны иметь доступ, ваше приложение должно хранить эти файлы в общем хранилище внешнего хранилища.
Когда пользователь удаляет ваше приложение, файлы, сохранённые в хранилище приложения, удаляются. В связи с этим не следует использовать это хранилище для сохранения чего-либо, что пользователь ожидает сохранить независимо от вашего приложения. Например, если ваше приложение позволяет пользователям делать фотографии, пользователь ожидает, что у него будет доступ к этим фотографиям даже после удаления приложения. Поэтому вместо этого следует использовать общее хранилище для сохранения таких файлов в соответствующей медиаколлекции .
В следующих разделах описывается, как хранить и получать доступ к файлам в каталогах, специфичных для приложения.
Доступ из внутреннего хранилища
Для каждого приложения система предоставляет каталоги во внутреннем хранилище, где приложение может организовать свои файлы. Один каталог предназначен для постоянных файлов приложения , а другой — для кэшированных файлов . Вашему приложению не требуются системные разрешения для чтения и записи файлов в этих каталогах.
Другие приложения не могут получить доступ к файлам, хранящимся во внутренней памяти. Это делает внутреннюю память подходящим местом для хранения данных приложений, к которым другие приложения не должны иметь доступа.
Однако имейте в виду, что эти каталоги, как правило, небольшие. Прежде чем записывать файлы приложения во внутреннюю память, приложение должно запросить свободное место на устройстве.
Доступ к постоянным файлам
Обычные постоянные файлы вашего приложения находятся в каталоге, доступ к которому можно получить через свойство filesDir
объекта контекста. Фреймворк предоставляет несколько методов для доступа к файлам в этом каталоге и их хранения.
Доступ и хранение файлов
Для доступа к файлам и их хранения можно использовать File
API.
Чтобы сохранить производительность вашего приложения, не открывайте и не закрывайте один и тот же файл несколько раз.
Следующий фрагмент кода демонстрирует, как использовать File
API:
Котлин
val file = File(context.filesDir, filename)
Ява
File file = new File(context.getFilesDir(), filename);
Хранить файл, используя поток
В качестве альтернативы использованию File
API вы можете вызвать openFileOutput()
, чтобы получить FileOutputStream
, который записывает данные в файл в каталоге filesDir
.
Следующий фрагмент кода показывает, как записать текст в файл:
Котлин
val filename = "myfile" val fileContents = "Hello world!" context.openFileOutput(filename, Context.MODE_PRIVATE).use { it.write(fileContents.toByteArray()) }
Ява
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()
:
Котлин
context.openFileInput(filename).bufferedReader().useLines { lines -> lines.fold("") { some, text -> "$some\n$text" } }
Ява
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()
, как показано в следующем фрагменте кода:
Котлин
var files: Array<String> = context.fileList()
Ява
Array<String> files = context.fileList();
Создавать вложенные каталоги
Вы также можете создать вложенные каталоги или открыть внутренний каталог, вызвав getDir()
в коде на основе Kotlin или передав корневой каталог и новое имя каталога в конструктор File
в коде на основе Java:
Котлин
context.getDir(dirName, Context.MODE_PRIVATE)
Ява
File directory = context.getFilesDir(); File file = new File(directory, filename);
Создать файлы кеша
Если вам требуется временное хранение конфиденциальных данных, используйте выделенный каталог кэша приложения во внутренней памяти. Как и в случае со всеми хранилищами, связанными с приложениями, файлы, хранящиеся в этом каталоге, удаляются при удалении приложения пользователем, хотя файлы в этом каталоге могут быть удалены и раньше .
Чтобы создать кэшированный файл, вызовите File.createTempFile()
:
Котлин
File.createTempFile(filename, null, context.cacheDir)
Ява
File.createTempFile(filename, null, context.getCacheDir());
Ваше приложение обращается к файлу в этом каталоге, используя свойство cacheDir
объекта контекста и API File
:
Котлин
val cacheFile = File(context.cacheDir, filename)
Ява
File cacheFile = new File(context.getCacheDir(), filename);
Удалить файлы кэша
Несмотря на то, что Android иногда самостоятельно удаляет файлы кэша, не стоит полагаться на то, что система сделает это за вас. Файлы кэша приложений всегда следует хранить во внутренней памяти устройства.
Чтобы удалить файл из кэш-каталога во внутреннем хранилище, воспользуйтесь одним из следующих методов:
Метод
delete()
объектаFile
, представляющего файл:Котлин
cacheFile.delete()
Ява
cacheFile.delete();
Метод
deleteFile()
контекста приложения, передающий имя файла:Котлин
context.deleteFile(cacheFileName)
Ява
context.deleteFile(cacheFileName);
Доступ из внешнего хранилища
Если внутреннего хранилища недостаточно для хранения файлов, относящихся к приложению, рассмотрите возможность использования внешнего хранилища. Система предоставляет каталоги во внешнем хранилище, где приложение может организовать файлы, представляющие ценность только для пользователя в рамках вашего приложения. Один каталог предназначен для постоянных файлов вашего приложения , а другой содержит кэшированные файлы .
На устройствах Android 4.4 (API уровня 19) и выше вашему приложению не требуется запрашивать разрешения на доступ к каталогам приложения во внешнем хранилище. Файлы, хранящиеся в этих каталогах, удаляются при удалении приложения.
На устройствах под управлением Android 9 (уровень API 28) или ниже ваше приложение может получать доступ к файлам других приложений, специфичным для данного приложения, при условии, что у него есть соответствующие разрешения на доступ к хранилищу. Чтобы предоставить пользователям больше контроля над файлами и уменьшить количество файлов, приложениям для Android 10 (уровень API 29) и выше по умолчанию предоставляется ограниченный доступ к внешнему хранилищу ( scoped storage ). При включенном ограниченном хранилище приложения не могут получать доступ к каталогам других приложений, специфичным для данного приложения.
Убедитесь, что хранилище доступно
Поскольку внешнее хранилище находится на физическом томе, который пользователь может удалить, убедитесь, что том доступен, прежде чем пытаться прочитать данные приложения из внешнего хранилища или записать данные приложения на него.
Вы можете запросить состояние тома, вызвав Environment.getExternalStorageState()
. Если возвращено состояние MEDIA_MOUNTED
, вы можете читать и записывать файлы, специфичные для приложения, во внешнем хранилище. Если же это MEDIA_MOUNTED_READ_ONLY
, вы можете только читать эти файлы.
Например, для определения доступности хранилища полезны следующие методы:
Котлин
// 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) }
Ява
// 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()
. Как показано во фрагменте кода, первый элемент возвращаемого массива считается основным томом внешнего хранилища. Используйте этот том, если он не заполнен или недоступен.
Котлин
val externalStorageVolumes: Array<out File> = ContextCompat.getExternalFilesDirs(applicationContext, null) val primaryExternalStorage = externalStorageVolumes[0]
Ява
File[] externalStorageVolumes = ContextCompat.getExternalFilesDirs(getApplicationContext(), null); File primaryExternalStorage = externalStorageVolumes[0];
Доступ к постоянным файлам
Чтобы получить доступ к файлам приложения из внешнего хранилища, вызовите getExternalFilesDir()
.
Чтобы сохранить производительность вашего приложения, не открывайте и не закрывайте один и тот же файл несколько раз.
Следующий фрагмент кода демонстрирует, как вызвать getExternalFilesDir()
:
Котлин
val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)
Ява
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
Создать файлы кеша
Чтобы добавить файл, специфичный для приложения, в кэш во внешнем хранилище, получите ссылку на externalCacheDir
:
Котлин
val externalCacheFile = File(context.externalCacheDir, filename)
Ява
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
Удалить файлы кэша
Чтобы удалить файл из внешнего каталога кэша, используйте метод delete()
объекта File
, представляющего файл:
Котлин
externalCacheFile.delete()
Ява
externalCacheFile.delete();
Медиа-контент
Если ваше приложение работает с медиафайлами, которые представляют ценность для пользователя только внутри вашего приложения, лучше всего хранить их в каталогах, относящихся к конкретному приложению, во внешнем хранилище, как показано в следующем фрагменте кода:
Котлин
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 }
Ява
@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()
. В противном случае приложение может запросить у пользователя удаление некоторых файлов с устройства или удаление всех файлов кэша с устройства.
В следующем фрагменте кода показан пример того, как ваше приложение может запрашивать свободное место на устройстве:
Котлин
// 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 } }
Ява
// 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
в файле манифеста. Приложения для управления файлами могут вызывать это действие, даже если ваше приложение не экспортирует это действие, то есть когда ваше действие устанавливает для свойства android:exported
значение false
.
Попросить пользователя удалить некоторые файлы устройства
Чтобы запросить у пользователя выбор файлов на устройстве для удаления, вызовите намерение, включающее действие ACTION_MANAGE_STORAGE
. Это намерение выводит пользователю запрос. При желании в запросе может отображаться объём свободного места на устройстве. Для отображения этой удобной для пользователя информации используйте результат следующего расчёта:
StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()
Попросить пользователя удалить все файлы кэша
В качестве альтернативы вы можете попросить пользователя очистить кэш всех приложений на устройстве. Для этого вызовите намерение, включающее действие ACTION_CLEAR_APP_CACHE
.
Дополнительные ресурсы
Дополнительную информацию о сохранении файлов в хранилище устройства можно найти в следующих ресурсах.