865

当我尝试打开文件时,应用程序崩溃了。它在 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。但是,我的应用程序需要在根目录中打开文件。有任何想法吗?

4

28 回答 28

1544

如果您的targetSdkVersion >= 24,那么我们必须使用FileProvider类来授予对特定文件或文件夹的访问权限,以使其他应用程序可以访问它们。我们创建自己的继承类FileProvider,以确保我们的 FileProvider 不会与在导入依赖项中声明的 FileProvider 冲突,如此处所述

用 URI替换file://URI 的步骤content://

  • 在标签下添加 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="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>
    </application>
</manifest>
  • 然后在文件夹中创建一个provider_paths.xml文件res/xml。如果文件夹尚不存在,则可能需要创建它。该文件的内容如下所示。它描述了我们希望在根文件夹中共享(path=".")对名为external_files的外部存储的访问。
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>
  • 最后一步是更改下面的代码行

     Uri photoURI = Uri.fromFile(createImageFile());
    

     Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
    
  • 编辑:如果您使用意图使系统打开您的文件,您可能需要添加以下代码行:

     intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

请参阅此处已解释的完整代码和解决方案

于 2016-08-09T18:33:21.063 回答
335

除了使用 的解决方案之外FileProvider,还有另一种解决方法。简单的说

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

Application.onCreate(). 通过这种方式,VM 忽略了文件URI暴露。

方法

builder.detectFileUriExposure()

启用文件暴露检查,如果我们不设置 VmPolicy,这也是默认行为。

我遇到了一个问题,如果我用 acontent:// URI发送东西,一些应用程序就是无法理解它。target SDK并且不允许降级版本。在这种情况下,我的解决方案很有用。

更新:

正如评论中提到的,StrictMode 是诊断工具,不应该用于这个问题。一年前我发布这个答案时,许多应用程序只能接收文件 uris。当我尝试向他们发送 FileProvider uri 时,他们只是崩溃了。现在大多数应用程序都已解决此问题,因此我们应该使用 FileProvider 解决方案。

于 2016-11-18T10:31:21.703 回答
199

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

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

编辑:当您包含 URI 时,请Intent确保添加以下行:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

你很高兴。希望能帮助到你。

于 2017-08-18T07:58:15.870 回答
174

如果您的应用程序面向 API 24+,并且您仍然希望/需要使用 file:// 意图,则可以使用 hacky 方式禁用运行时检查:

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:// 方案打开相同的应用程序提供的应用程序少得多。我不想通过限制我的应用程序的功能来为谷歌的设计缺陷买单。

谷歌希望开发人员使用内容方案,但系统并没有为此做好准备,多年来应用程序一直使用文件而不是“内容”,文件可以编辑和保存回来,而通过内容方案提供的文件不能(可以他们?)。

于 2017-02-24T11:21:58.077 回答
90

如果您targetSdkVersion是 24 或更高,则不能在 Android 7.0+ 设备上使用file: UriIntents

您的选择是:

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

(来自这个示例项目

于 2016-07-05T12:42:59.933 回答
50

首先,您需要向您的 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 并选择创建 xml 资源选项后按 Alt + Enter)

接下来在 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 卡的根文件夹。我已经测试了这段代码,它确实有效。

于 2016-08-12T19:45:54.640 回答
35

我的解决方案是将文件路径“Uri.parse”为字符串,而不是使用 Uri.fromFile()。

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 SDKs up to 29.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

似乎 fromFile() 使用文件指针,我想当内存地址暴露给所有应用程序时,它可能是不安全的。但是文件路径字符串永远不会伤害任何人,因此它可以在不抛出 FileUriExposedException 的情况下工作。

在 API 级别 9 到 29 上测试!成功打开文本文件以在另一个应用程序中进行编辑。根本不需要 FileProvider,也不需要 Android 支持库。这在 API 级别 30(Android 11)或更高版本上无法正常工作,因为 getExternalStorageDirectory() 已被弃用。

于 2018-10-22T09:15:12.410 回答
26

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

这有助于我修复来自外部存储的文件的崩溃,希望这将有助于一些与我有相同问题的人:)

于 2017-01-15T11:59:26.550 回答
25

只需将以下代码粘贴到活动中onCreate()

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

它将忽略 URI 暴露。

快乐编码:-)

于 2018-09-20T09:15:48.963 回答
24

只需将以下代码粘贴到 Activity 中onCreate()

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

它将忽略 URI 暴露。

于 2018-01-16T06:50:25.373 回答
19

使用 fileProvider 是要走的路。但是您可以使用这个简单的解决方法:

警告:它将在下一个 Android 版本中修复 - https://issuetracker.google.com/issues/37122890#comment4

代替:

startActivity(intent);

经过

startActivity(Intent.createChooser(intent, "Your title"));
于 2017-02-08T03:02:29.943 回答
13

我使用了上面给出的 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);
于 2016-12-11T16:05:31.140 回答
12

只需将以下代码粘贴到 Activity 中onCreate()

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

它将忽略 URI 暴露。

于 2019-06-10T06:00:08.690 回答
10

这是我的解决方案:

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>

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 上进行了测试,并且成功了。

于 2019-07-11T09:04:54.250 回答
8

在 onCreate 中添加这两行

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..."));
于 2019-03-13T09:17:38.973 回答
4

要从服务器下载 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>
于 2018-07-28T11:57:58.967 回答
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实际上是在设置getIntent().addFlags而不是对我新声明的 playIntent 进行操作。

于 2018-09-17T12:59:37.117 回答
4
As of Android N, in order to work around this issue, you need to use the FileProvider API

这里有3个主要步骤,如下所述

第 1 步:清单条目

<manifest ...>
    <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>
</manifest>

第 2 步:创建 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>

第 3 步:代码更改

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);
于 2020-01-18T13:16:17.060 回答
4

我花了将近一天的时间试图弄清楚为什么我会得到这个异常。经过一番努力,这个配置完美运行(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>

文件路径.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
}

我在这里解释整个过程。

于 2020-06-03T09:00:08.050 回答
1

@Pkosta 's answer is one way of doing this.

Besides using FileProvider, you can also insert the file into MediaStore (especially for image and video files), because files in MediaStore are accessible to every app:

The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.

For example, you can insert a video file to MediaStore like this:

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

contentUri is like content://media/external/video/media/183473, which can be passed directly to Intent.putExtra:

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

This works for me, and save the hassles of using FileProvider.

于 2019-01-25T12:02:23.870 回答
1

我知道这是一个很老的问题,但这个答案是为未来的观众准备的。所以我遇到了类似的问题,经过研究,我找到了这种方法的替代方法。

您在此处的意图例如:从您在 Kotlin 中的路径查看您的图像

 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)代替图像,在我的情况下,它工作得很好

于 2019-05-29T06:05:53.913 回答
1

我使用了这种方法,因此 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);
}
于 2019-05-28T07:31:21.507 回答
1

如果android版本> 24,我刚刚完成了以下操作

File fl = new File(url);
    Uri uri = Uri.fromFile(fl);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (android.os.Build.VERSION.SDK_INT>=24)
    {
        Context context = getApplicationContext();
        uri = FileProvider.getUriForFile(
                context,
                context.getApplicationContext()
                        .getPackageName() + ".provider", fl);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(uri, mimetype);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

按照这个链接然后https://medium.com/@ali.muzaffar/what-is-android-os-fileuriexposedexception-and-what-you-can-do-about-it-70b9eb17c6d0#.54odzsnk4

于 2021-03-23T07:13:20.977 回答
0

我想从应用程序的范围存储中共享图像,这就是我遇到此异常的地方。搜索了几个小时,最后,我找到了这个博客

它有点长,所以我在这里分享要点,但我会建议你仔细阅读。

底线是您不能从应用程序的范围存储中共享任何内容。同样在 Android 12 中,intent 选择器底部对话框会显示您正在共享的图像的预览,顺便说一句,这非常酷,但它无法从作用域存储 URI 加载预览。

解决方案是在缓存目录中创建您“打算”共享的文件的副本。

val cachePath = File(externalCacheDir, "my_images/")
cachePath.mkdirs()
val bitmap = loadImageFromStorage(currentQuote.bookId)
val file = File(cachePath, "cache.png")
val fileOutputStream: FileOutputStream
try {
    fileOutputStream = FileOutputStream(file)
    bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
    fileOutputStream.flush()
    fileOutputStream.close()
} catch (e: FileNotFoundException) {
    e.printStackTrace()
} catch (e: IOException) {
    e.printStackTrace()
}
val cacheImageUri: Uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
            
val intent = Intent(Intent.ACTION_SEND).apply {
    clipData = ClipData.newRawUri(null, cacheImageUri)
    putExtra(Intent.EXTRA_STREAM, cacheImageUri)
    type = "image/ *"
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, null))
            

这就是我从范围存储加载文件的方式

fun Context.loadImageFromStorage(path: String): Bitmap? {
    try {
        val file = getFile(path)
        val bitmap = BitmapFactory.decodeStream(FileInputStream(file))
        return bitmap
    } catch (e: Exception) {
        e.printStackTrace()

        //Returning file from public storage in case the file is stored in public storage 
        return BitmapFactory.decodeStream(FileInputStream(File(path)))
    }
    
    return null
}


fun Context.getFile(path: String): File? {
    val cw = ContextWrapper(this)
    val directory = cw.getDir("image_dir", Context.MODE_PRIVATE)
    if (!directory.exists())
        directory.mkdir()
    try {
        val fileName = directory.absolutePath + "/" + path.split("/").last()
        return File(fileName)
    } catch (e: Exception) {
        e.printStackTrace()
    }
    
    return null
}

最后,不要忘记更新您的provider_paths.xml文件

<external-cache-path name="external_cache" path="." />

<external-cache-path name="external_files" path="my_images/"/>
于 2021-07-17T06:14:48.050 回答
-1

试试这个解决方案

将这些权限放在清单中

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.CAMERA" />

意图捕捉图像

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

在活动结果中获取捕获的图像

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

获取图像 URI 的方法

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }
于 2019-12-30T13:29:13.947 回答
-1

这有效

 val uri = if (Build.VERSION.SDK_INT < 24) Uri.fromFile(file) else Uri.parse(file.path)
                val shareIntent = Intent().apply {
                    action = Intent.ACTION_SEND
                    type = "application/pdf"
                    putExtra(Intent.EXTRA_STREAM, uri)
                    putExtra(
                        Intent.EXTRA_SUBJECT,
                        "Purchase Bill..."
                    )
                    putExtra(
                        Intent.EXTRA_TEXT,
                        "Sharing Bill purchase items..."
                    )
                }
                startActivity(Intent.createChooser(shareIntent, "Share Via"))
于 2021-04-09T09:01:44.650 回答
-1

只需让它忽略 URI Exposure... 在创建后添加它

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build()); 
于 2019-11-07T11:26:38.213 回答
-1

Xamarin.Android

注意:路径xml/provider_paths.xml (.axml) 无法解析,即使在Resources下创建了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);
于 2018-09-26T13:18:13.553 回答