ファイルを開こうとするとアプリがクラッシュします。 Android Nougatより下では動作しますが、Android Nougatではクラッシュします。システムパーティションからではなく、SDカードからファイルを開こうとするとクラッシュします。いくつかの許可の問題?
サンプルコード
File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line
ログ:
Android.os.FileUriExposedException:file:///storage/emulated/0/test.txtがIntent.getData()によってアプリを超えて公開されている
編集:
Android Nougatをターゲットにしている場合、file://
URIはもう許可されません。代わりにcontent://
URIを使うべきです。ただし、私のアプリはルートディレクトリのファイルを開く必要があります。何か案は?
あなたのtargetSdkVersion >= 24
の場合、FileProvider
クラスを使用して特定のファイルまたはフォルダにアクセスし、他のアプリケーションからアクセスできるようにする必要があります。 here のように、インポートされた依存関係で宣言されたFileProviderとFileProviderが競合しないように、FileProvider
を継承する独自のクラスを作成します。
file://
URIをcontent://
URIに置き換える手順:
FileProvider
を拡張するクラスを追加してください
public class GenericFileProvider extends FileProvider {}
FileProviderの<provider>
タグをAndroidManifest.xml
タグの下の<application>
に追加します。競合を避けるためにAndroid:authorities
属性に固有の権限を指定してください。インポートされた依存関係は、${applicationId}.provider
および他の一般的に使用される権限を指定する可能性があります。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
...
<application
...
<provider
Android:name=".GenericFileProvider"
Android:authorities="${applicationId}.fileprovider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
provider_paths.xml
フォルダーにres/xml
ファイルを作成します。フォルダが存在しない場合は、フォルダを作成する必要があります。ファイルの内容を以下に示します。ルートフォルダ(path=".")
にある外部ストレージへのアクセスを external_files という名前で共有したいということです。<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path name="external_files" path="."/>
</paths>
最後のステップは、以下のコード行を変更することです。
Uri photoURI = Uri.fromFile(createImageFile());
に
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
編集: システムにファイルを開かせる目的で使用している場合は、次のコード行を追加する必要があります。
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
参照してください、完全なコードと解決策が説明されている ここ。
FileProvider
を使用した解決策の他に、これを回避する別の方法があります。簡単に言えば
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Application.onCreate()
で。このように、VMはファイルURI
の公開を無視します。
方法
builder.detectFileUriExposure()
ファイルの公開チェックを有効にします。これは、VmPolicyを設定しない場合のデフォルトの動作でもあります。
content://
URI
を使って何かを送ると、理解できないアプリケーションがあるという問題がありました。 target SDK
バージョンのダウングレードは許可されていません。この場合私の解決策は便利です。
更新:
コメントで述べたように、StrictModeは診断ツールであり、この問題に使用することは想定されていません。私が1年前にこの答えを投稿したとき、多くのアプリはFile urisしか受け取れません。 FileProvider URIを送信しようとしたときにクラッシュするだけです。これは現在ほとんどのアプリで修正されているので、FileProviderソリューションを使用する必要があります。
アプリがAPI 24+をターゲットにしていて、それでもfile://インテントを使用する必要がある場合は、ランタイムチェックを無効にするための簡単な方法を使用できます。
if(Build.VERSION.SDK_INT>=24){
try{
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
m.invoke(null);
}catch(Exception e){
e.printStackTrace();
}
}
メソッドStrictMode.disableDeathOnFileUriExposure
は非表示になり、次のように文書化されています。
/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/
問題は、私のアプリが下手ではなく、content://インテントを使用することによって不自由になりたくないということです。たとえば、content://スキームでmp3ファイルを開くと、file://スキームで同じファイルを開く場合よりもアプリの数が少なくなります。私は自分のアプリの機能を制限することによってGoogleのデザイン障害の代償を払いたくありません。
Googleは開発者にコンテンツスキームの使用を望んでいますが、システムはこれに対応していません。何年もの間、アプリは「コンテンツ」ではなくファイルを使用していました。彼ら?)。
targetSdkVersion
が 24 よりも大きい場合、 FileProvider がアクセスを許可するために使用されます。
Xmlファイルを作成します(パス:res\xml) provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path name="external_files" path="."/>
</paths>
プロバイダー をAndroidManifest.xmlに追加します。
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="${applicationId}.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths"/>
</provider>
androidx を使用している場合、FileProviderパスは次のようになります。
Android:name="androidx.core.content.FileProvider"
および 置換
Uri uri = Uri.fromFile(fileImagePath);
に
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
編集: Intent
でURIを含めている間は、必ず以下の行を追加してください。
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
そしてあなたは行ってもいいです。それが役に立てば幸い。
targetSdkVersion
が24以上の場合、 Android 7.0以降のデバイスのUri
にfile:
Intents
の値を使用することはできません 。
あなたの選択は:
targetSdkVersion
を23以下にする。
コンテンツを内部ストレージに保存し、次に FileProvider
を使用して他のアプリで選択的に利用できるようにします
例えば:
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
(from このサンプルプロジェクトから )
まずAndroidManifestにプロバイダを追加する必要があります
<application
...>
<activity>
....
</activity>
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="com.your.package.fileProvider"
Android:grantUriPermissions="true"
Android:exported="false">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/file_paths" />
</provider>
</application>
xmlリソースフォルダにファイルを作成します(Android Studioを使用している場合は、file_pathを強調表示した後にAlt + Enterを押して[xmlリソースの作成]オプションを選択できます)。
File_pathsファイルで次のように入力します。
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/com.your.package/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
この例はexternal-pathのためのもので、他のオプションのために here を参照することができます。これにより、そのフォルダとそのサブフォルダにあるファイルを共有できます。
あとは、次のようにインテントを作成するだけです。
MimeTypeMap mime = MimeTypeMap.getSingleton();
String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
String type = mime.getMimeTypeFromExtension(ext);
try {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(Uri.fromFile(newFile), type);
}
startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
} catch (ActivityNotFoundException anfe) {
Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
}
_ edit _ :file_pathにSDカードのルートフォルダを追加しました。私はこのコードをテストしました、そしてそれはうまくいきます。
@palash kの答えは正しいし、内部記憶装置のファイルに対してはうまくいきましたが、私の場合は外部記憶装置からファイルを開きたいのですが、sdcardやusbのように外部記憶装置からファイルを開くとアプリがクラッシュしました。 provider_paths.xml 承認された回答から
provider_paths.xml を以下のように変更します。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path path="Android/data/${applicationId}/" name="files_root" />
<root-path
name="root"
path="/" />
</paths>
そしてJavaクラスで(受け入れられた答えはちょっとした編集だけで変更なし)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
これは私が外部記憶装置からのファイルのためのクラッシュを直すのを助けます、これが私のものと同じ問題を抱えている誰かに役立つことを願っています:)
以下のコードをactivity onCreate()に貼り付けるだけです。
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
URIの公開を無視します
FileProviderを使用するのがよい方法です。しかし、この単純な回避策を使用することができます。
_ warning _ :次のAndroidリリースで修正される予定です - https://issuetracker.google.com/issues/37122890#comment4
置き換える:
startActivity(intent);
によって
startActivity(Intent.createChooser(intent, "Your title"));
私の解決策は、Uri.fromFile()を使用する代わりに、文字列としてファイルパスを 'Uri.parse'することでした。
String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
uri = Uri.fromFile(file);
} else {
uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);
FromFile()がファイルポインタを使用しているように見えます。メモリアドレスがすべてのアプリケーションに公開されている場合は安全でない可能性があります。しかし、ファイルパスStringは誰にも害を与えないので、FileUriExposedExceptionをスローせずに機能します。
APIレベル9〜27でテスト済み別のアプリで編集するためにテキストファイルを正常に開きます。 FileProviderもAndroidサポートライブラリもまったく必要としません。
以下のコードをアクティビティonCreate()
に貼り付けるだけです。
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
これはURIの公開を無視します。
ハッピーコーディング:-)
私は上記のPalashの答えを使いましたが、それはいくらか不完全でした、私はこのような許可を与えなければなりませんでした
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}else {
uri = Uri.fromFile(new File(path));
}
intent.setDataAndType(uri, "application/vnd.Android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
アクティビティonCreate()に以下のコードを貼り付けるだけです
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
URIの露出を無視します
サーバーからpdfをダウンロードするには、サービスクラスに以下のコードを追加してください。これが役に立つことを願っています。
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf");
intent = new Intent(Intent.ACTION_VIEW);
//Log.e("pathOpen", file.getPath());
Uri contentUri;
contentUri = Uri.fromFile(file);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) {
Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
intent.setDataAndType(apkURI, "application/pdf");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(contentUri, "application/pdf");
}
そして、あなたのマニフェストにパーミッションとプロバイダーを追加することを忘れないでください。
<uses-permission Android:name="Android.permission.INTERNET" />
<uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />
<application
<provider
Android:name="Android.support.v4.content.FileProvider"
Android:authorities="${applicationId}.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths" />
</provider>
</application>
理由はわかりませんが、Pkosta( https://stackoverflow.com/a/38858040 )とまったく同じようにしましたが、エラーが発生し続けました。
Java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted
私はこの問題について何時間も無駄にしました。原因は?コトリン.
val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent
は実際に私の新しく宣言されたplayIntentを操作する代わりにgetIntent().addFlags
を設定していました。
onCreateにこの2行を追加します
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
シェア方法
File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
これはかなり古い質問ですが、この回答は将来の視聴者のためのものです。それで私は同様の問題に遭遇しました、そして研究の後に、私はこのアプローチに代わるものを見つけました。
ここでのあなたの意図の例:コトリンのあなたの道からあなたのイメージを見るために
val intent = Intent()
intent.setAction(Intent.ACTION_VIEW)
val file = File(currentUri)
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val contentURI = getContentUri(context!!, file.absolutePath)
intent.setDataAndType(contentURI,"image/*")
startActivity(intent)
下記の主な機能
private fun getContentUri(context:Context, absPath:String):Uri? {
val cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf<String>(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + "=? ",
arrayOf<String>(absPath), null)
if (cursor != null && cursor.moveToFirst())
{
val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
}
else if (!absPath.isEmpty())
{
val values = ContentValues()
values.put(MediaStore.Images.Media.DATA, absPath)
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
else
{
return null
}
}
同様に、画像の代わりに、あなたはpdfのような他のどんなファイルフォーマットを使うこともできます、そして私の場合、それはちょうどうまくいきました
ここに私の解決策:
in Manifest.xml
<application
Android:name=".main.MainApp"
Android:allowBackup="true"
Android:icon="@drawable/ic_app"
Android:label="@string/application_name"
Android:logo="@drawable/ic_app_logo"
Android:theme="@style/MainAppBaseTheme">
<provider
Android:name="androidx.core.content.FileProvider"
Android:authorities="${applicationId}.provider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/provider_paths"/>
</provider>
in res/xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<external-path name="external_files" path="."/>
</paths>
私のフラグメントには次のコードがあります:
Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);
必要なのはそれだけです。
また不要作成する
public class GenericFileProvider extends FileProvider {}
Android 5.0、6.0、およびAndroid 9.0でテストしたところ、成功しました。
imageuriパスが簡単にコンテンツに入るように、私はこのメソッドを入れました。
enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(context.getContentResolver(),
inImage, "Title", null);
return Uri.parse(path);
}