当我尝试打开文件时,应用程序崩溃了。它在 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
类来授予对特定文件或文件夹的访问权限,以便其他应用程序可以访问它们。我们创建自己的继承 FileProvider
的类,以确保我们的 FileProvider 不会与如 here 所述的导入依赖项中声明的 FileProvider 冲突。
将 file://
URI 替换为 content://
URI 的步骤:
在
<?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>
然后在 res/xml 文件夹中创建一个 provider_paths.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);
请参考已解释的完整代码和解决方案here.
除了使用 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 解决方案。
VmPolicy
来禁用它。
StrictMode.enableDefaults();
,我只在我的开发版本上运行它可以防止发生这种崩溃 - 所以我现在有一个崩溃的生产应用程序,但它在开发时不会崩溃。所以基本上,在这里启用诊断工具隐藏了一个严重的问题。感谢@hqzxzwb 帮助我揭开这个谜团。
如果 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);
编辑:当您使用 Intent
包含 URI 时,请确保添加以下行:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
你很高兴。希望能帮助到你。
如果您的应用程序针对 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:// 方案打开相同的应用程序提供的应用程序要少得多。我不想通过限制我的应用程序的功能来为谷歌的设计缺陷买单。
谷歌希望开发人员使用内容方案,但系统并没有为此做好准备,多年来应用程序一直使用文件而不是“内容”,文件可以编辑和保存回来,而通过内容方案提供的文件不能(可以他们?)。
ContentResolver
同时具有 openInputStream()
和 openOutputStream()
。这样做的一个不太麻烦的方法是只使用 configure the VM rules yourself,而不启用 file
Uri
规则。
如果您的 targetSdkVersion
是 24 或更高,you can not use file:
Uri
values in Intents
on Android 7.0+ devices。
您的选择是:
将您的 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);
(来自 this sample project)
/system
分区上的文件一起使用时会发生什么?每个应用程序都应该能够在没有 root 的情况下访问此分区。
/system
中的所有内容都是世界可读的。话虽如此,我的猜测是你仍然会得到这个例外。我怀疑他们只是在检查方案,而不是试图确定文件是否真的是世界可读的。但是,FileProvider
不会帮助您,因为您无法教它从 /system
发球。您可以 create a custom strategy for my StreamProvider
或推出自己的 ContentProvider
来解决问题。
/data
, /system
),因为这个“好变化”。
首先,您需要向您的 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>
此示例适用于外部路径,您可以参考 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();
}
编辑:我在 file_paths 中添加了 sd 卡的根文件夹。我已经测试了这段代码,它确实有效。
String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
另外,我建议任何寻找答案的人先通读 FileProvider 并了解您在此处处理的 Android N 及更高版本中的文件权限问题。有内部存储与外部存储以及常规文件路径与缓存路径的选项。
java.lang.IllegalArgumentException: Failed to find configured root ...
,唯一有效的是 xml 文件上的 <files-path path="." name="files_root" />
而不是 <external-path ...
。我的文件保存在内部存储器中。
我的解决方案是将文件路径“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() 已被弃用。
FileProvider
及其 getUriForFile()
方法。
@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)
这有助于我修复来自外部存储的文件的崩溃,希望这将有助于一些与我有相同问题的人:)
<root-path
的信息?它正在工作。 <external-path path="Android/data/${applicationId}/" name="files_root" />
对从外部存储打开的文件没有影响。
Android/data/${applicationId}/
。
只需将以下代码粘贴到活动 onCreate()
中。
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
快乐编码:-)
只需将以下代码粘贴到 Activity onCreate()
中:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
使用 fileProvider 是要走的路。但是您可以使用这个简单的解决方法:
警告:它将在下一个 Android 版本中修复 - https://issuetracker.google.com/issues/37122890#comment4
代替:
startActivity(intent);
经过
startActivity(Intent.createChooser(intent, "Your title"));
我使用了上面给出的 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);
只需将以下代码粘贴到 Activity onCreate()
中:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
这是我的解决方案:
在 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 上进行了测试,并且成功了。
在 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..."));
要从服务器下载 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>
@xml/provider_paths
是什么?
我不知道为什么,我所做的一切都与 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 进行操作。
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);
<root-path name="root" path="/" />
<root-path>
,我也不会这样做
我花了将近一天的时间试图弄清楚为什么我会得到这个异常。经过一番努力,这个配置完美运行(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
}
我解释了整个过程here。
如果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);
@Pkosta 的回答是这样做的一种方式。
除了使用 FileProvider
,您还可以将文件插入 MediaStore
(尤其是图像和视频文件),因为每个应用程序都可以访问 MediaStore 中的文件:
MediaStore 主要针对视频、音频和图像 MIME 类型,但从 Android 3.0(API 级别 11)开始,它也可以存储非媒体类型(有关更多信息,请参阅 MediaStore.Files)。可以使用 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
的麻烦。
我使用了这种方法,因此 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);
}
我知道这是一个很老的问题,但这个答案是为未来的观众准备的。所以我遇到了类似的问题,经过研究,我找到了这种方法的替代方法。
您在此处的意图例如:从您在 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)代替图像,在我的情况下,它工作得很好
我想从应用程序的范围存储中共享图像,这就是我遇到此异常的地方。搜索了几个小时,最后我找到了 this blog。
它有点长,所以我在这里分享要点,但我会建议你仔细阅读。
底线是您不能从应用程序的范围存储中共享任何内容。同样在 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/"/>
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);
试试这个解决方案
将这些权限放在清单中
<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);
}
这有效
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"))
只需让它忽略 URI Exposure... 在创建后添加它
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
不定期副业成功案例分享
(Build.VERSION.SDK_INT > M)
条件是无用的。FileProvider
,否则使用android:name="android.support.v4.content.FileProvider"
。请参阅developer.android.com/reference/android/support/v4/content/…