Depois de configurar seu app para compartilhar arquivos usando URIs de conteúdo, é possível 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 do servidor que outros aplicativos possam invocar. Essa abordagem permite que um aplicativo cliente permita que os usuários selecionem um arquivo 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 de apps clientes e responder com um URI de conteúdo, seu app precisa
fornecer um Activity
de seleção de arquivos. Apps clientes iniciam essa
Activity
chamando startActivityForResult()
com um Intent
contendo a ação
ACTION_PICK
. 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 o
Activity
no manifesto, com um filtro de intent
que corresponda à ação ACTION_PICK
e às
categorias CATEGORY_DEFAULT
e
CATEGORY_OPENABLE
. Adicione também filtros do tipo MIME
para os arquivos que o app exibe a outros apps. O snippet a seguir mostra como especificar o novo 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 Activity
que exiba os arquivos disponíveis no 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 esse 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, seu aplicativo precisa determinar qual arquivo foi selecionado e
gerar um URI de conteúdo para ele. Como o Activity
exibe a
lista de arquivos disponíveis em uma ListView
, quando o usuário clica no nome de um arquivo,
o sistema chama o método onItemClick()
, em que você pode acessar o arquivo selecionado.
Ao usar uma intent para enviar o URI de um arquivo de um app para outro,
é preciso ter o cuidado de ter um URI que outros
apps possam ler. Fazer isso em dispositivos com o Android 6.0 (nível 23 da API) e versões mais recentes
exige cuidados especiais
por causa de mudanças no modelo de permissões nessa versão do Android, principalmente
a READ_EXTERNAL_STORAGE
's
se tornando uma
permissão perigosa, que o app receptor pode não ter.
Com essas considerações em mente, recomendamos que você evite o uso de
Uri.fromFile()
, que
apresenta várias desvantagens. Esse método:
- não permite o compartilhamento de arquivos entre perfis;
- É necessário que o app tenha permissão para
WRITE_EXTERNAL_STORAGE
em dispositivos com Android 4.4 (nível 19 da API) ou versões anteriores. - Requer que os apps receptores tenham a permissão
READ_EXTERNAL_STORAGE
, que falhará em destinos de compartilhamento importantes, como o Gmail, que não têm essa permissão.
Em vez de usar Uri.fromFile()
,
use 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://
gerados por Uri.fromFile()
, elas funcionam
em URIs associados a provedores de conteúdo. A
API FileProvider
pode
ajudar 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 especificada no elemento
<provider>
para a 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, incluindo 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
especificado 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 não foi especificado, 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 ao app cliente adicionando o URI de conteúdo a uma Intent
e 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 conceder acesso com segurança aos seus arquivos usando permissões de acesso temporárias. Evite chamar o método
Context.grantUriPermission()
para o URI de
conteúdo de um arquivo, já que ele concede acesso que só pode ser revogado
chamando Context.revokeUriPermission()
.
Não use Uri.fromFile()
. Ele força o recebimento de apps
a ter a permissão READ_EXTERNAL_STORAGE
,
não funciona se você estiver tentando compartilhar entre usuários e, em versões
do Android anteriores à 4.4 (API de nível 19), exigiria que o
app tivesse WRITE_EXTERNAL_STORAGE
.
Além disso, destinos 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 com provedores de conteúdo. Em vez de implementar um próprio apenas para isso,
você pode e precisa usar FileProvider
,
conforme explicado em Compartilhamento de arquivos.
Compartilhar o arquivo com o app solicitante
Para compartilhar o arquivo com o app que o solicitou, transmita o Intent
que contém o URI de conteúdo e as permissões para setResult()
. Quando a Activity
que você acabou de definir for concluída, o
sistema vai 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()
. Por 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 outras informações relacionadas, consulte:
- Design de URIs de conteúdo
- Como implementar permissões do provedor de conteúdo
- Permissões
- Intents e filtros de intent