Android 7.0动态权限大总结
应公司项目需求,做了下Android 7.0适配。对于我们程序员,适配7.0主要就是对手机本地文件的Uri做转换处理。注意红色字体,意思也就是说对于http开头的等等,非手机存储中的文件就不用管了。Uri.parse(“package”) 这样的也不用管!!!!! 7.0的适配,就是对手机存储中的私有文件路径的保护,当系统发现你通过intent带走了一个uri,地址是本地的文件,就会限制的。其他的原理普及请搜索其他文章吧,此处略。 下面贴一下使用步骤和我的工具类。 一、需要修改当前module的AndroidManifest.xml文件,添加provider标签,映射路径。 <android:supportsRtl="true"> <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.myFileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/path_file"/> 文中红色部分是固定写法,官网就是这么说的,别乱试了。android:resource标签的值是main/res/xml/path_file.xml文件,即在 res下新建xml文件夹,再新建一个xml文件。 二、在res/xml下新建一个path_file.xml文件,文件名随便去,但是与上步骤一致即可。 <?xmlversion="1.0"encoding="utf-8"?> <resources> <paths> <!-- <files-path/>代表的根目录:Context.getFilesDir() <cache-path/>代表的根目录:getCacheDir() <external-path/>代表的根目录:Environment.getExternalStorageDirectory() <external-files-path/>代表的根目录:Context.getExternalFilesDir(String)Context.getExternalFilesDir(null). <external-cache-path/>代表的根目录:Context.getExternalCacheDir(). <root-path/>代表设备的根目录newFile("/"); --> <!--path=""代表根目录,也可以指定特定目录,name="camera_picture"是虚拟目录camera_picture--> <root-pathname="root"path=""/> <files-pathname="files"path=""/> <cache-pathname="cache"path=""/> <external-pathname="external"path=""/> <external-files-pathname="external_files"path=""/> <external-cache-pathname="external_cache"path=""/> </paths> </resources> 里面共有6个path标签,含义都写了,根据你的需要些对应的标签即可。比喻<external-path/>标签,里面的path=”"时,这个标签映射的就是外挂sd卡根目录了,name属性没什么用,来迷惑第三方应用的虚拟目录,来掩盖文件的真实路径。 三、工具类来了。 FileUriPermissionCompat.Java /** *@Author:duke *@DateTime:2017-06-0614:43 *@Description:android7.0uri权限适配, *(通过intent暴漏uri或file给第三方app时的)私有目录被禁止访问 *已对localpath和netpath做了适配 */ publicclassFileUriPermissionCompat{ privatestaticfinalStringTAG=FileUriPermissionCompat.class.getSimpleName(); //TODO:此处需要更改为对应值 //此处需要改成AndroidManifest.xml中申请的对应的provider的authorities值 privatestaticfinalStringAUTHORITIES="com.duke.personalkeeper.myFileProvider"; /** *是否需要适配7.0权限 * *@return */ publicstaticbooleanisNeedAdapt(){ //24以上版本 returnBuild.VERSION.SDK_INT>=Build.VERSION_CODES.N; } publicstaticUriadaptUriAndGrantPermission(Contextcontext,Intentintent,Filefile){ Uriuri=adaptUri(context,file); if(uri==null){ returnnull; } grantUriPermission(context,intent,uri); returnuri; } publicstaticUriadaptUri(Contextcontext,Filefile){ if(context==null||file==null){ returnnull; } //网络路径的特殊处理,不需要7.0适配,但必须用parse()方法 if(file.getPath().startsWith("http")){ returnUri.parse(file.getPath()); } Uriuri=null; try{ if(isNeedAdapt()){ //需要7.0特殊适配 //通过系统提供的FileProvider类创建一个content类型的Uri对象 uri=FileProvider.getUriForFile(context,AUTHORITIES,file); }else{ //不需要适配 uri=Uri.fromFile(file); } }catch(Exceptione){ Log.e(TAG,"authoritiesvalueerror,socan'tconverturi!"); e.printStackTrace(); } returnuri; } /** *对第三方应用赋予对uri读写的权限 * *@paramcontext *@paramintent *@paramsaveUri适配后的uri */ publicstaticvoidgrantUriPermission(Contextcontext,Intentintent,UrisaveUri){ if(!isNeedAdapt()){ return; } if(context==null||intent==null||saveUri==null){ return; } //网络路径的特殊处理,不需要权限 if(saveUri.getScheme()!=null&&saveUri.getScheme().startsWith("http")){ //不需要授权 return; } //1、授权(系统相册、相机、裁剪时需要)--这种写法待分析 ListresInfoList=context.getPackageManager().queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY); for(ResolveInforesolveInfo:resInfoList){ StringpackageName=resolveInfo.activityInfo.packageName; if(TextUtils.isEmpty(packageName)){ continue; } context.grantUriPermission(packageName,saveUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } //2、授权(安装apk时需要) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } publicstaticvoidrevokeUriPermission(Contextcontext,Intentintent,UrisaveUri){ if(!isNeedAdapt()){ return; } if(context==null||intent==null||saveUri==null){ return; } //网络路径的特殊处理,不需要权限 if(saveUri.getScheme()!=null&&saveUri.getScheme().startsWith("http")){ //不需要授权 return; } try{ context.revokeUriPermission(saveUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION); }catch(Exceptione){ e.printStackTrace(); } } } 核心代码: uri=FileProvider.getUriForFile(context,AUTHORITIES,file); file就是你想要暴露给其他应用的文件地址,比喻你要拍照,把结果保存到file文件中。 AUTHORITIES就是上面第一步中,android:authorities=”${applicationId}.myFileProvider”的实际值, ${applicationId}取得是app/build.gradle中defaultConfig标签的applicationid值。通过系统提供的FileProvider类的静态方法转换file地址为一个以content://开头的特殊的uri。如果不转换的话,直接用Uri.fromFile(file),你得到的是一个file:///xxxxx这样的uri。就这差别。 转换了uri之后,还需要授权: /** *对第三方应用赋予对uri读写的权限 * *@paramcontext *@paramintent *@paramsaveUri适配后的uri */ publicstaticvoidgrantUriPermission(Contextcontext,Intentintent,UrisaveUri){ if(!isNeedAdapt()){ return; } if(context==null||intent==null||saveUri==null){ return; } //网络路径的特殊处理,不需要权限 if(saveUri.getScheme()!=null&&saveUri.getScheme().startsWith("http")){ //不需要授权 return; } //1、授权(系统相册、相机、裁剪时需要)--这种写法待分析 List<ResolveInfo>resInfoList=context.getPackageManager().queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY); for(ResolveInforesolveInfo:resInfoList){ StringpackageName=resolveInfo.activityInfo.packageName; if(TextUtils.isEmpty(packageName)){ continue; } context.grantUriPermission(packageName,saveUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } //2、授权(安装apk时需要) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } 里面有两部分授权方式,经过多轮测试,发现需要同时使用比较好。 第一种方式,for循环,是因为有时候你并不确定需要分享的应用的包名是哪一个,所以找到所有有可能的第三方应用,全部授权了。 后来测试发现,安装apk的时候,只有上面的授权是不行的,还得加上intent.addFlag的方式再次授权才行。 最后,需要注意的是: 1、注意7.0的版本判断。 2、切记,7.0的权限有可能需要对sd卡读写,需要6.0的读写sd卡权限。当你测试7.0权限不成功时,考虑下6.0的权限是否到位了。 其他的没的说了。就这些。 本文作者:佚名 来源:51CTO