Depois de configurar seu app para compartilhar arquivos usando URIs de conteúdo, é possível responder aos comandos solicitações para esses arquivos. Uma forma de responder a essas solicitações é fornecer um arquivo de seleção do aplicativo de servidor que outros aplicativos podem invocar. Essa abordagem permite que um cliente de serviço para permitir que os usuários selecionem um arquivo do aplicativo do servidor e recebam o arquivo URI de conteúdo.
Esta lição mostra como criar uma Activity
de seleção de arquivos no seu app.
que responde a solicitações de arquivos.
Receber solicitações de arquivos
Para receber solicitações de arquivos de aplicativos clientes e responder com um URI de conteúdo, seu aplicativo deve
forneça um Activity
de seleção de arquivo. Apps clientes iniciam isto
Activity
chamando startActivityForResult()
com um Intent
contendo a ação.
ACTION_PICK
. Quando o app cliente chama
startActivityForResult()
, seu app pode
retornam um resultado ao app cliente, na forma de um URI de conteúdo para o arquivo selecionado pelo usuário.
Para saber como implementar uma solicitação de arquivo em um aplicativo cliente, consulte a lição Como solicitar um arquivo compartilhado.
Criar uma atividade de seleção de arquivos
Para configurar a seleção de arquivos Activity
, comece especificando o
Activity
no manifesto, com um filtro de intent
que corresponde à ação ACTION_PICK
e ao
categorias CATEGORY_DEFAULT
e
CATEGORY_OPENABLE
. Adicionar também filtros de tipo MIME
para os arquivos que seu aplicativo exibe a outros aplicativos. O snippet a seguir mostra como especificar
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 mostre os arquivos disponíveis na
ao diretório files/images/
do app no armazenamento interno e permite que o usuário escolha
o arquivo desejado. O snippet a seguir demonstra como definir isso
Activity
e responda à 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
Quando um usuário seleciona um arquivo compartilhado, o aplicativo deve determinar qual arquivo foi selecionado e
depois gerar um URI de conteúdo para o arquivo. Como o Activity
exibe o
lista de arquivos disponíveis em um ListView
, quando o usuário clica no nome de um arquivo
o sistema chamará 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,
você precisa ter o cuidado de conseguir um URI que
podem ler. Isso acontece em dispositivos com o Android 6.0 (nível 23 da API) e versões mais recentes.
exige configurações
devido a mudanças no modelo de permissões nessa versão do Android, especialmente
READ_EXTERNAL_STORAGE
se tornar um
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
WRITE_EXTERNAL_STORAGE
em dispositivos com o Android 4.4 (nível 19 da API) ou versões anteriores. - Exige que os apps receptores tenham o
READ_EXTERNAL_STORAGE
, que vai falhar em alvos de compartilhamento importantes, como o Gmail, que não têm essa permissão.
Em vez de usar Uri.fromFile()
,
é possível 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 URIs file://
,
gerados por Uri.fromFile()
, eles fazem
funcionam em URIs associados a provedores de conteúdo. A
A API FileProvider
pode
ajudar você a criar esses URIs. Essa abordagem também funciona com arquivos
no armazenamento externo, mas no armazenamento local do app que envia o intent.
No onItemClick()
, receba um
File
para o nome do arquivo selecionado e o transmita como um argumento para
getUriForFile()
, junto com
autoridade especificada na
<provider>
para a FileProvider
.
O URI de conteúdo resultante contém a autoridade, um segmento de caminho correspondente ao URL
(conforme especificado nos metadados XML) e o nome do arquivo, incluindo seu
. Como FileProvider
mapeia diretórios para um caminho
baseados em metadados XML é descrito 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
descritos na seção Especificar diretórios compartilháveis. Se você ligar
getUriForFile()
para um
File
em um caminho não 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 ao app cliente da seguinte forma:
adicionar o URI de conteúdo a uma Intent
e, em seguida, definir sinalizações de permissão no
o 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
de conceder acesso seguro aos seus arquivos usando permissões de acesso temporário. Evitar chamadas
Método Context.grantUriPermission()
para um
URI de conteúdo do arquivo, já que esse método concede acesso que você só pode revogar
chamando Context.revokeUriPermission()
.
Não use Uri.fromFile()
. Ela força os apps receptores
tenham a permissão READ_EXTERNAL_STORAGE
,
não funcionará se você estiver tentando compartilhar entre usuários e em versões
do Android anterior à 4.4 (API de nível 19), exigiria sua
para ter WRITE_EXTERNAL_STORAGE
.
E alvos de compartilhamento muito importantes, como o aplicativo Gmail, não têm
a READ_EXTERNAL_STORAGE
, causando
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 com os URIs file:// como são gerados
Uri.fromFile()
, eles têm
trabalham em URIs associados a provedores de conteúdo. Em vez de implementar sua própria solução apenas para isso,
você pode e deve 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 permissões para setResult()
. Quando o Activity
que você acabou de definir for concluído, o
o sistema envia o 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. Associar um método a
o botão usando o
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 outras informações relacionadas, consulte:
- Como projetar URIs de conteúdo
- Implementação de permissões do provedor de conteúdo
- Permissões
- Intents e filtros de intent