Depois de configurar o app para compartilhar arquivos usando URIs de conteúdo, você pode responder a solicitações de outros apps para esses arquivos. Uma maneira de responder a essas solicitações é fornecer uma interface de seleção de arquivos do app no servidor que outros apps possam invocar. Com essa abordagem, um app cliente permite que os usuários selecionem um arquivo a partir do app do servidor e recebam o URI de conteúdo do arquivo selecionado.
Esta lição mostra como criar uma Activity
de seleção de arquivos no seu app que responda a solicitações de arquivos.
Receber solicitações de arquivos
Para receber solicitações de arquivos a partir de apps cliente e responder com um URI de conteúdo, seu app precisa fornecer uma Activity
de seleção de arquivos. Apps cliente iniciam esta Activity
chamando startActivityForResult()
com uma Intent
contendo a ACTION_PICK
da ação. Quando o app cliente chama startActivityForResult()
, seu app pode retornar um resultado ao app cliente, na forma de um URI de conteúdo para o arquivo que o usuário selecionou.
Para saber como implementar uma solicitação de arquivo em um app cliente, consulte a lição Como solicitar um arquivo compartilhado.
Criar uma atividade de seleção de arquivos
Para configurar a Activity
de seleção de arquivos, comece especificando a Activity
no manifesto, com um filtro de intent que corresponda à ACTION_PICK
da ação e às categorias CATEGORY_DEFAULT
e CATEGORY_OPENABLE
. Além disso, adicione filtros do tipo MIME para os arquivos que o app exibe para outros apps. O snippet a seguir mostra como especificar a nova Activity
e filtro de 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>
Definir a atividade de seleção de arquivos no código
Em seguida, defina uma subclasse da Activity
que exiba os arquivos disponíveis a partir do diretório files/images/
do app no armazenamento interno e permita que o usuário escolha o arquivo desejado. O snippet a seguir demonstra como definir essa Activity
e responder à seleção do usuário.
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 */ ... } ... }
Responder a uma seleção de arquivos
Depois que um usuário seleciona um arquivo compartilhado, o app precisa determinar qual arquivo foi selecionado e, em seguida, gerar um URI de conteúdo para ele. Como a Activity
exibe a lista de arquivos disponíveis em uma ListView
, quando o usuário clica em um nome de arquivo, o sistema chama o método onItemClick()
, em que você pode receber o arquivo selecionado.
Ao usar uma intent para enviar o URI de um arquivo de um app para outro, tenha o cuidado de ter um URI que outros apps possam ler. Fazer isso em dispositivos que executam o Android 6.0 (API de nível 23) e versões posteriores requer cuidados especiais por causa de mudanças no modelo de permissões naquela versão do Android, particularmente READ_EXTERNAL_STORAGE
, que está se tornando uma
permissão perigosa, que o app receptor pode não ter.
Com essas considerações em mente, recomendamos que você evite usar Uri.fromFile()
, que apresenta várias desvantagens. Esse método:
- não permite o compartilhamento de arquivos entre perfis;
- exige que o app tenha permissão para
WRITE_EXTERNAL_STORAGE
em dispositivos com Android 4.4 (API de nível 19) ou versões anteriores; - requer que apps receptores tenham a permissão
READ_EXTERNAL_STORAGE
, que falhará em segmentos importantes de compartilhamento, como o Gmail, que não têm essa permissão.
Em vez de usar Uri.fromFile()
, você pode usar permissões de URI para conceder a outros apps acesso a URIs específicos. Embora as permissões de URI não funcionem em file://
como gerado por Uri.fromFile()
, elas funcionam em URIs associados a provedores de conteúdo. A API FileProvider
pode ajudar você a criar esses URIs. Essa abordagem também funciona com arquivos que não estão no armazenamento externo, mas no armazenamento local do app que envia a intent.
Em onItemClick()
, receba um objeto File
para o nome do arquivo selecionado e transmita-o como um argumento para getUriForFile()
, com a autoridade que você especificou no elemento <provider>
para o FileProvider
.
O URI de conteúdo resultante contém a autoridade, um segmento de caminho correspondente ao diretório do arquivo (conforme especificado nos metadados XML) e o nome do arquivo que inclui a extensão. A forma como FileProvider
mapeia diretórios para segmentos de caminho com base em metadados XML é descrita na seção Especificar diretórios compartilháveis.
O snippet a seguir mostra como detectar o arquivo selecionado e ter um URI de conteúdo para ele:
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()); } ... } }); ... }
Lembre-se de que você só pode gerar URIs de conteúdo para arquivos que residem em um diretório que você especificou no arquivo de metadados que contém o elemento <paths>
conforme descrito na seção Especificar diretórios compartilháveis. Se você chamar getUriForFile()
para um File
em um caminho que você não tenha especificado, você receberá uma IllegalArgumentException
.
Conceder permissões para o arquivo
Agora que você tem um URI de conteúdo para o arquivo que quer compartilhar com outro app, é necessário permitir que o app cliente acesse o arquivo. Para permitir o acesso, conceda permissões para o app cliente, adicionando o URI de conteúdo a uma Intent
e, em seguida, definindo sinalizações de permissão na Intent
. As permissões concedidas são temporárias e expiram automaticamente quando a pilha de tarefas do app receptor é concluída.
O snippet de código a seguir mostra como configurar a permissão de leitura para o arquivo.
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); } ... } ... }); ... }
Cuidado: chamar setFlags()
é a única maneira de fornecer acesso seguro aos seus arquivos usando permissões de acesso temporárias. Evite chamar o método Context.grantUriPermission()
para o conteúdo de um arquivo URI, porque esse método concede acesso que você só pode revogar chamando Context.revokeUriPermission()
.
Não use Uri.fromFile()
. Ele obriga o app receptor a ter a permissão READ_EXTERNAL_STORAGE
, não funciona se você está tentando o compartilhamento entre usuários e, em versões do Android anteriores a 4.4 (API de nível 19), exigirá que o app tenha WRITE_EXTERNAL_STORAGE
.
Além disso, segmentos de compartilhamento muito importantes, como o app Gmail, não têm o READ_EXTERNAL_STORAGE
, fazendo com que essa chamada falhe.
Em vez disso, você pode usar as permissões de URI para conceder a outros apps acesso a URIs específicos.
Embora as permissões de URI não funcionem em URIs file:// como gerado por Uri.fromFile()
, elas funcionam em URIs associados a provedores de conteúdo. Em vez de implementar um próprio apenas para isso, você pode e precisa usar FileProvider
, conforme explicado em Como compartilhar arquivos.
Compartilhar o arquivo com o app solicitante
Para compartilhar o arquivo com o app que o solicitou, transmita a Intent
que contém o URI de conteúdo e permissões para setResult()
. Quando a Activity
que você acabou de definir estiver concluída, o sistema enviará a Intent
que contém o URI de conteúdo para o app cliente. O snippet de código a seguir mostra como fazer isso:
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); } } });
Forneça aos usuários uma maneira de retornar imediatamente ao app cliente depois de escolher um arquivo.
Uma maneira de fazer isso é fornecer uma marca de seleção ou o botão Concluído. Associe um método ao botão usando o atributo android:onClick
. No método, chame finish()
. Exemplo:
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 ver mais informações relacionadas, consulte: