android.os.FileUriExposedException: file:///storage/emulated/0/test.txt 通过 Intent.getData() 暴露在应用之外
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
当我尝试打开文件时,应用程序崩溃了。它在 Android 牛轧糖下工作,但在 Android 牛轧糖上它崩溃了。它仅在我尝试从 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 exposed beyond app through
Intent.getData()
编辑:
定位 Android 牛轧糖时,不再允许 file://
URI。我们应该改用 content://
URI。但是,我的应用程序需要打开根目录中的文件。有什么想法吗?
如果您的 targetSdkVersion
为 24 或更高,you can not use file:
Uri
values in Intents
on Android 7.0+ devices。
您的选择是:
将您的 targetSdkVersion
降到 23 或更低,或者
将您的内容放在内部存储上,然后use 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)
如果您的 targetSdkVersion >= 24
,那么我们必须使用 FileProvider
class 来授予对特定文件或文件夹的访问权限,以便其他应用程序可以访问它们。我们创建自己的 class 继承 FileProvider
以确保我们的 FileProvider 不与在导入的依赖项中声明的 FileProvider 冲突,如 here.
所述
将 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
...
<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 让系统打开您的文件,您可能需要添加以下代码行:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
完整代码和解决方案请参考已说明here.
首先您需要向您的 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 工作室,您可以在突出显示 file_paths 和 select 后按 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>
此示例适用于外部路径,您可以参考 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卡的根文件夹。我已经测试了这段代码,它确实有效。
除了使用 FileProvider
的解决方案外,还有另一种解决方法。简单地说
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
在 Application.onCreate()
中。这样VM就忽略了文件URI
exposure.
方法
builder.detectFileUriExposure()
启用文件公开检查,如果我们不设置 VmPolicy,这也是默认行为。
我遇到了一个问题,如果我使用 content://
URI
发送一些东西,一些应用程序就是无法理解它。并且不允许降级 target SDK
版本。在这种情况下,我的解决方案很有用。
更新:
如评论中所述,StrictMode 是诊断工具,不应该用于此问题。一年前我发这个答案的时候,很多app只能接收File 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 的答案是正确的并且适用于内部存储文件,但就我而言,我也想从外部存储打开文件,当从外部存储(如 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 class(没有变化,因为接受的答案只是一个小的编辑)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
这帮助我解决了外部存储文件崩溃的问题,希望这能帮助那些和我有同样问题的人
:)
使用 fileProvider 是可行的方法。
但是您可以使用这个简单的解决方法:
WARNING: It will be fixed in next Android release -
https://issuetracker.google.com/issues/37122890#comment4
替换:
startActivity(intent);
来自
startActivity(Intent.createChooser(intent, "Your title"));
如果您的应用面向 API 24+,并且您仍然 want/need 使用 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.
*/
问题是我的应用程序不是很差劲,而是不想因为使用 content:// intents 而瘫痪,很多应用程序都无法理解。例如,使用 content:// 方案打开 mp3 文件比使用 file:// 方案打开相同的应用程序提供的应用程序少得多。我不想通过限制我的应用程序的功能来为 Google 的设计错误付出代价。
Google 希望开发者使用 content scheme,但系统并没有为此做好准备,多年来应用程序一直使用文件而不是 "content",文件可以编辑并保存回来,而通过内容方案提供的文件不能(可以吗?)。
如果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>
中添加一个提供商
<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);
一切顺利。希望对你有帮助。
只需将下面的代码粘贴到 Activity onCreate()
:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
要从服务器下载 pdf,请在您的服务中添加以下代码 class。希望对您有所帮助。
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>
我不知道为什么,我做的每件事都和 Pkosta 完全一样 ( ),但一直出现错误:
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 进行操作。
只需将下面的代码粘贴到 activity onCreate()
.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
编码愉快:-)
Xamarin.Android
注:路径xml/provider_paths.xml(.axml)不能'得到解决,即使在 xml 文件夹下 Resources (也许它可以放在现有位置,如 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);
我的解决方案是 '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中的文件可供每个应用访问:
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.
例如,您可以像这样将视频文件插入 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 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,在我的例子中,它工作得很好
只需将下面的代码粘贴到 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 上进行了测试,结果很成功。
干脆让它忽略URI Exposure...
在创建后添加它
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);
}
在 ONACTIVITYRESULT 中获取捕获的图像
@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);
}
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);
我花了将近一天的时间来弄清楚为什么会出现此异常。经过大量努力,此配置完美运行 (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
}
全程讲解here.
如果 android version > 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);
这个有效
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"))
我想从应用程序的范围存储中共享图像,这就是我遇到此异常的地方。搜索了几个小时,最后,我找到了 this blog。
它有点长,所以我在这里分享要点,但我会建议你通读它。
最重要的是,您不能从应用程序的分区存储中共享任何内容。同样在 Android 12 中,意图选择器底部对话框会显示您正在共享的图像的预览,顺便说一下,这非常酷,但它无法从范围存储 URI 加载预览。
解决方案是为您 'intent' 创建文件副本以在缓存目录中共享。
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/"/>
当我尝试打开文件时,应用程序崩溃了。它在 Android 牛轧糖下工作,但在 Android 牛轧糖上它崩溃了。它仅在我尝试从 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 exposed beyond app through Intent.getData()
编辑:
定位 Android 牛轧糖时,不再允许 file://
URI。我们应该改用 content://
URI。但是,我的应用程序需要打开根目录中的文件。有什么想法吗?
如果您的 targetSdkVersion
为 24 或更高,you can not use file:
Uri
values in Intents
on Android 7.0+ devices。
您的选择是:
将您的
targetSdkVersion
降到 23 或更低,或者将您的内容放在内部存储上,然后use
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)
如果您的 targetSdkVersion >= 24
,那么我们必须使用 FileProvider
class 来授予对特定文件或文件夹的访问权限,以便其他应用程序可以访问它们。我们创建自己的 class 继承 FileProvider
以确保我们的 FileProvider 不与在导入的依赖项中声明的 FileProvider 冲突,如 here.
将 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
...
<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 让系统打开您的文件,您可能需要添加以下代码行:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
完整代码和解决方案请参考已说明here.
首先您需要向您的 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 工作室,您可以在突出显示 file_paths 和 select 后按 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>
此示例适用于外部路径,您可以参考 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卡的根文件夹。我已经测试了这段代码,它确实有效。
除了使用 FileProvider
的解决方案外,还有另一种解决方法。简单地说
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
在 Application.onCreate()
中。这样VM就忽略了文件URI
exposure.
方法
builder.detectFileUriExposure()
启用文件公开检查,如果我们不设置 VmPolicy,这也是默认行为。
我遇到了一个问题,如果我使用 content://
URI
发送一些东西,一些应用程序就是无法理解它。并且不允许降级 target SDK
版本。在这种情况下,我的解决方案很有用。
更新:
如评论中所述,StrictMode 是诊断工具,不应该用于此问题。一年前我发这个答案的时候,很多app只能接收File 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 的答案是正确的并且适用于内部存储文件,但就我而言,我也想从外部存储打开文件,当从外部存储(如 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 class(没有变化,因为接受的答案只是一个小的编辑)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
这帮助我解决了外部存储文件崩溃的问题,希望这能帮助那些和我有同样问题的人 :)
使用 fileProvider 是可行的方法。 但是您可以使用这个简单的解决方法:
WARNING: It will be fixed in next Android release - https://issuetracker.google.com/issues/37122890#comment4
替换:
startActivity(intent);
来自
startActivity(Intent.createChooser(intent, "Your title"));
如果您的应用面向 API 24+,并且您仍然 want/need 使用 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.
*/
问题是我的应用程序不是很差劲,而是不想因为使用 content:// intents 而瘫痪,很多应用程序都无法理解。例如,使用 content:// 方案打开 mp3 文件比使用 file:// 方案打开相同的应用程序提供的应用程序少得多。我不想通过限制我的应用程序的功能来为 Google 的设计错误付出代价。
Google 希望开发者使用 content scheme,但系统并没有为此做好准备,多年来应用程序一直使用文件而不是 "content",文件可以编辑并保存回来,而通过内容方案提供的文件不能(可以吗?)。
如果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>
<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);
一切顺利。希望对你有帮助。
只需将下面的代码粘贴到 Activity onCreate()
:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
要从服务器下载 pdf,请在您的服务中添加以下代码 class。希望对您有所帮助。
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>
我不知道为什么,我做的每件事都和 Pkosta 完全一样 (
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 进行操作。
只需将下面的代码粘贴到 activity onCreate()
.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
它将忽略 URI 暴露。
编码愉快:-)
Xamarin.Android
注:路径xml/provider_paths.xml(.axml)不能'得到解决,即使在 xml 文件夹下 Resources (也许它可以放在现有位置,如 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);
我的解决方案是 '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中的文件可供每个应用访问:
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.
例如,您可以像这样将视频文件插入 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 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,在我的例子中,它工作得很好
只需将下面的代码粘贴到 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 上进行了测试,结果很成功。
干脆让它忽略URI Exposure... 在创建后添加它
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);
}
在 ONACTIVITYRESULT 中获取捕获的图像
@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);
}
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);
我花了将近一天的时间来弄清楚为什么会出现此异常。经过大量努力,此配置完美运行 (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
}
全程讲解here.
如果 android version > 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);
这个有效
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"))
我想从应用程序的范围存储中共享图像,这就是我遇到此异常的地方。搜索了几个小时,最后,我找到了 this blog。
它有点长,所以我在这里分享要点,但我会建议你通读它。
最重要的是,您不能从应用程序的分区存储中共享任何内容。同样在 Android 12 中,意图选择器底部对话框会显示您正在共享的图像的预览,顺便说一下,这非常酷,但它无法从范围存储 URI 加载预览。
解决方案是为您 'intent' 创建文件副本以在缓存目录中共享。
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/"/>