他のアプリとコンテンツを共有するためのサポートFileProviderの使用方法


142

AndroidサポートライブラリのFileProviderを使用して、内部ファイルを外部アプリケーションと正しく(OPENではなく)共有する方法を探しています。

ドキュメントの例に従って、

<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>

そして、ShareCompatを使用して、次のようにファイルを他のアプリと共有します。

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

FLAG_GRANT_READ_URI_PERMISSION dataはインテントので指定されたUriにのみ許可を与えるので機能しません。EXTRA_STREAMエクストラの値ではなく(によって設定されたようにsetStream)許可します。

プロバイダーandroid:exportedtrueを設定してセキュリティを危険にさらそうとしましたがFileProvider、それ自体がエクスポートされているかどうかを内部で確認しました。


7
+1これは本当に元の質問のようです- 私が知る限り、GoogleやStackOverflowにはもありません
フィル

最小限のgithubサンプルプロジェクトを使用して基本的なFileProviderセットアップを実行するための投稿は次のとおりです:stackoverflow.com/a/48103567/2162226 。ローカルスタンドアロンプ​​ロジェクト
Gene Bo

回答:


159

FileProviderサポートライブラリから使用する場合、他のアプリが特定のUriを読み取るための権限を(実行時に)手動で付与および取り消す必要があります。使用Context.grantUriPermissionContext.revokeUriPermission方法。

例えば:

//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

最後の手段として、パッケージ名を提供できない場合は、特定のインテントを処理できるすべてのアプリに権限を付与できます。

//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

ドキュメントによる代替方法:

  • setData()を呼び出して、コンテンツURIをインテントに配置します。
  • 次に、FLAG_GRANT_READ_URI_PERMISSIONまたはFLAG_GRANT_WRITE_URI_PERMISSION、あるいはその両方を使用して、メソッドIntent.setFlags()を呼び出します。
  • 最後に、インテントを別のアプリに送信します。ほとんどの場合、これを行うには、setResult()を呼び出します。

    インテントで付与された権限は、受信側のアクティビティのスタックがアクティブである間は有効のままです。スタックが完了すると、
    権限は自動的に削除されます。
    クライアントアプリの1つのアクティビティに付与された権限は
    、そのアプリの他のコンポーネントに自動的に拡張されます。

ところで 必要な場合は、FileProviderのソースをコピーしてattachInfoメソッドを変更し、プロバイダーがエクスポートされているかどうかを確認できないようにすることができます。


26
grantUriPermissionメソッドを使用して、アクセス許可を付与するアプリのパッケージ名を提供する必要があります。ただし、共有する場合、通常、共有先がどのアプリケーションであるかはわかりません。
Randy Sugianto 'Yuku'

パッケージ名がわからず、他のアプリがそれを開けるようにしたいだけの場合
StuStirling

3
最新のソリューション@Lechoの実際のコードを提供していただけますか?
StErMi 2014年

2
サポートFileProviderがsetFlagsで機能せず、grantUriPermissionで機能する理由を追跡するバグが開いていますか?それともバグではありませんか?その場合、これはバグではありませんか?
nmr

1
これに加えて、ShareCompatを使用して作成されたインテントに対してgrantUriPermission呼び出しが機能しないことを発見しましたが、手動でインテントを作成したときに機能しました。
StuStirling 2016年

33

内部のアプリフォルダーからファイルを共有する方法を完全に機能するコードサンプル。Android 7およびAndroid 5でテスト済み。

AndroidManifest.xml

</application>
   ....
    <provider
        android:name="androidx.core.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>

コード自体

    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);

1
ShareCompat.IntentBuilderとURIの読み取り権限を使用すると、ようやくそれができました。
Cord Rehn、2016年

Androidの他のバージョンの中断
Oliver Dixon

@OliverDixon:どれ?Android 6.0および9.0でテストしました。
バイオレットキリン

1
これが機能するを使用する唯一の答えFileProviderです。他の回答では、インテントの処理が間違っており(彼らはを使用していませんShareCompat.IntentBuilder)、そのインテントは、外部アプリから意味のある方法で開くことができません。私は最終的にあなたの答えを見つけるまで、文字通り一日のほとんどこのナンセンスに費やしてきました。
バイオレットキリン

1
@VioletGiraffe私もShareCompatを使用していませんが、私の作品です。私の問題は、セレクターインテントとセレクターに渡される前にインテントに読み取り権限を付与する必要があるときに、誤ってセレクターのインテントに読み取り権限を付与していたことでした。私はそれが理にかなっていると思います。正しいインテントに読み取り権限を付与していることを確認してください。
ライアン

23

この解決策は、OS 4.4以降で機能します。すべてのデバイスで機能させるために、古いデバイスの回避策を追加しました。これにより、常に最も安全なソリューションが使用されます。

Manifest.xml:

    <provider
        android:name="androidx.core.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);
    }
}

権限は、フラグメントまたはアクティビティのonResumeおよびonDestroy()メソッドのrevokeFileReadPermission()で取り消されます。


1
この解決策は私にとってはうまくいきましたが、私がintent.setDataAndType(uri、 "video / *");を追加した後でのみです。
ちなみに、

fileからonResumeに変更されないということonDestroyですか?
CoolMind 2018

16

Philが元の質問についてのコメントで言っているように、これはユニークであり、GoogleのSOに関する他の情報はないので、私も結果を共有する必要があると思いました。

私のアプリでは、FileProviderはそのままで、共有インテントを使用してファイルを共有しました。FileProviderのセットアップ以外に、特別な構成やコードは必要ありませんでした。私のmanifest.xmlに以下を配置しました:

    <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>

my_paths.xmlには、

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

私のコードでは:

    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)));

また、アプリのプライベートストレージにあるファイルストアをGmailやgoogleドライブなどのアプリと問題なく共有できます。


9
残念ながら、これは機能しません。その変数fileToShareは、ファイルがSDカードまたは外部ファイルシステムに存在する場合にのみ機能します。FileProviderを使用する目的は、内部システムのコンテンツを共有できるようにすることです。
キースコノリー

これはローカルストレージファイルで正常に動作するようです(Android 5以降)。しかし、Android 4で問題が発生します
Peterdk

15

私の知る限り、これはAndroidの新しいバージョンでのみ機能するため、おそらく別の方法でそれを実行する必要があります。このソリューションは、4.4では機能しますが、4.0または2.3.3では機能しません。したがって、これは、Androidデバイスで実行することを目的としたアプリのコンテンツを共有するのに役立つ方法ではありません。

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>

権限の指定方法に注意してください。URIを作成して共有インテントを起動するアクティビティを指定する必要があります。この場合、アクティビティはSharingActivityと呼ばれます。この要件は、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>

パスの指定方法に注意してください。上記はデフォルトでプライベート内部ストレージのルートになります。

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"));

この例では、JPEG画像を共有しています。

最後に、ファイルを適切に保存し、次のような方法でファイルにアクセスできることを確認することをお勧めします。

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!");
}

8

私のアプリではFileProviderは問題なく動作し、ファイルディレクトリに保存されている内部ファイルをGmail、Yahooなどの電子メールクライアントに添付できます。

私が置いたAndroidのドキュメントで述べたように私のマニフェストで:

<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>

また、私のファイルはルートファイルディレクトリに格納されていたため、filepaths.xmlは次のようになりました。

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

今コードで:

 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();
                }
            }

これは私にとってうまくいきました。


1
あなたはpaths = "を投稿するための本当のMVPです。" 私のために働いた
robsstackoverflow 2017年

イム取得エラーは次のように「が含まれて構成されたルートを見つけることができませんでした/」私は同じコードを使用している
Rakshithクマール

5

カメラから画像を取得した場合、これらのソリューションはAndroid 4.4では機能しません。この場合、バージョンを確認することをお勧めします。

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);
}

1

上記の答えを改善するためだけに:NullPointerExを取得している場合:

コンテキストなしでgetApplicationContext()を使用することもできます

                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);
                }

1

数日間私たちをブロックした何かを共有したいと思います。fileproviderコード、アプリケーションタグの後にではなく、アプリケーションタグの間に挿入する必要があります。些細なことかもしれませんが、明記されることはなく、私は誰かを助けることができると思いました!(piolo94に再度感謝します)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.