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:
- Progettare gli URI dei contenuti
- Implementazione delle autorizzazioni del fornitore di contenuti
- Autorizzazioni
- Intent e filtri di intent