Android 7.0动态权限大总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 7.0动态权限大总结相关的知识,希望对你有一定的参考价值。

应公司项目需求,做了下Android 7.0适配。对于我们程序员,适配7.0主要就是对手机本地文件的Uri做转换处理。注意红色字体,意思也就是说对于http开头的等等,非手机存储中的文件就不用管了。Uri.parse("package") 这样的也不用管!!!!!

7.0的适配,就是对手机存储中的私有文件路径的保护,当系统发现你通过intent带走了一个uri,地址是本地的文件,就会限制的。其他的原理普及请搜索其他文章吧,此处略。

下面贴一下使用步骤和我的工具类。


一、需要修改当前module的androidManifest.xml文件,添加provider标签,映射路径。

  1. <application android:allowBackup="true" android:label="@string/app_name"  
  2.         android:supportsRtl="true">  
  3.   
  4.         <provider  
  5.             <span style="color:#ff0000;">android:name="android.support.v4.content.FileProvider"</span>  
  6.             android:authorities="${applicationId}.myFileProvider"  
  7.             <span style="color:#ff0000;">android:exported="false"  
  8.             android:grantUriPermissions="true"</span>>  
  9.             <meta-data  
  10.                 <span style="color:#ff0000;">android:name="android.support.FILE_PROVIDER_PATHS"</span>  
  11.                 android:resource="@xml/path_file" />  
  12.         </provider>  
  13.     </application>  

文中红色部分是固定写法,官网就是这么说的,别乱试了。android:resource标签的值是main/res/xml/path_file.xml文件,即在 res下新建xml文件夹,再新建一个xml文件。


二、在res/xml下新建一个path_file.xml文件,文件名随便去,但是与上步骤一致即可。

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <paths>  
  4.         <!--  
  5.         <files-path/>代表的根目录: Context.getFilesDir()  
  6.         <cache-path/>代表的根目录: getCacheDir()  
  7.         <external-path/>代表的根目录: Environment.getExternalStorageDirectory()  
  8.         <external-files-path/>代表的根目录: Context.getExternalFilesDir(String) Context.getExternalFilesDir(null).  
  9.         <external-cache-path />代表的根目录: Context.getExternalCacheDir().  
  10.         <root-path />代表设备的根目录new File("/");  
  11.         -->  
  12.         <!-- path=""代表根目录,也可以指定特定目录,name="camera_picture"是虚拟目录camera_picture -->  
  13.         <root-path name="root" path="" />  
  14.         <files-path name="files" path="" />  
  15.         <cache-path name="cache" path="" />  
  16.         <external-path name="external" path="" />  
  17.         <external-files-path name="external_files" path="" />  
  18.         <external-cache-path name="external_cache" path="" />  
  19.     </paths>  
  20. </resources>  

里面共有6个path标签,含义都写了,根据你的需要些对应的标签即可。比喻<external-path/>标签,里面的path=""时,这个标签映射的就是外挂sd卡根目录了,name属性没什么用,来迷惑第三方应用的虚拟目录,来掩盖文件的真实路径。


三、工具类来了。

FileUriPermissionCompat.Java

  1. /**  
  2.  * @Author: duke  
  3.  * @DateTime: 2017-06-06 14:43  
  4.  * @Description: android 7.0 uri权限适配, <br/>  
  5.  * (通过intent暴漏uri或file给第三方app时的)私有目录被禁止访问 <br/>  
  6.  * 已对local path和net path做了适配 <br/>  
  7.  */  
  8. public class FileUriPermissionCompat {  
  9.     private static final String TAG = FileUriPermissionCompat.class.getSimpleName();  
  10.   
  11.     // TODO: 此处需要更改为对应值  
  12.     //此处需要改成AndroidManifest.xml中申请的对应的provider的authorities值  
  13.     private static final String AUTHORITIES = "com.duke.personalkeeper.myFileProvider";  
  14.   
  15.     /**  
  16.      * 是否需要适配7.0权限  
  17.      *  
  18.      * @return  
  19.      */  
  20.     public static boolean isNeedAdapt() {  
  21.         //24以上版本  
  22.         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;  
  23.     }  
  24.   
  25.     public static Uri adaptUriAndGrantPermission(Context context, Intent intent, File file) {  
  26.         Uri uri = adaptUri(context, file);  
  27.         if (uri == null) {  
  28.             return null;  
  29.         }  
  30.         grantUriPermission(context, intent, uri);  
  31.         return uri;  
  32.     }  
  33.   
  34.     public static Uri adaptUri(Context context, File file) {  
  35.         if (context == null || file == null) {  
  36.             return null;  
  37.         }  
  38.         //网络路径的特殊处理,不需要7.0适配,但必须用parse()方法  
  39.         if (file.getPath().startsWith("http")) {  
  40.             return Uri.parse(file.getPath());  
  41.         }  
  42.         Uri uri = null;  
  43.         try {  
  44.             if (isNeedAdapt()) {  
  45.                 //需要7.0特殊适配  
  46.                 //通过系统提供的FileProvider类创建一个content类型的Uri对象  
  47.                 uri = FileProvider.getUriForFile(context, AUTHORITIES, file);  
  48.             } else {  
  49.                 //不需要适配  
  50.                 uri = Uri.fromFile(file);  
  51.             }  
  52.         } catch (Exception e) {  
  53.             Log.e(TAG, "authorities value error, so can‘t convert uri !");  
  54.             e.printStackTrace();  
  55.         }  
  56.         return uri;  
  57.     }  
  58.   
  59.     /**  
  60.      * 对第三方应用赋予对uri读写的权限  
  61.      *  
  62.      * @param context  
  63.      * @param intent  
  64.      * @param saveUri 适配后的uri  
  65.      */  
  66.     public static void grantUriPermission(Context context, Intent intent, Uri saveUri) {  
  67.         if (!isNeedAdapt()) {  
  68.             return;  
  69.         }  
  70.         if (context == null || intent == null || saveUri == null) {  
  71.             return;  
  72.         }  
  73.         //网络路径的特殊处理,不需要权限  
  74.         if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {  
  75.             //不需要授权  
  76.             return;  
  77.         }  
  78.         //1、授权(系统相册、相机、裁剪时需要)  -- 这种写法待分析  
  79.         List<ResolveInforesInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);  
  80.         for (ResolveInfo resolveInfo : resInfoList) {  
  81.             String packageName = resolveInfo.activityInfo.packageName;  
  82.             if (TextUtils.isEmpty(packageName)) {  
  83.                 continue;  
  84.             }  
  85.             context.grantUriPermission(packageName, saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
  86.         }  
  87.         //2、授权(安装apk时需要)  
  88.         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
  89.     }  
  90.   
  91.     public static void revokeUriPermission(Context context, Intent intent, Uri saveUri) {  
  92.         if (!isNeedAdapt()) {  
  93.             return;  
  94.         }  
  95.         if (context == null || intent == null || saveUri == null) {  
  96.             return;  
  97.         }  
  98.         //网络路径的特殊处理,不需要权限  
  99.         if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {  
  100.             //不需要授权  
  101.             return;  
  102.         }  
  103.         try {  
  104.             context.revokeUriPermission(saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
  105.         } catch (Exception e) {  
  106.             e.printStackTrace();  
  107.         }  
  108.     }  
  109. }  


核心代码:

  1. 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之后,还需要授权:

  1. /**  
  2.      * 对第三方应用赋予对uri读写的权限  
  3.      *  
  4.      * @param context  
  5.      * @param intent  
  6.      * @param saveUri 适配后的uri  
  7.      */  
  8.     public static void grantUriPermission(Context context, Intent intent, Uri saveUri) {  
  9.         if (!isNeedAdapt()) {  
  10.             return;  
  11.         }  
  12.         if (context == null || intent == null || saveUri == null) {  
  13.             return;  
  14.         }  
  15.         //网络路径的特殊处理,不需要权限  
  16.         if (saveUri.getScheme() != null && saveUri.getScheme().startsWith("http")) {  
  17.             //不需要授权  
  18.             return;  
  19.         }  
  20.         //1、授权(系统相册、相机、裁剪时需要)  -- 这种写法待分析  
  21.         List<ResolveInforesInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);  
  22.         for (ResolveInfo resolveInfo : resInfoList) {  
  23.             String packageName = resolveInfo.activityInfo.packageName;  
  24.             if (TextUtils.isEmpty(packageName)) {  
  25.                 continue;  
  26.             }  
  27.             context.grantUriPermission(packageName, saveUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
  28.         }  
  29.         //2、授权(安装apk时需要)  
  30.         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);  
  31.     }  


里面有两部分授权方式,经过多轮测试,发现需要同时使用比较好。

第一种方式,for循环,是因为有时候你并不确定需要分享的应用的包名是哪一个,所以找到所有有可能的第三方应用,全部授权了。

后来测试发现,安装apk的时候,只有上面的授权是不行的,还得加上intent.addFlag的方式再次授权才行。


最后,需要注意的是:

1、注意7.0的版本判断。

2、切记,7.0的权限有可能需要对sd卡读写,需要6.0的读写sd卡权限。当你测试7.0权限不成功时,考虑下6.0的权限是否到位了。


其他的没的说了。就这些。











以上是关于Android 7.0动态权限大总结的主要内容,如果未能解决你的问题,请参考以下文章

Android 6.0/7.0权限

Android 6.0/7.0权限

Android系统动态申请权限的机制流程总结

Android 7.0 下载APK后自动安装

Android动态权限申请

Android权限之动态权限