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

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

  • Каталоги внутреннего хранилища. Эти каталоги включают в себя как выделенное место для хранения постоянных файлов, так и другое место для хранения данных кэша. Система запрещает другим приложениям доступ к этим местам, а в 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) и выше, по умолчанию предоставляется ограниченный доступ к внешнему хранилищу или хранилищу с ограниченной областью действия . Если хранилище с ограниченной областью включено, приложения не могут получить доступ к каталогам конкретных приложений, принадлежащим другим приложениям.

Убедитесь, что хранилище доступно

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

Вы можете запросить состояние тома, вызвав 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 .

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

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

Видео