Доступ к файлам приложения

Во многих случаях ваше приложение создает файлы, к которым другие приложения не имеют доступа или не должны иметь доступа. Система предоставляет следующие места для хранения таких файлов , специфичных для приложения :

  • Внутренние каталоги хранения: Эти каталоги включают в себя как выделенное место для хранения постоянных файлов, так и другое место для хранения данных кэша. Система предотвращает доступ к этим каталогам для других приложений, а в Android 10 (уровень API 29) и выше эти каталоги зашифрованы. Эти характеристики делают эти каталоги хорошим местом для хранения конфиденциальных данных, к которым имеет доступ только ваше приложение.

  • Внешние каталоги хранения: Эти каталоги включают в себя как выделенное место для хранения постоянных файлов, так и другое место для хранения данных кэша. Хотя другое приложение может получить доступ к этим каталогам, если у него есть соответствующие разрешения, файлы, хранящиеся в этих каталогах, предназначены для использования только вашим приложением. Если вы специально планируете создавать файлы, к которым другие приложения должны иметь доступ, ваше приложение должно хранить эти файлы в общей части внешнего хранилища.

Когда пользователь удаляет ваше приложение, файлы, сохраненные в хранилище, предназначенном для этого приложения, удаляются. Из-за такого поведения вам не следует использовать это хранилище для сохранения чего-либо, что пользователь ожидает сохранить независимо от вашего приложения. Например, если ваше приложение позволяет пользователям делать фотографии, пользователь ожидает, что он сможет получить доступ к этим фотографиям даже после удаления приложения. Поэтому вместо этого вам следует использовать общее хранилище для сохранения таких файлов в соответствующую коллекцию медиафайлов .

В следующих разделах описывается, как хранить файлы и получать к ним доступ в каталогах, относящихся к конкретным приложениям.

Доступ из внутренней памяти

Для каждого приложения система предоставляет в памяти отдельные каталоги, где приложение может организовывать свои файлы. Один каталог предназначен для постоянных файлов приложения , а другой содержит кэшированные файлы . Вашему приложению не требуются никакие системные разрешения для чтения и записи в файлы в этих каталогах.

Другие приложения не могут получить доступ к файлам, хранящимся во внутренней памяти. Поэтому внутренняя память — хорошее место для хранения данных приложений, к которым другие приложения не должны иметь доступа.

Однако следует помнить, что эти каталоги, как правило, небольшие. Перед записью файлов, специфичных для приложения, во внутреннюю память, ваше приложение должно проверить наличие свободного места на устройстве.

Доступ к постоянным файлам

Обычные, постоянно хранящиеся файлы вашего приложения находятся в каталоге, к которому вы можете получить доступ, используя свойство filesDir объекта контекста. Фреймворк предоставляет несколько методов, которые помогут вам получить доступ к файлам в этом каталоге и сохранить их.

Доступ к файлам и их хранение

Вы можете использовать File API для доступа к файлам и их хранения.

Чтобы поддерживать производительность вашего приложения, не открывайте и не закрывайте один и тот же файл несколько раз.

Следующий фрагмент кода демонстрирует, как использовать File API:

Котлин

val file = File(context.filesDir, filename)

Java

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())
}

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() :

Котлин

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() , как показано в следующем фрагменте кода:

Котлин

var files: Array<String> = context.fileList()

Java

Array<String> files = context.fileList();

Создание вложенных каталогов

Вы также можете создавать вложенные каталоги или открывать внутренний каталог, вызывая метод getDir() в коде на Kotlin или передавая корневой каталог и имя нового каталога в конструктор File в коде на Java:

Котлин

context.getDir(dirName, Context.MODE_PRIVATE)

Java

File directory = context.getFilesDir();
File file = new File(directory, filename);

Создайте файлы кэша

Если вам необходимо временно хранить конфиденциальные данные, следует использовать выделенный для приложения каталог кэша во внутренней памяти. Как и в случае со всеми хранилищами, предназначенными для конкретных приложений, файлы, хранящиеся в этом каталоге, удаляются при удалении приложения пользователем, хотя удаление файлов в этом каталоге может произойти и раньше .

Для создания кэшированного файла вызовите метод File.createTempFile() :

Котлин

File.createTempFile(filename, null, context.cacheDir)

Java

File.createTempFile(filename, null, context.getCacheDir());

Ваше приложение обращается к файлу в этом каталоге, используя свойство cacheDir объекта контекста и File API:

Котлин

val cacheFile = File(context.cacheDir, filename)

Java

File cacheFile = new File(context.getCacheDir(), filename);

Удалите файлы кэша

Хотя Android иногда самостоятельно удаляет файлы кэша, не стоит полагаться на систему в этом вопросе. Файлы кэша вашего приложения всегда должны храниться во внутренней памяти устройства.

Для удаления файла из кэш-каталога во внутренней памяти используйте один из следующих способов:

  • Метод delete() объекта File , представляющего файл:

    Котлин

    cacheFile.delete()

    Java

    cacheFile.delete();
  • Метод deleteFile() контекста приложения, передающий имя файла:

    Котлин

    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 , то вы можете только читать эти файлы.

Например, для определения доступности хранилища полезны следующие методы:

Котлин

// 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() . Как показано в фрагменте кода, первый элемент возвращаемого массива считается основным внешним томом хранения. Используйте этот том, если он не заполнен или недоступен.

Котлин

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() :

Котлин

val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)

Java

File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

Создайте файлы кэша

Чтобы добавить файл, специфичный для приложения, в кэш во внешнем хранилище, получите ссылку на externalCacheDir :

Котлин

val externalCacheFile = File(context.externalCacheDir, filename)

Java

File externalCacheFile = new File(context.getExternalCacheDir(), filename);

Удалите файлы кэша

Для удаления файла из внешнего каталога кэша используйте метод delete() объекта File , представляющего этот файл:

Котлин

externalCacheFile.delete()

Java

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
}

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() . В противном случае ваше приложение может запросить у пользователя удаление некоторых файлов с устройства или удаление всех файлов кэша с устройства.

Приведенный ниже фрагмент кода демонстрирует пример того, как ваше приложение может запрашивать информацию о свободном месте на устройстве:

Котлин

// 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 в файле манифеста. Приложения-файловые менеджеры могут вызывать эту активность, даже если ваше приложение не экспортирует эту активность; то есть, если ваша активность устанавливает android:exported в false .

Предложите пользователю удалить некоторые файлы устройства.

Чтобы запросить у пользователя выбор файлов на устройстве для удаления, вызовите интент, включающий действие ACTION_MANAGE_STORAGE . Этот интент отобразит пользователю запрос. При желании этот запрос может показывать объем свободного места на устройстве. Для отображения этой удобной для пользователя информации используйте результат следующего вычисления:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Предложите пользователю удалить все файлы кэша.

В качестве альтернативы вы можете попросить пользователя очистить кэш-файлы всех приложений на устройстве. Для этого вызовите интент, включающий действие ACTION_CLEAR_APP_CACHE .

Дополнительные ресурсы

Для получения дополнительной информации о сохранении файлов во внутреннюю память устройства обратитесь к следующим ресурсам.

Видео