Condivisione di un file

Dopo aver configurato l'app in modo da condividere i file utilizzando gli URI dei contenuti, puoi rispondere alle richieste di questi file di altre app. Un modo per rispondere a queste richieste è fornire un'interfaccia di selezione file dall'app server che altre applicazioni possono richiamare. Questo approccio consente a un'applicazione client di consentire agli utenti di selezionare un file dall'app server e poi di ricevere l'URI dei contenuti del file selezionato.

Questa lezione spiega come creare una selezione di file Activity nella tua app che risponda alle richieste di file.

Ricevi richieste di file

Per ricevere richieste di file dalle app client e rispondere con un URI di contenuti, la tua app deve fornire una selezione di file Activity. Le app client avviano questo Activity chiamando startActivityForResult() con un Intent contenente l'azione ACTION_PICK. Quando l'app client chiama startActivityForResult(), l'app può restituire all'app client un risultato sotto forma di URI di contenuti per il file selezionato dall'utente.

Per informazioni su come implementare una richiesta per un file in un'app client, consulta la lezione Richiesta di un file condiviso.

Creare un'attività di selezione file

Per configurare la selezione di file Activity, inizia specificando Activity nel file manifest, insieme a un filtro per intent che corrisponda all'azione ACTION_PICK e alle categorie CATEGORY_DEFAULT e CATEGORY_OPENABLE. Aggiungi anche filtri di tipo MIME per i file che la tua app pubblica in altre app. Il seguente snippet mostra come specificare il nuovo filtro Activity e per intent:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
        <application>
        ...
            <activity
                android:name=".FileSelectActivity"
                android:label="@File Selector" >
                <intent-filter>
                    <action
                        android:name="android.intent.action.PICK"/>
                    <category
                        android:name="android.intent.category.DEFAULT"/>
                    <category
                        android:name="android.intent.category.OPENABLE"/>
                    <data android:mimeType="text/plain"/>
                    <data android:mimeType="image/*"/>
                </intent-filter>
            </activity>

Definisci l'attività di selezione file nel codice

In seguito, definisci una sottoclasse Activity che mostri i file disponibili nella directory files/images/ dell'app nella memoria interna e che consenta all'utente di scegliere il file desiderato. Lo snippet seguente mostra come definire questo Activity e rispondere alla selezione dell'utente:

Kotlin

class MainActivity : Activity() {

    // The path to the root of this app's internal storage
    private lateinit var privateRootDir: File
    // The path to the "images" subdirectory
    private lateinit var imagesDir: File
    // Array of files in the images subdirectory
    private lateinit var imageFiles: Array<File>
    // Array of filenames corresponding to imageFiles
    private lateinit var imageFilenames: Array<String>

    // Initialize the Activity
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent = Intent("com.example.myapp.ACTION_RETURN_FILE")
        // Get the files/ subdirectory of internal storage
        privateRootDir = filesDir
        // Get the files/images subdirectory;
        imagesDir = File(privateRootDir, "images")
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles()
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null)
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
        ...
    }
    ...
}

Java

public class MainActivity extends Activity {
    // The path to the root of this app's internal storage
    private File privateRootDir;
    // The path to the "images" subdirectory
    private File imagesDir;
    // Array of files in the images subdirectory
    File[] imageFiles;
    // Array of filenames corresponding to imageFiles
    String[] imageFilenames;
    // Initialize the Activity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent =
                new Intent("com.example.myapp.ACTION_RETURN_FILE");
        // Get the files/ subdirectory of internal storage
        privateRootDir = getFilesDir();
        // Get the files/images subdirectory;
        imagesDir = new File(privateRootDir, "images");
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles();
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null);
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
         ...
    }
    ...
}

Rispondere a una selezione di file

Dopo che un utente ha selezionato un file condiviso, l'applicazione deve determinare quale file è stato selezionato e generare un URI dei contenuti per il file. Poiché Activity mostra l'elenco dei file disponibili in un ListView, quando l'utente fa clic sul nome di un file, il sistema chiama il metodo onItemClick(), che consente di ottenere il file selezionato.

Quando utilizzi un intent per inviare l'URI di un file da un'app a un'altra, devi prestare attenzione a ottenere un URI che le altre app possano leggere. Questa operazione sui dispositivi con Android 6.0 (livello API 23) e versioni successive richiede particolare attenzione a causa delle modifiche al modello di autorizzazioni in quella versione di Android, in particolare READ_EXTERNAL_STORAGE diventa un' autorizzazione pericolosa, che potrebbe non essere disponibile nell'app ricevente.

Alla luce di queste considerazioni, consigliamo di evitare l'utilizzo di Uri.fromFile(), il che presenta diversi svantaggi. Questo metodo:

  • Non consente la condivisione di file tra profili.
  • È necessario che la tua app disponga dell'autorizzazione WRITE_EXTERNAL_STORAGE sui dispositivi con Android 4.4 (livello API 19) o versioni precedenti.
  • Richiede che le app di ricezione dispongano dell'autorizzazione READ_EXTERNAL_STORAGE, che non andrà a buon fine sulle destinazioni importanti delle condivisioni, come Gmail, che non hanno questa autorizzazione.

Anziché utilizzare Uri.fromFile(), puoi utilizzare le autorizzazioni URI per concedere ad altre app l'accesso a URI specifici. Anche se le autorizzazioni URI non funzionano per gli URI file:// generati da Uri.fromFile(), funzionano per gli URI associati ai fornitori di contenuti. L'API FileProvider può aiutarti a creare questi URI. Questo approccio funziona anche con i file che non si trovano nell'unità di archiviazione esterna, ma nello spazio di archiviazione locale dell'app che invia l'intent.

In onItemClick(), recupera un oggetto File per il nome del file selezionato e passalo come argomento a getUriForFile(), insieme all'autorità specificata nell'elemento <provider> per FileProvider. L'URI del contenuto risultante contiene l'autorità, un segmento di percorso corrispondente alla directory del file (come specificato nei metadati XML) e il nome del file, inclusa la sua estensione. Il modo in cui FileProvider mappa le directory ai segmenti del percorso in base ai metadati XML viene descritto nella sezione Specificare le directory condivisibili.

Il seguente snippet mostra come rilevare il file selezionato e ottenere l'URI dei contenuti per il file:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */
            val requestFile = File(imageFilenames[position])
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            val fileUri: Uri? = try {
                FileProvider.getUriForFile(
                        this@MainActivity,
                        "com.example.myapp.fileprovider",
                        requestFile)
            } catch (e: IllegalArgumentException) {
                Log.e("File Selector",
                        "The selected file can't be shared: $requestFile")
                null
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            /*
             * When a filename in the ListView is clicked, get its
             * content URI and send it to the requesting app
             */
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                /*
                 * Get a File for the selected file name.
                 * Assume that the file names are in the
                 * imageFilename array.
                 */
                File requestFile = new File(imageFilename[position]);
                /*
                 * Most file-related method calls need to be in
                 * try-catch blocks.
                 */
                // Use the FileProvider to get a content URI
                try {
                    fileUri = FileProvider.getUriForFile(
                            MainActivity.this,
                            "com.example.myapp.fileprovider",
                            requestFile);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                          "The selected file can't be shared: " + requestFile.toString());
                }
                ...
            }
        });
        ...
    }

Ricorda che puoi generare URI dei contenuti solo per i file che si trovano in una directory da te specificata nel file di metadati che contiene l'elemento <paths>, come descritto nella sezione Specificare le directory condivisibili. Se chiami getUriForFile() per un File in un percorso che non hai specificato, riceverai un IllegalArgumentException.

Concedi le autorizzazioni per il file

Ora che disponi di un URI dei contenuti per il file che vuoi condividere con un'altra app, devi consentire all'app client di accedere al file. Per consentire l'accesso, concedi le autorizzazioni all'app client aggiungendo l'URI dei contenuti a un Intent e impostando i flag di autorizzazione su Intent. Le autorizzazioni che concedi sono temporanee e scadono automaticamente al termine dello stack di attività dell'app ricevente.

Il seguente snippet di codice mostra come impostare l'autorizzazione di lettura per il file:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                // Grant temporary read permission to the content URI
                resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ...
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    // Grant temporary read permission to the content URI
                    resultIntent.addFlags(
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
                ...
             }
             ...
        });
    ...
    }

Attenzione:la chiamata al numero setFlags() è l'unico modo per concedere in modo sicuro l'accesso ai tuoi file usando autorizzazioni di accesso temporaneo. Evita di chiamare il metodo Context.grantUriPermission() per l'URI dei contenuti di un file, poiché questo metodo concede un accesso che puoi revocare solo chiamando Context.revokeUriPermission().

Non usare Uri.fromFile(). Impone alla ricezione delle app di disporre dell'autorizzazione READ_EXTERNAL_STORAGE. Non funziona se stai tentando di condividere tra utenti e, nelle versioni di Android precedenti alla 4.4 (livello API 19), richiede che l'app abbia WRITE_EXTERNAL_STORAGE. Inoltre, i target di condivisione molto importanti, come l'app Gmail, non includono READ_EXTERNAL_STORAGE e, di conseguenza, la chiamata non riesce. Puoi utilizzare le autorizzazioni URI per concedere ad altre app l'accesso a URI specifici. Sebbene le autorizzazioni URI non funzionino per gli URI file:// come vengono generate da Uri.fromFile(), funzionano sugli URI associati ai fornitori di contenuti. Anziché implementare una tua soluzione solo a questo scopo, puoi e devi usare FileProvider come spiegato nella sezione Condivisione di file.

Condividi il file con l'app richiedente

Per condividere il file con l'app che lo ha richiesto, passa il Intent contenente l'URI dei contenuti e le autorizzazioni a setResult(). Una volta completata l'istruzione Activity appena definita, il sistema invia all'app client l'elemento Intent contenente l'URI dei contenuti. Il seguente snippet di codice mostra come eseguire questa operazione:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                ...
                // Put the Uri and MIME type in the result Intent
                resultIntent.setDataAndType(fileUri, contentResolver.getType(fileUri))
                // Set the result
                setResult(Activity.RESULT_OK, resultIntent)
            } else {
                resultIntent.setDataAndType(null, "")
                setResult(RESULT_CANCELED, resultIntent)
            }
        }
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    ...
                    // Put the Uri and MIME type in the result Intent
                    resultIntent.setDataAndType(
                            fileUri,
                            getContentResolver().getType(fileUri));
                    // Set the result
                    MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent);
                    } else {
                        resultIntent.setDataAndType(null, "");
                        MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent);
                    }
                }
        });

Offri agli utenti un modo per tornare immediatamente all'app client dopo che hanno scelto un file. Un modo per farlo è aggiungere un segno di spunta o un pulsante Fine. Associa un metodo al pulsante utilizzando l'attributo android:onClick del pulsante. Nel metodo, chiama finish(). Ecco alcuni esempi:

Kotlin

    fun onDoneClick(v: View) {
        // Associate a method with the Done button
        finish()
    }

Java

    public void onDoneClick(View v) {
        // Associate a method with the Done button
        finish();
    }

Per ulteriori informazioni correlate, consulta: