最近作者又碰到因为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中加上摄像头、读写磁盘的权限,如下
< uses-permission android:name = "android.permission.CAMERA" /> < uses-permission android:name = "android.permission.RECORD_AUDIO" /> < uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE" /> < uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" />
第二步、
在AndroidManifest.xml中加上自定义权限的ContentProvider,如下
< provider android:name = "android.support.v4.content.FileProvider" android:authorities = "com.alex.demo.FileProvider" android:exported = "false" android:grantUriPermissions = "true" > < meta-data android:name = "android.support.FILE_PROVIDER_PATHS" android:resource = "@xml/file_paths" /> </ provider >
android:authorities = "com.alex.demo.FileProvider" 自定义的权限
android:exported = "false" 是否设置为独立进程
android:grantUriPermissions = "true" 是否拥有共享文件的临时权限
android:resource = "@xml/external_storage_root" 共享文件的文件根目录,名字可以自定义
第三步、
在项目res目录下创建一个xml文件夹,里面创建一个file_paths.xml文件,上一步定义的什么名称,这里就什么名称,如图:
<? xml version = "1.0" encoding = "utf-8" ?> < paths > < external-path name = "external_storage_root" path = "." /> </ paths >
name = "external_storage_root" 这个是根目录名称,可以自定义
好了,基本工作准备好,下面开始具体的使用吧
1、调用系统相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); String filePath = ShowConfig .getCacheFolderPath() + File.separator + ".temp.jpg"; if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.N) { mOriginUri = FileProvider .getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider", new File(filePath)); } else { mOriginUri = Uri .fromFile(new File(filePath)); } intent.putExtra(MediaStore.EXTRA_OUTPUT, mOriginUri); if(mAttachedFragment == null) mAttachedActivity.startActivityForResult(intent, REQUEST_CROP_CAM_IMG_CODE); else mAttachedFragment.startActivityForResult(intent, REQUEST_CROP_CAM_IMG_CODE);
2、调用系统播放器播放视频
Intent intent = new Intent(); intent.setAction(android.content.Intent.ACTION_VIEW); File file = FileUtils .createFile(mSelectedVideoPath); Uri uri; if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider .getUriForFile(this, getApplicationContext().getPackageName() + ".FileProvider", file); intent.setDataAndType(contentUri, "video/*"); } else { uri = Uri .fromFile(file); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(uri, "video/*"); } startActivity(intent);
3、切图
Intent intent = new Intent("com.android.camera.action.CROP"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(uri, "image/*"); Uri customUri = null ; if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.N) { customUri = FileProvider .getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider", new File(mLocalAvatarImagePath)); } else { customUri = Uri .fromFile(new File(mLocalAvatarImagePath)); } intent.putExtra("output", customUri); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1); // 裁剪框比例 intent.putExtra("aspectY", 0.75); // intent.putExtra("outputX", mOutputWidth); // 输出图片大小 // intent.putExtra("outputY", mOutputHeight); intent.putExtra("scale", true); // 去黑边 intent.putExtra("scaleUpIfNeeded", true); // 去黑边 if(mAttachedFragment == null) mAttachedActivity.startActivityForResult(intent, REQUEST_UPLOAD_IMG_CODE); else mAttachedFragment.startActivityForResult(intent, REQUEST_UPLOAD_IMG_CODE);
4、用系统安装器安装APK
Uri fileUri = null ; if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.N) { fileUri = FileProvider .getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider", new File(filePath)); } else { fileUri = Uri .fromFile(new File(filePath)); } Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); installIntent.setAction(Intent.ACTION_VIEW); installIntent.setDataAndType(fileUri, "application/vnd.android.package-archive"); 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;