Accéder aux fichiers spécifiques à l'application

Dans de nombreux cas, votre application crée des fichiers auxquels d'autres applications n'ont pas besoin d'accéder ou ne doivent pas accéder. Le système fournit les emplacements suivants pour stocker de tels fichiers spécifiques à l'application :

  • Répertoires de stockage interne : ces répertoires incluent un emplacement dédié pour le stockage des fichiers persistants et un autre pour le stockage des données de cache. Le système empêche d'autres applications d'accéder à ces emplacements. Ils sont chiffrés sur Android 10 (niveau d'API 29) ou version ultérieure. Ces caractéristiques font de ces emplacements un endroit idéal pour stocker des données sensibles auxquelles seule votre application peut accéder.

  • Répertoires de stockage externe : ces répertoires incluent un emplacement dédié pour le stockage des fichiers persistants et un autre pour le stockage des données de cache. Bien qu'il soit possible qu'une autre application accède à ces répertoires si elle dispose des autorisations appropriées, les fichiers stockés dans ces répertoires sont destinés à être utilisés uniquement par votre application. Si vous avez l'intention de créer des fichiers auxquels d'autres applications doivent avoir accès, votre application doit les stocker dans la partie stockage partagé du stockage externe.

Lorsque l'utilisateur désinstalle votre application, les fichiers enregistrés dans l'espace de stockage spécifique à l'application sont supprimés. En raison de ce comportement, vous ne devez pas utiliser cet espace de stockage pour enregistrer les éléments que l'utilisateur s'attend à conserver indépendamment de votre application. Par exemple, si votre application permet aux utilisateurs de prendre des photos, ils s'attendent à pouvoir y accéder même après avoir désinstallé votre application. Vous devez donc utiliser un espace de stockage partagé pour enregistrer ces types de fichiers dans la collection multimédia appropriée.

Les sections suivantes expliquent comment stocker des fichiers dans des répertoires spécifiques à une application et y accéder.

Accéder depuis la mémoire de stockage interne

Pour chaque application, le système fournit des répertoires dans la mémoire de stockage interne où une application peut organiser ses fichiers. L'un est conçu pour les fichiers persistants de votre application, et l'autre contient les fichiers mis en cache de votre application. Votre application ne nécessite aucune autorisation système pour lire et écrire des fichiers dans ces répertoires.

Les autres applications ne peuvent pas accéder aux fichiers stockés dans la mémoire de stockage interne. La mémoire de stockage interne est donc un bon endroit pour les données auxquelles les autres applications ne doivent pas accéder.

Toutefois, n'oubliez pas que ces répertoires sont peu volumineux. Avant d'écrire des fichiers spécifiques à l'application dans la mémoire de stockage interne, celle-ci doit interroger l'espace disponible sur l'appareil.

Accéder aux fichiers persistants

Les fichiers ordinaires et persistants de votre application se trouvent dans un répertoire auquel vous pouvez accéder à l'aide de la propriété filesDir d'un objet de contexte. Le framework fournit plusieurs méthodes pour vous aider à accéder aux fichiers de ce répertoire et à les stocker.

Accéder aux fichiers et les stocker

Vous pouvez utiliser l'API File pour accéder aux fichiers et les stocker.

Pour maintenir les performances de votre application, évitez d'ouvrir et fermer le même fichier plusieurs fois.

L'extrait de code suivant montre comment utiliser l'API File :

Kotlin

val file = File(context.filesDir, filename)

Java

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

Stocker un fichier à l'aide d'un flux

Au lieu d'utiliser l'API File, vous pouvez appeler openFileOutput() pour obtenir un FileOutputStream qui écrit dans un répertoire filesDir.

L'extrait de code suivant montre comment rédiger du texte dans un fichier :

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

Pour autoriser d'autres applications à accéder aux fichiers stockés dans ce répertoire dans la mémoire de stockage interne, utilisez un FileProvider avec l'attribut FLAG_GRANT_READ_URI_PERMISSION.

Accéder à un fichier à l'aide d'un flux

Pour lire un fichier en tant que flux, utilisez 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();
}

Afficher la liste des fichiers

Vous pouvez obtenir un tableau contenant les noms de tous les fichiers du répertoire filesDir en appelant fileList(), comme indiqué dans l'extrait de code suivant :

Kotlin

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

Java

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

Créer des répertoires imbriqués

Vous pouvez également créer des répertoires imbriqués ou ouvrir un répertoire interne en appelant getDir() dans du code basé sur Kotlin ou en transmettant le répertoire racine et un nouveau nom de répertoire dans un constructeur File dans le code Java :

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

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

Créer des fichiers de cache

Si vous ne devez stocker des données sensibles que temporairement, vous devez enregistrer les données dans le répertoire de cache désigné de l'application dans la mémoire interne. Comme pour tout espace de stockage spécifique à une application, les fichiers stockés dans ce répertoire sont supprimés lorsque l'utilisateur désinstalle votre application. Toutefois, les fichiers de ce répertoire peuvent être supprimés plus tôt.

Pour créer un fichier mis en cache, appelez File.createTempFile() :

Kotlin

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

Java

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

Votre application accède à un fichier de ce répertoire à l'aide de la propriété cacheDir d'un objet de contexte et de l'API File :

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

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

Supprimer les fichiers du cache

Bien qu'Android supprime automatiquement des fichiers de cache, vous ne devez pas compter sur le système pour nettoyer ces fichiers. Vous devez toujours assurer la gestion des fichiers de cache de votre application dans la mémoire de stockage interne.

Pour supprimer un fichier du répertoire de cache dans la mémoire de stockage interne, utilisez l'une des méthodes suivantes :

  • La méthode delete() sur un objet File qui représente le fichier :

    Kotlin

    cacheFile.delete()

    Java

    cacheFile.delete();
  • La méthode deleteFile() du contexte de l'application, en transmettant le nom du fichier :

    Kotlin

    context.deleteFile(cacheFileName)

    Java

    context.deleteFile(cacheFileName);

Accéder depuis un espace de stockage externe

Si la mémoire de stockage interne est insuffisante pour stocker les fichiers spécifiques à l'application, envisagez d'utiliser un espace de stockage externe. Le système fournit des répertoires au sein d'un espace de stockage externe où une application ne peut organiser les fichiers utiles pour l'utilisateur que dans votre application. L'un est conçu pour les fichiers persistants de votre application, tandis que l'autre contient les fichiers mis en cache de votre application.

Sous Android 4.4 (niveau d'API 19) ou version ultérieure, votre application n'a pas besoin de demander des autorisations liées au stockage pour accéder aux répertoires spécifiques à l'application dans un espace de stockage externe. Les fichiers stockés dans ces répertoires sont supprimés lorsque votre application est désinstallée.

Sur les appareils équipés d'Android 9 (niveau d'API 28) ou version antérieure, votre application peut accéder aux fichiers spécifiques qui appartiennent à d'autres applications, à condition que celle-ci dispose des autorisations de stockage appropriées. Pour donner plus de contrôle sur les fichiers et limiter l'encombrement des fichiers, les applications qui ciblent Android 10 (niveau d'API 29) ou une version ultérieure bénéficient par défaut d'un accès limité à l'espace de stockage. Lorsque l'espace de stockage cloisonné est activé, les applications ne peuvent pas accéder aux répertoires spécifiques à d'autres applications.

Vérifier que l'espace de stockage est disponible

Puisque le stockage externe réside sur un volume physique que l'utilisateur peut être en mesure de supprimer, vérifiez que le volume est accessible avant d'essayer de lire ou d'écrire des données spécifiques à une application dans le stockage externe.

Vous pouvez interroger l'état du volume en appelant Environment.getExternalStorageState(). Si l'état renvoyé est MEDIA_MOUNTED, vous pouvez lire et écrire des fichiers spécifiques à l'application sur un espace de stockage externe. S'il l'état est MEDIA_MOUNTED_READ_ONLY, vous n'avez accès qu'en lecture seule.

Par exemple, les méthodes suivantes sont utiles pour déterminer la disponibilité du stockage :

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

Sur les appareils ne disposant pas d'un espace de stockage externe amovible, exécutez la commande suivante pour activer un volume virtuel permettant de tester la logique de disponibilité du stockage externe :

adb shell sm set-virtual-disk true

Sélectionner un emplacement de stockage physique

Parfois, un appareil qui alloue une partition de sa mémoire interne en tant que stockage externe fournit également un emplacement pour carte SD. Cela signifie que l'appareil dispose de plusieurs volumes physiques pouvant contenir un stockage externe. Vous devez donc sélectionner celui à utiliser pour votre stockage spécifique à l'application.

Pour accéder aux différents emplacements, appelez ContextCompat.getExternalFilesDirs(). Comme indiqué dans l'extrait de code, le premier élément du tableau renvoyé est considéré comme le volume de stockage externe principal. Utilisez ce volume, sauf s'il est complet ou indisponible.

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];

Accéder aux fichiers persistants

Pour accéder aux fichiers spécifiques à l'application depuis un espace de stockage externe, appelez getExternalFilesDir().

Pour maintenir les performances de votre application, évitez d'ouvrir et fermer le même fichier plusieurs fois.

L'extrait de code suivant montre comment appeler getExternalFilesDir() :

Kotlin

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

Java

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

Créer des fichiers de cache

Pour ajouter un fichier spécifique à l'application au cache dans un espace de stockage externe, vous devez obtenir une référence à l'externalCacheDir :

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

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

Supprimer les fichiers du cache

Pour supprimer un fichier du répertoire de cache externe, utilisez la méthode delete() sur un objet File qui représente le fichier :

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Contenu multimédia

Si votre application fonctionne avec des fichiers multimédias qui ne fournissent de la valeur à l'utilisateur que dans votre application, il est préférable de les stocker dans des répertoires spécifiques à l'application sur un espace de stockage externe, comme illustré dans l'extrait de code suivant :

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;
}

Il est important d'utiliser les noms de répertoire fournis par des constantes d'API comme DIRECTORY_PICTURES. Ces noms de répertoire permettent de s'assurer que le système traite correctement les fichiers. Si aucun des noms de sous-répertoire prédéfinis ne convient à vos fichiers, vous pouvez transmettre null à getExternalFilesDir(). Cette commande renvoie le répertoire racine spécifique à l'application dans le stockage externe.

Interroger l'espace libre

Nombreux sont les utilisateurs disposant de peu d'espace de stockage disponible sur leurs appareils. Votre application doit donc utiliser de l'espace de manière réfléchie.

Si vous connaissez à l'avance la quantité de données que vous stockez, vous pouvez connaître l'espace que l'appareil peut fournir à votre application en appelant getAllocatableBytes(). La valeur renvoyée pour getAllocatableBytes() peut être supérieure à la quantité d'espace disponible actuellement sur l'appareil si le système identifie les fichiers qu'il peut supprimer des répertoires de cache des autres applications.

Si l'espace est suffisant pour enregistrer les données de votre application, appelez allocateBytes(). Sinon, votre application peut demander à l'utilisateur de supprimer certains fichiers de l'appareil ou de supprimer tous les fichiers du cache de l'appareil.

L'extrait de code suivant montre comment votre application peut interroger l'espace disponible sur l'appareil :

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

Créer une activité de gestion de l'espace de stockage

Votre application peut déclarer et créer une activité personnalisée qui, lorsqu'elle est lancée, permet à l'utilisateur de gérer les données que votre application a stockées sur son appareil. Vous déclarez cette activité personnalisée "Gérer l'espace" à l'aide de l'attribut android:manageSpaceActivity dans le fichier manifeste. Les applications de gestionnaire de fichiers peuvent appeler cette activité, même lorsque votre application n'exporte pas l'activité. C'est-à-dire, lorsque votre activité définit android:exported sur false.

Demander à l'utilisateur de supprimer certains fichiers de l'appareil

Pour demander à l'utilisateur de choisir les fichiers à supprimer sur l'appareil, appelez un intent qui inclut l'action ACTION_MANAGE_STORAGE. Cet intent affiche une invite pour l'utilisateur. Si vous le souhaitez, cette invite peut indiquer l'espace disponible sur l'appareil. Pour afficher ces informations faciles à comprendre, utilisez le résultat du calcul suivant :

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Demander à l'utilisateur de supprimer tous les fichiers de cache

Vous pouvez également demander à l'utilisateur de vider le cache de toutes les applications de l'appareil. Pour ce faire, appelez un intent qui inclut l'action ACTION_CLEAR_APP_CACHE.

Ressources supplémentaires

Pour en savoir plus sur l'enregistrement de fichiers dans l'espace de stockage de l'appareil, consultez les ressources suivantes.

Vidéos