web-dev-qa-db-ja.com

android.os.FileUriExposedException:file:///storage/emulated/0/test.txtがIntent.getData()によってアプリを超えて公開されています

ファイルを開こうとするとアプリがクラッシュします。 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を使うべきです。ただし、私のアプリはルートディレクトリのファイルを開く必要があります。何か案は?

603
Thomas Vos

あなたの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);
    

参照してください、完全なコードと解決策が説明されている ここ。

1090
Pkosta

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ソリューションを使用する必要があります。

274
hqzxzwb

アプリが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は開発者にコンテンツスキームの使用を望んでいますが、システムはこれに対応していません。何年もの間、アプリは「コンテンツ」ではなくファイルを使用していました。彼ら?)。

142
Pointer Null

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

そしてあなたは行ってもいいです。それが役に立てば幸い。

120
Pankaj Lilan

targetSdkVersionが24以上の場合、 Android 7.0以降のデバイスのUrifile:Intentsの値を使用することはできません

あなたの選択は:

  1. targetSdkVersionを23以下にする。

  2. コンテンツを内部ストレージに保存し、次に FileProvider を使用して他のアプリで選択的に利用できるようにします

例えば:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(from このサンプルプロジェクトから

80
CommonsWare

まず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カードのルートフォルダを追加しました。私はこのコードをテストしました、そしてそれはうまくいきます。

44
Karn Patel

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

これは私が外部記憶装置からのファイルのためのクラッシュを直すのを助けます、これが私のものと同じ問題を抱えている誰かに役立つことを願っています:)

25
Ramz

以下のコードをactivity onCreate()に貼り付けるだけです。

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

URIの公開を無視します

20
Kaushal Sachan

FileProviderを使用するのがよい方法です。しかし、この単純な回避策を使用することができます。

_ warning _ :次のAndroidリリースで修正される予定です - https://issuetracker.google.com/issues/37122890#comment4

置き換える:

startActivity(intent);

によって

startActivity(Intent.createChooser(intent, "Your title"));
19
Simon

私の解決策は、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サポートライブラリもまったく必要としません。

18
CrazyJ36

以下のコードをアクティビティonCreate()に貼り付けるだけです。

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
StrictMode.setVmPolicy(builder.build());

これはURIの公開を無視します。

ハッピーコーディング:-)

14
Ripdaman Singh

私は上記の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);
12
Max

アクティビティonCreate()に以下のコードを貼り付けるだけです

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

URIの露出を無視します

6
AnilS

サーバーから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>
4

理由はわかりませんが、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を設定していました。

3
jimbo1qaz

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..."));
2
DEVSHK

これはかなり古い質問ですが、この回答は将来の視聴者のためのものです。それで私は同様の問題に遭遇しました、そして研究の後に、私はこのアプローチに代わるものを見つけました。

ここでのあなたの意図の例:コトリンのあなたの道からあなたのイメージを見るために

 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のような他のどんなファイルフォーマットを使うこともできます、そして私の場合、それはちょうどうまくいきました

1
S.Sho

ここに私の解決策:

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でテストしたところ、成功しました。

1
Alex

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);
}
0
Jitu Batiya