android 7.0 因为Uri.fromFile引起的FileUriExposedException异常

最近作者又碰到因为android 7.0 引起的兼容问题了。

在7.0以前的版本:
//创建临时图片
File photoOutputFile = SDPath.getFile("temp.jpg", SDPath.PHOTO_FILE_STR);
Uri photoOutputUri = Uri.fromFile(photoOutputFile);

这个file文件直接非常简单的转换成"file://XXX/XXX/XXX"的uri格式

7.0后的版本:

当把targetSdkVersion指定成24及之上并且在API>=24的设备上运行时。这种方式则会出现FileUriExposedException异常

android.os.FileUriExposedException:         file:///XXX exposed beyond app through ClipData.Item.getUri()
    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
    at android.net.Uri.checkFileUriExposed(Uri.java:2346)
    at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:8909)
    ...
原因

Android不再允许在app中把file://Uri暴露给其他app,包括但不局限于通过Intent或ClipData 等方法。

原因在于使用file://Uri会有一些风险,比如:

  • 文件是私有的,接收file://Uri的app无法访问该文件。
  • 在Android6.0之后引入运行时权限,如果接收file://Uri的app没有申请READ_EXTERNAL_STORAGE权限,在读取文件时会引发崩溃。

因此,google提供了FileProvider,使用它可以生成content://Uri来替代file://Uri

解决方案
首先声明: com.alex.demo 为项目的包名,以下需要包名的地方替换即可

第一步、

 在AndroidManifest.xml中加上摄像头、读写磁盘的权限,如下

[html]  view plain  copy
  1. <uses-permission android:name="android.permission.CAMERA" />  
  2. <uses-permission android:name="android.permission.RECORD_AUDIO" />  
  3. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
  4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  

第二步、

 在AndroidManifest.xml中加上自定义权限的ContentProvider,如下

[html]  view plain  copy
  1. <provider  
  2.             android:name="android.support.v4.content.FileProvider"  
  3.             android:authorities="com.alex.demo.FileProvider"  
  4.             android:exported="false"  
  5.             android:grantUriPermissions="true">  
  6.             <meta-data  
  7.                 android:name="android.support.FILE_PROVIDER_PATHS"  
  8.                 android:resource="@xml/file_paths" />  
  9.         </provider>  

[html]  view plain  copy
  1. android:authorities="com.alex.demo.FileProvider" 自定义的权限  
[html]  view plain  copy
  1. android:exported="false" 是否设置为独立进程  
[html]  view plain  copy
  1. android:grantUriPermissions="true" 是否拥有共享文件的临时权限  
[html]  view plain  copy
  1. android:resource="@xml/external_storage_root" 共享文件的文件根目录,名字可以自定义  

第三步、

在项目res目录下创建一个xml文件夹,里面创建一个file_paths.xml文件,上一步定义的什么名称,这里就什么名称,如图:


[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <paths>  
  3.     <external-path  
  4.         name="external_storage_root"  
  5.         path="." />  
  6. </paths>  

[html]  view plain  copy
  1. name="external_storage_root" 这个是根目录名称,可以自定义  

好了,基本工作准备好,下面开始具体的使用吧


1、调用系统相机

[html]  view plain  copy
  1. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);  
  2.             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
  3.             String filePath = ShowConfig.getCacheFolderPath() + File.separator + ".temp.jpg";  
  4.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {  
  5.                 mOriginUri = FileProvider.getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider",  
  6.                         new File(filePath));  
  7.             } else {  
  8.                 mOriginUri = Uri.fromFile(new File(filePath));  
  9.             }  
  10.             intent.putExtra(MediaStore.EXTRA_OUTPUT, mOriginUri);  
  11.   
  12.             if(mAttachedFragment == null)  
  13.                 mAttachedActivity.startActivityForResult(intent, REQUEST_CROP_CAM_IMG_CODE);  
  14.             else  
  15.                 mAttachedFragment.startActivityForResult(intent, REQUEST_CROP_CAM_IMG_CODE);  
2、调用系统播放器播放视频

[html]  view plain  copy
  1. Intent intent = new Intent();  
  2.             intent.setAction(android.content.Intent.ACTION_VIEW);  
  3.             File file = FileUtils.createFile(mSelectedVideoPath);  
  4.             Uri uri;  
  5.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {  
  6.                 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
  7.                 Uri contentUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".FileProvider", file);  
  8.                 intent.setDataAndType(contentUri, "video/*");  
  9.             } else {  
  10.                 uri = Uri.fromFile(file);  
  11.                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  12.                 intent.setDataAndType(uri, "video/*");  
  13.             }  
  14.   
  15.             startActivity(intent);  
3、切图
[html]  view plain  copy
  1. Intent intent = new Intent("com.android.camera.action.CROP");  
  2.                 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
  3.                 intent.setDataAndType(uri, "image/*");  
  4.                 Uri customUri = null;  
  5.                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {  
  6.                     customUri = FileProvider.getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider",  
  7.                             new File(mLocalAvatarImagePath));  
  8.                 } else {  
  9.                     customUri = Uri.fromFile(new File(mLocalAvatarImagePath));  
  10.                 }  
  11.                 intent.putExtra("output", customUri);  
  12.                 intent.putExtra("crop", "true");  
  13.                 intent.putExtra("aspectX", 1);  // 裁剪框比例  
  14.                 intent.putExtra("aspectY", 0.75);  
  15. //            intent.putExtra("outputX", mOutputWidth);  // 输出图片大小  
  16. //            intent.putExtra("outputY", mOutputHeight);  
  17.                 intent.putExtra("scale", true);  // 去黑边  
  18.                 intent.putExtra("scaleUpIfNeeded", true);  // 去黑边  
  19.                 if(mAttachedFragment == null)  
  20.                     mAttachedActivity.startActivityForResult(intent, REQUEST_UPLOAD_IMG_CODE);  
  21.                 else  
  22.                     mAttachedFragment.startActivityForResult(intent, REQUEST_UPLOAD_IMG_CODE);  

4、用系统安装器安装APK

[html]  view plain  copy
  1. Uri fileUri = null;  
  2.                             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {  
  3.                                 fileUri = FileProvider.getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider",  
  4.                                         new File(filePath));  
  5.                             } else {  
  6.                                 fileUri = Uri.fromFile(new File(filePath));  
  7.                             }  
  8.                             Intent installIntent = new Intent(Intent.ACTION_VIEW);  
  9.                             installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  10.                             installIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
  11.                             installIntent.setAction(Intent.ACTION_VIEW);  
  12.                             installIntent.setDataAndType(fileUri,  
  13.                                     "application/vnd.android.package-archive");  
  14.                             context.startActivity(installIntent);  
我所碰到的异常
1、java.lang.SecurityException: Provider must not be exported
解决方案:android:exported必须设置成false
2、Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
解决方案:AndroidManifest.xml处的android:authorities必须跟mActivity.getPackageName() + ".fileprovider"一样

此文章是我在使用过程中遇到问题时所搜到的,并且把两篇文章的重要部分综合了一下,方便遇到同样的问题能快速的处理。如果大家在使用的过程中还有遇到其他的 问题以及解决办法,欢迎留言。我把这两篇文章的原稿地址留在下面;

http://blog.csdn.net/msn465780/article/details/59058088?locationNum=8&fps=1;

http://www.jianshu.com/p/55b817530fa3;


评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值