Во многих случаях ваше приложение создает файлы, к которым другим приложениям не нужен доступ или к которым они не должны иметь доступ. Система предоставляет следующие места для хранения таких файлов , специфичных для приложения :
Внутренние каталоги хранения: эти каталоги включают как выделенное место для хранения постоянных файлов, так и другое место для хранения данных кэша. Система не позволяет другим приложениям получать доступ к этим местам, а на Android 10 (уровень API 29) и выше эти места зашифрованы. Эти характеристики делают эти места хорошим местом для хранения конфиденциальных данных, к которым может получить доступ только само ваше приложение.
Внешние каталоги хранения: эти каталоги включают как выделенное место для хранения постоянных файлов, так и другое место для хранения данных кэша. Хотя другое приложение может получить доступ к этим каталогам, если у этого приложения есть соответствующие разрешения, файлы, хранящиеся в этих каталогах, предназначены для использования только вашим приложением. Если вы специально собираетесь создавать файлы, к которым другие приложения должны иметь доступ, ваше приложение должно хранить эти файлы в общей части внешнего хранилища.
Когда пользователь удаляет ваше приложение, файлы, сохраненные в хранилище, специфичном для приложения, удаляются. Из-за этого поведения вам не следует использовать это хранилище для сохранения чего-либо, что пользователь ожидает сохранить независимо от вашего приложения. Например, если ваше приложение позволяет пользователям делать фотографии, пользователь будет ожидать, что он сможет получить доступ к этим фотографиям даже после удаления вашего приложения. Поэтому вместо этого вам следует использовать общее хранилище для сохранения этих типов файлов в соответствующей коллекции мультимедиа .
В следующих разделах описывается, как хранить и получать доступ к файлам в каталогах, специфичных для приложения.
Доступ из внутреннего хранилища
Для каждого приложения система предоставляет каталоги во внутреннем хранилище, где приложение может организовывать свои файлы. Один каталог предназначен для постоянных файлов вашего приложения , а другой содержит кэшированные файлы вашего приложения . Вашему приложению не требуются никакие системные разрешения для чтения и записи файлов в этих каталогах.
Другие приложения не могут получить доступ к файлам, хранящимся во внутреннем хранилище. Это делает внутреннее хранилище хорошим местом для данных приложений, к которым другие приложения не должны иметь доступа.
Однако имейте в виду, что эти каталоги, как правило, небольшие. Перед записью файлов, специфичных для приложения, во внутреннюю память ваше приложение должно запросить свободное место на устройстве.
Доступ к постоянным файлам
Обычные постоянные файлы вашего приложения находятся в каталоге, к которому вы можете получить доступ с помощью свойства filesDir
объекта контекста. Фреймворк предоставляет несколько методов, которые помогут вам получить доступ и сохранить файлы в этом каталоге.
Доступ и хранение файлов
Для доступа к файлам и их хранения можно использовать File
API.
Чтобы сохранить производительность вашего приложения, не открывайте и не закрывайте один и тот же файл несколько раз.
Следующий фрагмент кода демонстрирует, как использовать API File
:
Котлин
val file = File(context.filesDir, filename)
Ява
File file = new File(context.getFilesDir(), filename);
Сохраните файл, используя поток
В качестве альтернативы использованию API File
можно вызвать 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) и выше, по умолчанию предоставляется доступ к области внешнего хранилища или области хранилища . Когда область хранилища включена, приложения не могут получить доступ к каталогам, специфичным для приложения, которые принадлежат другим приложениям.
Убедитесь, что хранилище доступно
Поскольку внешнее хранилище находится на физическом томе, который пользователь может удалить, убедитесь, что том доступен, прежде чем пытаться считать данные приложения с внешнего хранилища или записать данные приложения на него.
Вы можете запросить состояние тома, вызвав 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
.
Дополнительные ресурсы
Дополнительную информацию о сохранении файлов в хранилище устройства можно найти на следующих ресурсах.