android.os.FileUriExposedException:文件:///存储/仿真/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.异常:文件:///存储/模拟/0/test.txt暴露超出应用程序通过Intent.get数据()

编辑:

针对Android Nougat时,不再允许file:// URI。我们应该使用content:// URI代替。但是,我的应用程序需要在根目录中打开文件。有什么想法吗?

583281 次浏览

如果你的targetSdkVersion是24或更高,您不能在Android 7.0+设备上使用#3中的#1#2值

您的选择是:

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

(从这个示例项目

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

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

  • <application>标签下的AndroidManifest.xml中添加FileProvider<provider>标签。为android:authorities属性指定唯一权限以避免冲突,导入的依赖项可能会指定${applicationId}.provider和其他常用权限。
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"...<application...<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/provider_paths" /></provider></application></manifest>
  • 然后在res/xml文件夹中创建一个provider_paths.xml文件。如果文件夹尚不存在,则可能需要创建该文件夹。文件的内容如下所示。它描述了我们希望以external_files的名称共享对根文件夹(path=".")的外部存储的访问权限。
<?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);

请参阅已解释的完整代码和解决方案这里。

首先,您需要将提供商添加到您的Android清单

  <application...><activity>....</activity><providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.your.package.fileProvider"android:grantUriPermissions="true"android:exported="false"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider></application>

现在在xml资源文件夹中创建一个文件(如果使用android Studio,您可以在突出显示后点击Alt+enterfile_paths并选择创建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卡的根文件夹。我已经测试了这段代码,它确实有效。

除了使用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是诊断工具,不应该用于解决此问题。当我一年前发布这个答案时,许多应用程序只能接收文件uris。当我尝试向他们发送FileProvider uri时,它们就崩溃了。现在大多数应用程序中都修复了这个问题,所以我们应该使用FileProvider解决方案。

我使用了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);

@palash k答案是正确的,适用于内部存储文件,但在我的情况下,我想从外部存储打开文件,我的应用程序在从外部存储打开文件时崩溃,如SD卡和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-pathname="root"path="/" />
</paths>

在java类中(没有变化,因为接受的答案只是一个小编辑)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

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

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

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

替换:

startActivity(intent);

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

如果您的应用程序以API 24+为目标,并且您仍然想要/需要使用file://intents,您可以使用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.*/

问题是我的应用程序不是蹩脚的,而是不想通过使用内容://意图而被瘫痪,这些意图是许多应用程序无法理解的。例如,使用内容://方案打开mp3文件提供的应用程序比打开相同的文件://方案提供的应用程序少得多。我不想通过限制我的应用程序的功能来为谷歌的设计错误付出代价。

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

如果targetSdkVersion高于24,则使用文件提供者授予访问权限。

创建一个xml文件(Path: 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中添加提供商

    <providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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);

你可以走了。希望对你有帮助。

只需将以下代码粘贴到活动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
<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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实际上是设置getIntent().addFlags,而不是在我新声明的playIntent上操作。

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

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

它将忽略URI暴露。

快乐编码:-)

安卓手机

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

备注:xml需要大写,所以资源/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);

我的解决方案是“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()已被弃用。

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

除了使用FileProvider,您还可以将文件插入MediaStore(尤其是图像和视频文件),因为MediaStore中的文件可供每个应用程序访问:

MediaStore主要针对视频、音频和图像MIME类型,但从Android 3.0(API级别11)开始,它也可以存储非媒体类型(有关更多信息,请参阅MediaStore. Files)。可以使用scanFile()将文件插入MediaStore,然后将适合共享的content://style Uri传递给提供的onScanCompl的()回调。请注意,一旦添加到系统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的麻烦。

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

我把这个方法,所以Imageuri路径很容易得到的内容。

enter code herepublic 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);}

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

你的意图例如:从静态编程语言的路径中查看图像

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

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

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

它将忽略URI暴露。

这里是我的解决方案:

Manifest.xml

<applicationandroid: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">
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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上进行了测试,它的成功工作。

简单地让它忽略URI暴露…在创建后添加它

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

试试这个解决方案

把这些许可证写好

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

在一次激活结果中获取捕获图像

@Overrideprotected 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 BITMAPUri 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);}
As of Android N, in order to work around this issue, you need to use the FileProvider API

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

步骤1:清单输入

<manifest ...><application ...><providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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 Approachinstall.setDataAndType(Uri.fromFile(file), mimeType);// End Old approach// New ApproachUri apkURI = FileProvider.getUriForFile(context,context.getApplicationContext().getPackageName() + ".provider", file);install.setDataAndType(apkURI, mimeType);install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);// End New Approachcontext.startActivity(install);

我花了将近一天的时间试图弄清楚为什么我得到了这个异常。经过大量的斗争,这个配置工作得很好(静态编程语言):

AndroidManifest.xml

<providerandroid:name="androidx.core.content.FileProvider"android:authorities="com.lomza.moviesroom.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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}

我解释了整个过程这里

我刚刚完成以下如果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

这招管用

 val uri = if (Build.VERSION.SDK_INT < 24) Uri.fromFile(file) else Uri.parse(file.path)val shareIntent = Intent().apply {action = Intent.ACTION_SENDtype = "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"))

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

它有点长,所以我在这里分享要点,但我会推荐你去经历它。

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

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

val cachePath = File(externalCacheDir, "my_images/")cachePath.mkdirs()val bitmap = loadImageFromStorage(currentQuote.bookId)val file = File(cachePath, "cache.png")val fileOutputStream: FileOutputStreamtry {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 storagereturn 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/"/>