android studio Como usar o suporte FileProvider para compartilhar conteúdo para outros aplicativos?



toolbar android material design (8)

Eu estou procurando uma maneira de compartilhar corretamente (não abrir) um arquivo interno com aplicativo externo usando FileProvider da biblioteca de suporte do FileProvider .

Seguindo o exemplo nos documentos,

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.android.supportv4.my_files"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/my_paths" />
</provider>

e usando ShareCompat para compartilhar um arquivo para outros aplicativos da seguinte forma:

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

não funciona, uma vez que o FLAG_GRANT_READ_URI_PERMISSION apenas concede permissão para o Uri especificado nos data da intenção, não o valor do extra EXTRA_STREAM (como foi definido por setStream ).

Eu tentei comprometer a segurança, definindo android:exported para true para o provedor, mas FileProvider internamente verifica se ele é exportado, quando assim, ele lança uma exceção.


Exemplo de código totalmente funcional como compartilhar o arquivo da pasta interna do aplicativo. Testado no Android 7 e Android 5.

AndroidManifest.xml

</application>
   ....
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="android.getqardio.com.gmslocationtest"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

xml / provider_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="share"
        path="external_files"/>
</paths>

Código em si

    File imagePath = new File(getFilesDir(), "external_files");
    imagePath.mkdir();
    File imageFile = new File(imagePath.getPath(), "test.jpg");

    // Write data in your file

    Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);

    Intent intent = ShareCompat.IntentBuilder.from(this)
                .setStream(uri) // uri from FileProvider
                .setType("text/html")
                .getIntent()
                .setAction(Intent.ACTION_VIEW) //Change if needed
                .setDataAndType(uri, "image/*")
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

   startActivity(intent);

apenas para melhorar a resposta dada acima: se você está recebendo NullPointerEx:

você também pode usar getApplicationContext () sem contexto

                List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }

Quero compartilhar algo que nos bloqueou por alguns dias: o código do provedor de arquivos DEVE ser inserido entre as tags do aplicativo, não depois dele. Pode ser trivial, mas nunca especificado, e pensei que poderia ter ajudado alguém! (obrigado novamente a piolo94)


Uma vez que, como diz Phil em seu comentário sobre a questão original, isso é único e não há outras informações sobre SO no google, achei que também deveria compartilhar meus resultados:

No meu aplicativo FileProvider trabalhou fora da caixa para compartilhar arquivos usando a intenção de compartilhar. Não havia nenhuma configuração especial ou código necessário, além disso, para configurar o FileProvider. No meu manifest.xml eu coloquei:

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.my.apps.package.files"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

Em my_paths.xml eu tenho:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>

No meu código eu tenho:

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setType("application/xml");

    Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

    startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));

E eu sou capaz de compartilhar meus arquivos de armazenamento no meu armazenamento privado de aplicativos com aplicativos como o Gmail e o Google Drive sem nenhum problema.


Tanto quanto eu posso dizer isso só funcionará em versões mais recentes do Android, então você provavelmente terá que descobrir uma maneira diferente de fazê-lo. Essa solução funciona para mim na versão 4.4, mas não na versão 4.0 ou 2.3.3, portanto, essa não será uma maneira útil de compartilhar conteúdo para um aplicativo que deve ser executado em qualquer dispositivo Android.

No manifest.xml:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.myapp.SharingActivity"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

Tome nota cuidadosa de como você especifica as autoridades. Você deve especificar a atividade a partir da qual você criará o URI e iniciará a intenção de compartilhamento. Nesse caso, a atividade é chamada de SharingActivity. Esse requisito não é óbvio nos documentos do Google!

file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="just_a_name" path=""/>
</paths>

Tenha cuidado como você especifica o caminho. O padrão acima é a raiz do seu armazenamento interno privado.

Em SharingActivity.java:

Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));

Neste exemplo, estamos compartilhando uma imagem JPEG.

Finalmente, é provavelmente uma boa idéia assegurar-se de que você salvou o arquivo corretamente e que você pode acessá-lo com algo parecido com isto:

File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
    Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
    Log.w(TAG, "File not found!");
}

Se você obtiver uma imagem da câmera, nenhuma dessas soluções funcionará para o Android 4.4. Neste caso, é melhor verificar as versões.

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        uri = Uri.fromFile(file);
    } else {
        uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST);
}

Esta solução funciona para mim desde o OS 4.4. Para que funcione em todos os dispositivos, adicionei uma solução alternativa para dispositivos mais antigos. Isso garante que sempre seja usada a solução mais segura.

Manifest.xml:

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.package.name.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

file_paths.xml:

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

Java:

public static void sendFile(Context context) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    String dirpath = context.getFilesDir() + File.separator + "directory";
    File file = new File(dirpath + File.separator + "file.txt");
    Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // Workaround for Android bug.
    // grantUriPermission also needed for KITKAT,
    // see https://code.google.com/p/android/issues/detail?id=76683
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }
    if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
    }
}

public static void revokeFileReadPermission(Context context) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        String dirpath = context.getFilesDir() + File.separator + "directory";
        File file = new File(dirpath + File.separator + "file.txt");
        Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
        context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

A permissão é revogada com revokeFileReadPermission () nos métodos onResume e onDestroy () do Fragment ou da Activity.


No meu aplicativo FileProvider funciona muito bem, e eu sou capaz de anexar arquivos internos armazenados no diretório de arquivos para clientes de e-mail como Gmail, Yahoo etc.

No meu manifesto, como mencionado na documentação do Android, coloquei:

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.package.name.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>

E como meus arquivos estavam armazenados no diretório de arquivos raiz, os arquivos filepaths.xml eram os seguintes:

 <paths>
<files-path path="." name="name" />

Agora no código:

 File file=new File(context.getFilesDir(),"test.txt");

 Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);

 shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
                                     "Test");

 shareIntent.setType("text/plain");

 shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                                 new String[] {"email-address you want to send the file to"});

   Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
                                                   file);

                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);

                shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
                                                        uris);


                try {
                   context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));                                                      


                }
                catch(ActivityNotFoundException e) {
                    Toast.makeText(context,
                                   "Sorry No email Application was found",
                                   Toast.LENGTH_SHORT).show();
                }
            }

Isso funcionou para mim.Espero que isso ajude :)





android-fileprovider