分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow
常规Uri有两种:
Android N 之前,这些uri可以传递到其他应用。
Android N 系统,Android 框架执行的 StrictMode,API 禁止向您的应用外公开 file://URI。
如果一项包含文件 URI 的 Intent 离开您的应用,应用会停止运行,并出现 FileUriExposedException异常。官方文档在Android 7.0 行为变更进行了详细说明
android.os.FileUriExposedException: file:///storage/emulated/0/Download/appName-2.3.0.apk exposed beyond app through Intent.getData()
若要在应用间共享文件,您应发送一项 content://URI(代替file://URI),并授予 URI 临时访问权限。
FileProvider这个类就是把一个文件File,转换为 content://URI的
FileProvider是ContentProvider子类,所以FileProvider的使用方法,和ContentProvider使用基本上是一样的
<provider android:name="android.support.v4.content.FileProvider" android:authorities="app的包名.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />provider>
注意:
authorities:app的包名.fileProvider
grantUriPermissions:必须是true,表示授予 URI 临时访问权限 ( readPermission, writePermission, and permission attributes)
exported:true: The provider is available to other applications. false: The provider is not available to other applications.
resource:自定义的xml文件(下面会介绍)
file_paths.xml 是这样的
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <root-path name="root" path="" /> <files-path name="files" path="" /> <cache-path name="cache" path="" /> <external-path name="external" path="" /> <external-files-path name="name" path="path" /> <external-cache-path name="name" path="path" />paths>
每个节点都支持两个属性:
name:给这个访问路径起个名字
path:需要临时授权访问的相对路径(.代表所有路径)
<external-path name="external" path="pics" />
path即为代表external-path目录下的子目录,目录为:Environment.getExternalStorageDirectory()/pics,其他同理。
现在要使用content://uri替代file://uri,那么,content://的uri如何定义呢?总不能使用文件路径吧,那不是骗自己么~
所以,需要一个虚拟的路径对文件路径进行映射,所以需要编写个xml文件,通过path以及xml节点确定可访问的目录,通过name属性来映射真实的文件路径。
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="DCIM" path="DCIM/camerademo" /> <external-path path="." name="external_storage_root" /> <external-path name="Pictures" path="Pictures/camerademo" /> <files-path name="private_files" path="images" /> <cache-path name="private_cache" path="images" /> <external-files-path name="external_files" path="Pictures" /> <external-cache-path name="external_cache" path="" />paths>
下面的例子使用SDCard,代码如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external" path="" />paths>
/** * 打开相机拍照 * * @param activity * @return */ public static void openCamera(Activity activity) { String filename = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.CHINA) .format(new Date()) + ".png"; File pictureFile = new File(Environment.getExternalStorageDirectory(), filename ); Intent mIntent = new Intent(); mIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Uri contentUri = FileProvider.getUriForFile(activity, "app的包名.fileProvider", pictureFile ); //拍照结果输出到这个uri对应的file中 mIntent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri); //对这个uri进行授权 //mIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //对这个uri进行授权 mIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } else { //拍照结果输出到这个uri对应的file中 mIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(pictureFile )); } mIntent.putExtra(MediaStore.Images.Media.ORIENTATION, 0); activity.startActivityForResult(mIntent, REQUEST_CAMERA_IMAGE); }
核心代码就这一行了~
Uri contentUri = FileProvider.getUriForFile(activity, "app的包名.fileProvider", pictureFile );
第二个参数就是我们配置的authorities,这个很正常了,总得映射到确定的ContentProvider吧~所以需要这个参数。
第三个参数是指定的文件File
生成的uri:
content://com.xuexuan.fileprovider/external/20171201-094017.png
可以看到格式为:content://authorities/paths中name的值/文件的相对路径
,即name隐藏了可存储的文件夹路径。
这里需要多说一点,Uri 的最终路径,与file和path有很大关系
如果file的路径完全包含path路径的,则显示path路径+文件的相对路径,如下:
path路径
file路径 /storage/emulated/0/customscamera/1534305129374.jpg
则 Uri 为:content://com.xuexuan.fileprovider/my_external/customscamera/1534305129374.jpg
如果file的路径不完全包含path路径的,则显示文件的绝对路径,如下:
path路径
file路径 /storage/emulated/0/customscamera/1534305129374.jpg
则 Uri 为:content://com.futureway.blealarm.fileProvider/profile_photo/storage/emulated/0/customscamera/1534305129374.jpg
上面代码有一行注释是:对这个uri进行授权。
授权有两种方式:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
2、使用函数进行授权和移除权限
grantUriPermission(String toPackage, Uri uri,
int modeFlags)函数来进行授权
revokeUriPermission(Uri uri, int modeFlags);移除权限
方式二较为麻烦,因为需要指定目标应用包名,很多时候并不清楚,所以需要通过PackageManager进行查找到所有匹配的应用,全部进行授权。不过更为稳妥~
List resInfoList = context.getPackageManager() .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, uri, flag);}
如果没有授权,可能会遇到这样的错误
java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{18570a 27107:com.google.android.packageinstaller/u0a26} (pid=27107, uid=10026) that is not exported from UID 10004
以下这两个问题,可参考这篇文章
为什么在Android 7 设备上,Intent的action为ACTION_IMAGE_CAPTURE,不进行授权,不会遇到Permission Denial的问题
为什么Android 4.4设备遇到权限问题,不通过addFlags这种方式解决
java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/DCIM/camerademo/20170226_110056248725175.jpg
在生成Uri 的时候,指定的文件所在的路径没有包含在path所指定的路径中
遇到这样一个到现在没有理解的问题,在小米6,MIUI 9.0 上面,使用下面的代码,进行图片的裁剪。
1、如果不使用下面的函数授权,就会出现java.lang.SecurityException: Permission Denial
的错误
2、使用了下面的函数授权,可以正常返回。但是返回的数据是null。但是在指定路径有裁剪后的照片输出。
有大神知道问题的原因,帮忙在评论里指导我一下,十分感谢
/*** * 裁剪图片 * @param activity Activity * @param uri 图片的Uri */ public static void cropPicture(Activity activity, Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.putExtra("crop", "true");// 才能出剪辑的小方框,不然没有剪辑功能,只能选取图片 intent.putExtra("aspectX", 1); // 放大缩小比例的X intent.putExtra("aspectY", 1);// 放大缩小比例的X 这里的比例为: 1:1 intent.putExtra("outputX", 120); //这个是限制输出图片大小 intent.putExtra("outputY", 120); intent.putExtra("return-data", false); //切图大小不足输出,无黑框 intent.putExtra("scale", true); intent.putExtra("scaleUpIfNeeded", true); Log.e("FaceUtil", "图片path:" + uri.toString()); File imageFile = new File(uri.getPath()); //输出图片的路径 File outputImageFile = new File(Environment.getExternalStorageDirectory(), "picture" + System.currentTimeMillis() / 1000 + ".jpg"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileProvider", imageFile); Uri outputUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileProvider", outputImageFile); intent.setDataAndType(contentUri, "image/*"); //使用函数授权,所有的包名 List resInfoList = activity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; activity.grantUriPermission(packageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } //裁剪后的图片,将被保存在这个uri中 intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); } else { //裁剪后的图片,将被保存在这个uri中 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputImageFile)); intent.setDataAndType(uri, "image/*"); } intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); activity.startActivityForResult(intent, REQUEST_CROP_IMAGE); }
参考:
Android 7.0 行为变更 通过FileProvider在应用间共享文件吧
https://developer.android.com/guide/topics/manifest/provider-element.html
Android7.0须知–应用间共享文件(FileProvider)
解决 Android N 7.0 上 报错:android.os.FileUriExposedException
FileProvider无法获取外置SD卡问题解决方案 | Failed to find configured root that contains
FileProvider 的使用(Failed to find configured root that contains/storage/emulated/0/DCIM/ )
Android中Uri和Path之间的转换
分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow