当我试图打开一个文件时,应用程序崩溃了。它可以在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: ///storage/emulated/0/test.txt Intent.getData ()

编辑:

当针对Android Nougat时,file:// uri不再被允许。我们应该使用content:// uri。但是,我的应用程序需要打开根目录下的文件。什么好主意吗?


当前回答

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

这帮助我修复了外部存储文件的崩溃,希望这将帮助一些人有同样的问题,我的 :)

其他回答

我花了几乎一天的时间试图弄清楚为什么我得到了这个异常。经过大量的斗争,这个配置工作得很完美(Kotlin):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.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

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

目的本身

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

我在这里解释整个过程。

Xamarin的。安卓

注意:路径xml/provider_paths.xml (.axml)无法解析,即使在将xml文件夹放在资源下(也许它可以放在像Values这样的现有位置,但没有尝试),所以我采用了这个方法,目前它是有效的。测试表明,每次应用程序运行时只需要调用它一次(这是有意义的,因为它改变了主机VM的操作状态)。

注意:xml需要大写,所以Resources/ xml /provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");                
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");                
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);

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

这帮助我修复了外部存储文件的崩溃,希望这将帮助一些人有同样的问题,我的 :)

首先,你需要添加一个提供商到你的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_paths后按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>

这个例子是关于外部路径的,你可以参考这里的更多选项。 这将允许您共享该文件夹及其子文件夹中的文件。

现在剩下的就是创建意图,如下所示:

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

编辑:我在file_paths中添加了sd卡的根文件夹。我已经测试了这段代码,它确实工作。

@Pkosta的回答是这样做的一种方式。

除了使用FileProvider,你还可以将文件插入到MediaStore中(特别是图像和视频文件),因为MediaStore中的文件可以被每个应用程序访问:

MediaStore主要针对视频、音频和图像MIME类型,但是从Android 3.0 (API级别11)开始,它也可以存储非媒体类型(见MediaStore)。文件获取更多信息)。文件可以使用scanFile()插入到MediaStore中,然后将适合共享的content://样式的Uri传递给提供的onScanCompleted()回调。注意,一旦将内容添加到系统MediaStore中,设备上的任何应用程序都可以访问内容。

例如,你可以像这样插入一个视频文件到MediaStore:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUri类似于content://media/external/video/media/183473,它可以直接传递给Intent.putExtra:

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

这对我来说很有用,并且省去了使用FileProvider的麻烦。