Una vez que hayas configurado tu app para compartir archivos mediante URI de contenido, podrás responder las solicitudes de otras apps solicitudes para esos archivos. Una forma de responder a estas solicitudes es proporcionar una selección de archivos interfaz de la app del servidor que otras aplicaciones pueden invocar. Este enfoque le permite al cliente app para permitir que los usuarios seleccionen un archivo de la app de servidor y, luego, reciban el archivo URI de contenido.
En esta lección, se muestra cómo crear una selección de archivos Activity
en tu app
que responde a las solicitudes de archivos.
Cómo recibir solicitudes de archivos
Para recibir solicitudes de archivos de apps cliente y responder con un URI de contenido, tu app debe
proporciona una selección de archivos Activity
. Las apps cliente inician esto.
Activity
llamando a startActivityForResult()
con un Intent
que contiene la acción
ACTION_PICK
Cuando la app cliente llama
startActivityForResult()
, tu app puede hacer lo siguiente:
mostrar un resultado a la app cliente, en forma de un URI de contenido para el archivo que seleccionó el usuario.
Para obtener información sobre cómo implementar la solicitud de un archivo en una app cliente, consulta la lección Cómo solicitar un archivo compartido
Cómo crear una actividad de selección de archivos
Para configurar la selección de archivos Activity
, comienza por especificar el
Activity
en tu manifiesto, junto con un filtro de intents
que coincida con la acción ACTION_PICK
y
categorías CATEGORY_DEFAULT
y
CATEGORY_OPENABLE
Agregar también filtros de tipo de MIME
por los archivos que tu app envía a otras apps. En el siguiente fragmento, se muestra cómo especificar la
Activity
y filtro de intents nuevos:
<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>
Cómo definir la actividad de selección de archivos en el código
A continuación, define una subclase Activity
que muestre los archivos disponibles en
el directorio files/images/
de tu app en el almacenamiento interno y permite que el usuario elija
el archivo deseado. En el siguiente fragmento, se muestra cómo definir esta
Activity
y responde a la selección del usuario:
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 */ ... } ... }
Cómo responder a una selección de archivo
Una vez que un usuario selecciona un archivo compartido, tu aplicación debe determinar qué archivo se seleccionó y
luego, genera un URI de contenido para el archivo. Dado que Activity
muestra el
Lista de archivos disponibles en un ListView
, cuando el usuario hace clic en un nombre de archivo
el sistema llama al método onItemClick()
, en el que puedes obtener el archivo seleccionado.
Cuando usas un intent para enviar el URI de un archivo de una app a otra,
debes tener cuidado para obtener un URI que otros
pueden leer las apps. Hacerlo en dispositivos con Android 6.0 (nivel de API 23) y versiones posteriores
requiere atención
cuidado debido a los cambios en el modelo de permisos de esa versión de Android, en particular,
De READ_EXTERNAL_STORAGE
convertirse en
permiso peligroso, que la app receptora podría no tener.
Con estas consideraciones en mente, recomendamos que evites usar
Uri.fromFile()
, que
presenta varias desventajas. El método tiene las siguientes características:
- No permite compartir archivos entre perfiles.
- Requiere que tu app tenga
WRITE_EXTERNAL_STORAGE
en dispositivos con Android 4.4 (nivel de API 19) o versiones anteriores. - Requiere que las apps receptoras tengan las
READ_EXTERNAL_STORAGE
, que fallarán en objetivos de uso compartido importantes, como Gmail, que no tengan ese permiso.
En lugar de usar Uri.fromFile()
,
puedes usar permisos del URI para otorgar a otras apps
acceso a URI específicos. Aunque los permisos de URI no funcionan en URI de file://
generada por Uri.fromFile()
, sí
funcionan en URI asociados con proveedores de contenido. El
La API de FileProvider
puede
ayudarte a crear esos URIs. Este enfoque también funciona con archivos que no son
pero en el local de la app que envía el intent.
En onItemClick()
, obtén un
File
para el nombre del archivo seleccionado y pásalo como argumento a
getUriForFile()
, junto con el
autoridad que especificaste en el
<provider>
para el FileProvider
.
El URI de contenido resultante contiene la autoridad, un segmento de ruta que corresponde al
directorio (como se especifica en los metadatos XML) y el nombre del archivo, incluida su
. Cómo FileProvider
asigna directorios a la ruta de acceso
basados en metadatos XML, se describe en la sección
Especifica directorios para compartir.
En el siguiente fragmento, se muestra cómo detectar el archivo seleccionado y obtener un URI de contenido para él:
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()); } ... } }); ... }
Recuerda que solo puedes generar URI de contenido para archivos que se encuentren en un directorio
que especificaste en el archivo de metadatos que contiene el elemento <paths>
,
se describe en la sección Cómo especificar directorios para compartir. Si llamas
getUriForFile()
para un
File
en una ruta de acceso que no especificaste, recibirás un
IllegalArgumentException
Cómo otorgar permisos para el archivo
Ahora que tienes un URI de contenido para el archivo que quieres compartir con otra app, debes hacer lo siguiente:
permitir que la app cliente acceda al archivo. Para permitir el acceso, otorga permisos a la app cliente. Para ello, haz lo siguiente:
agregar el URI de contenido a un Intent
y, luego, establecer marcas de permiso en
Intent
Los permisos que otorgas son temporales y vencen
automáticamente cuando finalice la pila de tareas de la app receptora.
En el siguiente fragmento de código, se muestra cómo configurar el permiso de lectura para el archivo:
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); } ... } ... }); ... }
Precaución: Llamar a setFlags()
es la única
más sencilla de otorgar acceso a tus archivos de forma segura mediante permisos de acceso temporal. Evitar llamadas
Método Context.grantUriPermission()
para un
el URI de contenido del archivo, ya que este método otorga acceso que solo puedes revocar
llamando a Context.revokeUriPermission()
.
No uses Uri.fromFile()
. Fuerza la recepción de apps
para tener el permiso READ_EXTERNAL_STORAGE
no funcionará en absoluto si intentas compartir contenido entre usuarios y en
de las versiones de Android anteriores a la 4.4 (nivel de API 19), requeriría
para que la app tenga WRITE_EXTERNAL_STORAGE
.
Y los objetivos de uso compartido muy importantes, como la app de Gmail, no tienen
el READ_EXTERNAL_STORAGE
, lo que provoca
esta llamada falle.
En su lugar, puedes usar permisos de URI para otorgar a otras apps acceso a URI específicos.
Si bien los permisos de URI no funcionan en los URI file:// como los genera
Uri.fromFile()
, sí
funcionan en URI asociados con proveedores de contenido. En lugar de implementar la tuya solo para esto,
puedes y debes usar FileProvider
como se explica en Uso compartido de archivos.
Cómo compartir el archivo con la app que lo solicita
Para compartir el archivo con la app que lo solicitó, pasa el Intent
que incluye el URI de contenido y los permisos para setResult()
. Cuando finaliza el Activity
que acabas de definir, el
el sistema envía el Intent
que tiene el URI de contenido a la app cliente.
En el siguiente fragmento de código, se muestra cómo hacerlo:
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); } } });
Proporciona a los usuarios una forma de regresar de inmediato a la app cliente una vez que hayan elegido un archivo.
Una forma de hacerlo es proporcionar una marca de verificación o un botón Done. Asocia un método con
mediante el botón
atributo android:onClick
. En el método, llama
finish()
Por ejemplo:
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(); }
Para obtener información adicional relacionada, consulta:
- Diseña URI de contenido
- Cómo implementar permisos del proveedor de contenido
- Permisos
- Intents y filtros de intents