Accedere a file specifici per le app

In molti casi, l'app crea file a cui altre app non devono o non dovrebbero accedere. Il sistema fornisce le seguenti posizioni per l'archiviazione di questi file specifici dell'app:

  • Directory di archiviazione interna:queste directory includono sia una posizione dedicata per l'archiviazione di file persistenti sia un'altra posizione per l'archiviazione dei dati della cache. Il sistema impedisce ad altre app di accedere a queste posizioni e, su Android 10 (livello API 29) e versioni successive, queste posizioni sono criptate. Queste caratteristiche rendono queste posizioni un buon posto per archiviare dati sensibili a cui solo la tua app può accedere.

  • Directory di archiviazione esterna:queste directory includono sia una posizione dedicata per l'archiviazione di file persistenti sia un'altra posizione per l'archiviazione dei dati memorizzati nella cache. Sebbene sia possibile per un'altra app accedere a queste directory se dispone delle autorizzazioni appropriate, i file archiviati in queste directory sono destinati all'uso esclusivo da parte della tua app. Se intendi creare file a cui altre app devono poter accedere, la tua app deve archiviare questi file nella parte di spazio di archiviazione condiviso dello spazio di archiviazione esterno.

Quando l'utente disinstalla l'app, i file salvati nello spazio di archiviazione specifico dell'app vengono rimossi. A causa di questo comportamento, non devi utilizzare questo spazio di archiviazione per salvare qualsiasi elemento che l'utente si aspetta che persista indipendentemente dalla tua app. Ad esempio, se la tua app consente agli utenti di scattare foto, l'utente si aspetta di poter accedere a queste foto anche dopo aver disinstallato l'app. Pertanto, devi utilizzare lo spazio di archiviazione condiviso per salvare questi tipi di file nella raccolta multimediale appropriata.

Le sezioni seguenti descrivono come archiviare e accedere ai file all'interno di directory specifiche dell'app.

Accesso dallo spazio di archiviazione interno

Per ogni app, il sistema fornisce directory all'interno dello spazio di archiviazione interno in cui un'app può organizzare i propri file. Una directory è progettata per i file persistenti della tua app e un'altra contiene i file memorizzati nella cache della tua app. La tua app non richiede autorizzazioni di sistema per leggere e scrivere file in queste directory.

Altre app non possono accedere ai file archiviati nella memoria interna. Questo rende lo spazio di archiviazione interno un buon posto per i dati delle app a cui altre app non dovrebbero accedere.

Tieni presente, tuttavia, che queste directory tendono a essere di piccole dimensioni. Prima di scrivere file specifici dell'app nello spazio di archiviazione interno, l'app deve eseguire una query sullo spazio libero sul dispositivo.

Accedi ai file persistenti

I file ordinari e permanenti della tua app si trovano in una directory a cui puoi accedere utilizzando la proprietà filesDir di un oggetto di contesto. Il framework fornisce diversi metodi per aiutarti ad accedere ai file e archiviarli in questa directory.

Accedere ai file e archiviarli

Puoi utilizzare l'API File per accedere ai file e archiviarli.

Per mantenere il rendimento dell'app, non aprire e chiudere lo stesso file più volte.

Il seguente snippet di codice mostra come utilizzare l'API File:

Kotlin

val file = File(context.filesDir, filename)

Java

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

Archivia un file utilizzando uno stream

In alternativa all'utilizzo dell'API File, puoi chiamare openFileOutput() per ottenere un FileOutputStream che scrive in un file all'interno della directory filesDir.

Il seguente snippet di codice mostra come scrivere del testo in un file:

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

Per consentire ad altre app di accedere ai file archiviati in questa directory all'interno della memoria interna, utilizza un FileProvider con l'attributo FLAG_GRANT_READ_URI_PERMISSION.

Accedere a un file utilizzando uno stream

Per leggere un file come stream, utilizza 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();
}

Visualizza l'elenco dei file

Puoi ottenere un array contenente i nomi di tutti i file all'interno della directory filesDir chiamando fileList(), come mostrato nello snippet di codice seguente:

Kotlin

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

Java

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

Crea directory nidificate

Puoi anche creare directory nidificate o aprire una directory interna chiamando getDir() nel codice basato su Kotlin o passando la directory root e un nuovo nome di directory in un costruttore File nel codice basato su Java:

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

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

Crea file di cache

Se devi archiviare dati sensibili solo temporaneamente, devi utilizzare la directory della cache designata dell'app all'interno dello spazio di archiviazione interno per salvare i dati. Come per tutti gli spazi di archiviazione specifici per le app, i file archiviati in questa directory vengono rimossi quando l'utente disinstalla l'app, anche se i file in questa directory potrebbero essere rimossi prima.

Per creare un file memorizzato nella cache, chiama File.createTempFile():

Kotlin

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

Java

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

La tua app accede a un file in questa directory utilizzando la proprietà cacheDir di un oggetto contesto e l'API File:

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

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

Rimuovi i file della cache

Anche se a volte Android elimina i file della cache autonomamente, non dovresti fare affidamento sul sistema per la pulizia di questi file. Devi sempre conservare i file di cache dell'app nella memoria interna.

Per rimuovere un file dalla directory della cache nello spazio di archiviazione interno, utilizza uno dei seguenti metodi:

  • Il metodo delete() su un oggetto File che rappresenta il file:

    Kotlin

    cacheFile.delete()

    Java

    cacheFile.delete();
  • Il metodo deleteFile() del contesto dell'app, passando il nome del file:

    Kotlin

    context.deleteFile(cacheFileName)

    Java

    context.deleteFile(cacheFileName);

Accesso da unità di archiviazione esterna

Se lo spazio di archiviazione interno non è sufficiente per archiviare i file specifici dell'app, valuta la possibilità di utilizzare lo spazio di archiviazione esterno. Il sistema fornisce directory all'interno dello spazio di archiviazione esterno in cui un'app può organizzare i file che forniscono valore all'utente solo all'interno della tua app. Una directory è progettata per i file persistenti della tua app e un'altra contiene i file memorizzati nella cache della tua app.

Su Android 4.4 (livello API 19) o versioni successive, l'app non deve richiedere autorizzazioni relative allo spazio di archiviazione per accedere alle directory specifiche dell'app all'interno dello spazio di archiviazione esterno. I file archiviati in queste directory vengono rimossi quando l'app viene disinstallata.

Sui dispositivi con Android 9 (livello API 28) o versioni precedenti, la tua app può accedere ai file specifici di altre app, a condizione che disponga delle autorizzazioni di archiviazione appropriate. Per offrire agli utenti un maggiore controllo sui propri file e limitare il disordine dei file, alle app che hanno come target Android 10 (livello API 29) e versioni successive viene concesso l'accesso mirato allo spazio di archiviazione esterno o all'archiviazione mirata per impostazione predefinita. Quando l'archiviazione mirata è attiva, le app non possono accedere alle directory specifiche di altre app.

Verifica che lo spazio di archiviazione sia disponibile

Poiché lo spazio di archiviazione esterno si trova su un volume fisico che l'utente potrebbe essere in grado di rimuovere, verifica che il volume sia accessibile prima di provare a leggere o scrivere dati specifici dell'app nello spazio di archiviazione esterno.

Puoi eseguire una query sullo stato del volume chiamando Environment.getExternalStorageState(). Se lo stato restituito è MEDIA_MOUNTED, puoi leggere e scrivere file specifici dell'app nello spazio di archiviazione esterno. Se è MEDIA_MOUNTED_READ_ONLY, puoi solo leggere questi file.

Ad esempio, i seguenti metodi sono utili per determinare la disponibilità dello spazio di archiviazione:

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

Sui dispositivi senza spazio di archiviazione esterno rimovibile, utilizza il seguente comando per attivare un volume virtuale per testare la logica di disponibilità dello spazio di archiviazione esterno:

adb shell sm set-virtual-disk true

Seleziona una località di archiviazione fisica

A volte, un dispositivo che alloca una partizione della memoria interna come spazio di archiviazione esterno fornisce anche uno slot per schede SD. Ciò significa che il dispositivo ha più volumi fisici che potrebbero contenere spazio di archiviazione esterno, quindi devi selezionare quello da utilizzare per lo spazio di archiviazione specifico dell'app.

Per accedere alle diverse località, chiama ContextCompat.getExternalFilesDirs(). Come mostrato nello snippet di codice, il primo elemento nell'array restituito è considerato il volume di spazio di archiviazione esterno principale. Utilizza questo volume a meno che non sia pieno o non disponibile.

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

Accedi ai file persistenti

Per accedere ai file specifici dell'app dallo spazio di archiviazione esterno, chiama getExternalFilesDir().

Per mantenere il rendimento dell'app, non aprire e chiudere lo stesso file più volte.

Il seguente snippet di codice mostra come chiamare getExternalFilesDir():

Kotlin

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

Java

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

Crea file di cache

Per aggiungere un file specifico dell'app alla cache nello spazio di archiviazione esterno, ottieni un riferimento a externalCacheDir:

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

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

Rimuovi i file della cache

Per rimuovere un file dalla directory della cache esterna, utilizza il metodo delete() su un oggetto File che rappresenta il file:

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

Contenuti multimediali

Se la tua app funziona con file multimediali che forniscono valore all'utente solo all'interno della tua app, è meglio archiviarli in directory specifiche per l'app all'interno dello spazio di archiviazione esterno, come mostrato nel seguente snippet di codice:

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

È importante utilizzare i nomi delle directory forniti dalle costanti dell'API come DIRECTORY_PICTURES. Questi nomi di directory assicurano che i file vengano trattati correttamente dal sistema. Se nessuno dei nomi delle sottodirectory predefinite è adatto ai tuoi file, puoi passare null a getExternalFilesDir(). In questo modo viene restituita la directory principale specifica dell'app all'interno dello spazio di archiviazione esterno.

Esegui query sullo spazio libero

Molti utenti non hanno molto spazio di archiviazione disponibile sui propri dispositivi, quindi l'app dovrebbe consumare spazio in modo ponderato.

Se sai in anticipo quanti dati stai archiviando, puoi scoprire quanto spazio il dispositivo può fornire alla tua app chiamando getAllocatableBytes(). Il valore restituito di getAllocatableBytes() potrebbe essere maggiore della quantità attuale di spazio libero sul dispositivo. Questo perché il sistema ha identificato file che può rimuovere dalle directory della cache di altre app.

Se lo spazio è sufficiente per salvare i dati dell'app, chiama allocateBytes(). In caso contrario, l'app può chiedere all'utente di rimuovere alcuni file dal dispositivo o di rimuovere tutti i file della cache dal dispositivo.

Il seguente snippet di codice mostra un esempio di come la tua app può eseguire query sullo spazio libero sul dispositivo:

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

Crea un'attività di gestione dello spazio di archiviazione

La tua app può dichiarare e creare un'attività personalizzata che, una volta avviata, consente all'utente di gestire i dati memorizzati dalla tua app sul suo dispositivo. Dichiari questa attività personalizzata "Gestisci spazio" utilizzando l'attributo android:manageSpaceActivity nel file manifest. Le app di gestione file possono richiamare questa attività anche se la tua app non la esporta, ovvero quando l'attività imposta android:exported su false.

Chiedi all'utente di rimuovere alcuni file del dispositivo

Per chiedere all'utente di scegliere i file da rimuovere sul dispositivo, richiama un intent che includa l'azione ACTION_MANAGE_STORAGE. Questo intent mostra un prompt all'utente. Se vuoi, questo messaggio può mostrare la quantità di spazio libero disponibile sul dispositivo. Per mostrare queste informazioni di facile utilizzo, utilizza il risultato del seguente calcolo:

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

Chiedi all'utente di rimuovere tutti i file della cache

In alternativa, puoi chiedere all'utente di svuotare la cache di tutte le app sul dispositivo. Per farlo, richiama un intent che includa l'azione di intent ACTION_CLEAR_APP_CACHE.

Risorse aggiuntive

Per saperne di più sul salvataggio dei file nello spazio di archiviazione del dispositivo, consulta le seguenti risorse.

Video