Como compartilhar um arquivo

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: