Condivisione di un file

Dopo aver configurato l'app per la condivisione di file utilizzando gli URI dei contenuti, puoi rispondere a messaggi di altre app richieste per questi file. Un modo per rispondere a queste richieste è selezionare i file a riga di comando dell'app server che altre applicazioni possono richiamare. Questo approccio consente a un cliente per consentire agli utenti di selezionare un file dall'app server e ricevere poi i l'URI contenuto.

Questa lezione mostra come creare una selezione di file Activity nella tua app che risponde alle richieste dei file.

Ricevi richieste di file

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

Per scoprire come implementare una richiesta per un file in un'app client, consulta la lezione Richiedi un file condiviso.

Crea un'attività di selezione file

Per configurare la selezione di file Activity, inizia specificando la Activity nel manifest, insieme a un filtro per intent che corrisponde all'azione ACTION_PICK e categorie CATEGORY_DEFAULT e CATEGORY_OPENABLE. Aggiungi anche filtri di tipo MIME per i file che la tua app pubblica su altre app. Il seguente snippet mostra come specificare nuovo Activity e filtro 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 dei file nel codice

Poi definisci una sottoclasse Activity che mostri i file disponibili alla directory files/images/ dell'app nella memoria interna e consente all'utente di selezionare del file desiderato. Il seguente snippet mostra come definirlo Activity e rispondi 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

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

Quando utilizzi un intent per inviare l'URI di un file da un'app all'altra, devi fare attenzione a ottenere un URI che possono leggere. Questa operazione viene eseguita sui dispositivi con Android 6.0 (livello API 23) e versioni successive. richiede un'offerta speciale per le modifiche al modello di autorizzazioni in quella versione di Android, in particolare di READ_EXTERNAL_STORAGE diventare un autorizzazione pericolosa, che potrebbe mancare all'app ricevente.

Tenendo presenti queste considerazioni, ti consigliamo di evitare di utilizzare Uri.fromFile(), che presenta diversi svantaggi. Questo metodo:

  • Non consente la condivisione di file tra profili.
  • È necessario che l'app abbia WRITE_EXTERNAL_STORAGE sui dispositivi con Android 4.4 (livello API 19) o versioni precedenti.
  • È necessario che le app che ricevono i seguenti READ_EXTERNAL_STORAGE, che avrà esito negativo su destinazioni di condivisione importanti, come Gmail, che non hanno questa autorizzazione.

Anziché utilizzare Uri.fromFile(), puoi utilizzare le autorizzazioni URI per concedere altre app l'accesso a URI specifici. Sebbene le autorizzazioni URI non funzionino sugli URI file:// generate da Uri.fromFile(), per gli URI associati ai fornitori di contenuti. La L'API FileProvider può nella creazione di questi URI. Questo approccio funziona anche con i file che non sono nella memoria esterna, ma nella memoria locale dell'app che invia l'intent.

In onItemClick(), ricevi un File oggetto per il nome del file selezionato e passalo come argomento a getUriForFile(), insieme ai dell'autorità che hai specificato Elemento <provider> per FileProvider. L'URI dei contenuti risultante contiene l'autorità, un segmento di percorso corrispondente (come specificato nei metadati XML) e il nome del file, compreso il relativo . In che modo FileProvider mappa le directory al percorso i segmenti basati su metadati XML sono descritti nella sezione Specifica le directory condivisibili.

Il seguente snippet mostra come rilevare il file selezionato e ottenere un 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 risiedono in una directory specificato nel file di metadati che contiene l'elemento <paths>, 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 Intent. Le autorizzazioni concesse sono temporanee e scadono automaticamente al termine dell'elenco 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 a setFlags() è l'unica soluzione per concedere in modo sicuro l'accesso ai tuoi file usando le autorizzazioni di accesso temporanee. Evita di chiamare Metodo Context.grantUriPermission() per un l'URI dei contenuti del file, poiché questo metodo concede l'accesso che puoi revocare solo chiamata al numero Context.revokeUriPermission().

Non usare Uri.fromFile(). Obbliga la ricezione di app per avere l'autorizzazione READ_EXTERNAL_STORAGE, non funzionano se la condivisione con più utenti e nelle versioni di Android precedente alla 4.4 (livello API 19), sarebbero necessarie la tua per avere WRITE_EXTERNAL_STORAGE. E i target di condivisione molto importanti, come l'app Gmail, non hanno READ_EXTERNAL_STORAGE, causando la chiamata non andrà a buon fine. Puoi invece utilizzare le autorizzazioni URI per concedere ad altre app l'accesso a URI specifici. Sebbene le autorizzazioni URI non funzionino sugli URI file:// generati Uri.fromFile(), sì lavorare su URI associati ai Fornitori di contenuti. Anziché implementare il tuo solo per questo, puoi e dovresti usare FileProvider come spiegato in Condivisione di file.

Condividi il file con l'app che ha inviato la richiesta

Per condividere il file con l'app che lo ha richiesto, trasmetti il Intent contenente l'URI dei contenuti e le autorizzazioni per setResult(). Una volta completata la definizione del campo Activity che hai appena definito, invia l'elemento Intent contenente l'URI dei contenuti all'app client. 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);
                    }
                }
        });

Fornisci agli utenti un modo per tornare immediatamente all'app client dopo aver scelto un file. Un modo per farlo è inserire un segno di spunta o il pulsante Fine. Associa un metodo a utilizzando il metodo Attributo android:onClick. Nel metodo, richiama finish(). Ad esempio:

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: