ClipData.Item.getUri()エラーを介してアプリを超えて公開されたものを解決します



Solve Exposed Beyond App Through Clipdata

以下は、システムのカメラアプリを呼び出して写真を撮る簡単なコードです。

void takePhoto(String cameraPhotoPath) { File cameraPhoto = new File(cameraPhotoPath) Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE) takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(cameraPhoto)) startActivityForResult(takePhotoIntent, REQUEST_TAKE_PHOTO) }

通常、実行に問題はありませんが、targetSdkVersionが24以上として指定され、API> = 24のデバイスで実行されている場合、例外がスローされます。

android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/IMG_20170125_144112.jpg exposed beyond app through ClipData.Item.getUri() at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799) at android.net.Uri.checkFileUriExposed(Uri.java:2346) at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832) at android.content.Intent.prepareToLeaveProcess(Intent.java:8909) ...

その理由は、FileUriExposedExceptionのドキュメントを確認するためです。

アプリケーションがfile:// Uriを別のアプリケーションに公開すると例外がスローされます 。受信アプリが共有パスにアクセスできない可能性があるため、この公開はお勧めしません。

たとえば、受信アプリケーションがREAD_EXTERNAL_STORAGEランタイム権限を要求しない場合や、プラットフォームがユーザープロファイルの境界を越えてUriを共有する場合があります。代わりに、アプリケーションはcontent:// Urisを使用して、プラットフォームが受信アプリケーションの一時的なアクセス許可を拡張してリソースにアクセスできるようにする必要があります。このアクションは、N以上のアプリケーションに対してのみスローされます。以前のSDKバージョンのアプリケーションはfile:// Uriを共有できますが、共有しないことを強くお勧めします。全体として、Androidでは、file:// Uriをアプリ内の他のアプリに公開することはできなくなりました。これには、IntentやClipDataなどのメソッドが含まれますがこれらに限定されません。



その理由は、file:// Uriを使用すると、次のようなリスクが発生するためです。ファイルはプライベートであり、file:// Uriを受信するアプリケーションはファイルにアクセスできません。 Android6.0以降のランタイム権限の導入。 file:// Uriを受信するアプリケーションがREAD_EXTERNAL_STORAGE権限を申請しない場合、ファイルの読み取り時にクラッシュが発生します。

したがって、 GoogleはFileProviderを提供しています。これを使用して、file:// URIを置き換えるcontent:// Uriを生成できます。



最初にAndroidManifest.xmlにプロバイダーを追加します

void takePhoto(String cameraPhotoPath) { File cameraPhoto = new File(cameraPhotoPath) Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE) Uri photoUri = FileProvider.getUriForFile( this, getPackageName() + '.provider', cameraPhoto) takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri) startActivityForResult(takePhotoIntent, REQUEST_TAKE_PHOTO) }
res / xml / provider_paths.xml
private void initPhotoError(){ / / android 7.0 system to solve the problem of taking pictures StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder() StrictMode.setVmPolicy(builder.build()) builder.detectFileUriExposure() }
次に、コードを変更します
...

またはこの方法で変更します

onCreateで次のコードを呼び出します

java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.xxx/cache/test.txt at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:679) at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:378) ...

FileProviderは、content:// Uriの利点を利用します。 共有ファイルの読み取りおよび書き込み権限を制御できます Intent.setFlags()が呼び出されている限り、共有ファイルへの相手のアプリケーションのアクセス許可を設定でき、相手のアプリケーションが終了すると、その許可は自動的に無効になります。
対照的に、file:// openを使用する場合、アクセス制御はファイルシステムのアクセス許可を変更することによってのみ実現できるため、アクセス制御はすべての_appsに対して有効です。アプリを区別できません。共有ファイルの実際のパスを隠すことができます。
AndroidManifest.xmlのノードに追加されるFileProviderを定義します



UriFile imagePath = new File(Context.getFilesDir(), 'images') File newFile = new File(imagePath, '2016/pic.png') Uri contentUri = getUriForFile(getContext(), getPackageName() + '.fileprovider', newFile)

Android:authoritiesは、プロバイダーを識別するために使用される一意の識別子です。同じ携帯電話の「特権」文字列は、1つのアプリでのみ使用できます。競合が発生した場合、アプリをインストールできません。マニフェストプレースホルダーを使用して、権限の一意性を保証できます。 Android:exportedはfalseに設定する必要があります。そうしないと、ランタイムがエラーを報告しますjava.lang.SecurityException:プロバイダーをエクスポートしないでください。 Android:grantUriPermissionsは、共有ファイルのアクセス権を制御するために使用され、Javaコードで設定することもできます。パスと変換ルールの指定FileProviderは、共有ファイルの実際のパスを非表示にして、content:// openパスに変換するため、変換のルールも設定する必要があります。 Android:resource = '@ xml / provider_paths'この属性は、ルールが配置されているファイルを指定します。

RES / XML / provider_paths.xml:

|_+_|

< パス >次の子ノードに対応するパスを定義できますファイルパスContext.getFilesDir()
キャッシュパスContext.getCacheDir()
外部パスEnvironment.getExternalStorageDirectory()
外部ファイルへのパスContext.getExternalFilesDir(空)
外部キャッシュパスContext.getExternalCacheDir()
file://からcontent://への変換ルール:プレフィックスを置き換えます:category://のcontent:// $ {android:authorities}を置き換えます。

トラバースされた子ノードを照合および置換して、ファイルパスプレフィックスと一致できる子ノードを見つけます。ファイルパスの内容をパスの値に置き換えます。ファイルパスの残りの部分は変更されません。知っておく必要があるのは、 ファイルへのパスはXMLに含まれている必要があります つまり、2.1で一致する子ノードを見つけることができる必要があります。そうしないと、例外がスローされます。

|_+_|

コードで生成されたコンテンツ

ファイルアクセスのアクセス許可を設定するには、次の2つの方法があります。

Context.grantUriPermission(package、uri、modeFlags)を呼び出します。このようなアクセス許可の設定は、Context.revokeUriPermission(uri、modeFlags)またはsystem-restartを手動で呼び出した場合にのみ期限切れになります。
Intent.setFlags()を呼び出して、アクセス許可が無効になる時刻を設定します。つまり、インテントのアクティビティを受信したヒープが破棄される時刻を設定します。

オフトピック: ContentProviderの小さなトリック多くのAndroidSDKは、ユーザーが初期化コードを使用する前に呼び出す必要があることがよくあります。SomeSdk.init(Context)、実際、このステップはContentProviderの助けを借りて省略できます。原則は、OnCreateでSDKの初期化を実行するSDKのAndroidManifestにContentProviderを登録することです。 ContentProviderは、アプリケーションプロセスが作成されるときに作成され、コンテキストへの参照はそのonCreateで利用できます。