本文主要讲解了 Android 权限管理方面几个点:
提到 Android 权限管理,业内人士都知道 Google 在 Android 6.0 时提出了运行时权限管理机制,在 Android 6.0 之前,所申请的权限只需要在 AndroidManifest.xml 列举就可以,从而容易导致一些安全隐患,因此,在 Android 6.0 时,Google 为了更好的保护用户隐私提出了新的权限管理机制(官网 : Working with System Permissions ),同时将其分为两大类:
Normal Permissions 一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;
Dangerous Permission 一般是涉及到用户隐私的,需要用户进行授权(动态申请),比如读取 SIM 卡状态、访问通讯录、SD 卡读写等。
通过 adb shell pm list permissions -d -g 可以查看 Dangerous Permission (以权限组形式)
Dangerous Permission group
如上图所示 :Dangerous Permission 一般以 Permission group 形式存在,只要 Permission group 中某一个 permission 被 Granted,则整个 Permission group 下的权限均被 Granted (目前是这样,以后规则说不定会变)。
本节主要介绍介绍如何进行权限检查及权限兼容,主要分为以下几类:
安装的时候不会获得权限,在运行时向用户申请对应权限。这部分权限检查比较简单,不涉及权限兼容,使用官方方案就可以 ,使用 Context::checkSelfPermisson ,建议使用 ContextCompat::checkSelfPermisson 检查权限 即可 ,一般检查流程 如下:
上述情况较为简单,在此不再赘述。
使用的是老的权限机制,在 app 安装时会询问 AndroidManifest.xml 文件中的权限,但是用户可以在设置列表中关闭相关权限,这种情况可能会对 app 正常运行造成一定影响。
大家可能要问,终端设备系统小于 6.0 情况还需要考虑吗,肯定是用的老的权限管理机制,在 app 安装时会询问 AndroidManifest.xml 文件中的权限,用户关闭不了,真的是这样吗 ?
答案是否定的,在实测中发现,目前有不少国产 Rom 手机在 6.0 之前就有关闭权限的开关。这种情况也是我们兼容的对象。
下面将会以自己开发过程中遇到的问题进行展开 ,目前企鹅 FM 支持免流了,需要使用 READ_PHONE_STATE 权限 (读取 SIM 卡状态),由于之前未对改权限是否关闭没有进行相关判断,因此收到了很多例因为上述权限关闭,导致免流失败的情况。
适配过程如下 :
想法很简单,如果改权限被用户禁止了,那肯定会异常,因此可以在 catch 中做文章,结果发现这一招根本没有用,为啥了 ?因为使用 READ_PHONE_STATE 权限的方法内部已经 try catch ,外面无法捕获,因此该方法失效。
既然在 6.0 可以使用 Context::checkSelfPermisson 进行权限检查,那能否使用 support v4 中的 ContextCompat::checkSelfPermisson 方法了,试一下,发现在 api 23 以下失效,为了探究原因,查看了 ActivityCompat::requestPermissons 内部实现,如下
ActivityCompat::requestPermissons
内部权限检查方法在 api 23 以下,使用的是 PackageManager::checkPermission,再去查看 PackageManager::checkPermission 方法,如下:发现只要权限在 AndroidManifest.xml 中注册过,均会认为该权限 granted ,因此上述方法在 api 23 以下也失效。
PackageManager::checkPermission
查阅相关资料和请教组内同事,发现 Support V4 下面有一个专门检查权限的工具类 PermissionChecker。
查看 PermissionChecker 源码发现 ,PermissionChecker 内部实际上使用的是 AppOpsManagerCompt,而 AppOpsManager 是在 api 19 加入进入的( AppOpsManager 后面介绍 )
PermissionChecker::checkPermission
进而查看 AppOpsManagerCompat 内部实现
AppOpsManagerCompat::permissionToOp
IMPL 实现如下:
IMPL 实现
从上图可以看出:在 api 23 以下, AppOpsManagerImpl::permissionToOp 直接返回为 null ,这直接导致 api 23 以下权限检查将会返回 granted ,因此,该方法在 api 23 下,权限检查方法也会失效。
API 19 以上 ,Google 官方提供了 AppOpsManager 类来检查权限,看到这个 api 时,脑海浮现出 "天无绝人之路啊",里面有两个比较重要的方法 :AppOpsManager::checkOp(int op ,int uid ,String packageName) (hide 方法)和 AppOpsManager::checkOp(String op,int uid ,String packageName)(public 方法 ,api 23 以上可用),不经思考,直接写出了如下两个方法
需要使用反射 :
AppOpsManager::checkOp(int op ,int uid ,String packageName)
API >= 23 才可以使用 :
AppOpsManager::checkOp(String op,int uid ,String packageName)
在实测中发现,api 低于 23 时 ,OP_READ_PHONE_STATE =51 找不到,导致反射失败。
仔细察看了一下 6.0 (API 23 )_NUM_OP = 62, 如下,为何找不到 51 了
6.0 (API 23 )_NUM_OP = 62
难不成 每个版本还不一样,查看其他版本,验证了这个想法:
5.1.1
Op 个数限制 (需要找到对应源码说明 **)详情 参看 :
查看 Android 对应版本 _NUM_OP (目前最高支持 5.1.1 版本)
此时,OP_READ_PHONE_STATE = 51 在 6.0(API 23)以下,通过反射是找不到的,因此对于 READ_PHONE_STATE 权限检查仅限于 6.0 及 6.0 以上。
同时也仔细看了一下 AppOpsManager 类介绍,并不是为开发者设计的,不过其他权限兼容可以使用这种方法,前提是 要看 OP_* 是在什么版本才有的,需做兼容方案 。
- public class AppOpsManager extends Object API
- for interacting with"application operation"tracking.This API is not generally intended
- for third party application developers;
- most features are only available to system applications.Obtain an instance of it through Context.getSystemService with Context.APP_OPS_SERVICE.
(5)最后查看了几个第三方权限库(暂未看完)
既然在这里讲解跳转到 app 管理权限页面的方法,可想而之,事情绝对不太简单。Android 碎片化不仅在存在于 ui 适配 ,同样也存在于这里,导致我们无法使用同一种方式跳转到 app 管理权限页面(适配,Android 开发永远的痛)。
那有没有办法可以简化适配工作,减少开发量,方法当然有,不过需要我们自己去总结和探索的,目前已有方法:
- Intent intent = newIntent();
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.setData(Uri.fromParts("package", getPackageName(), null));
- try {
- startActivity(intent);
- } catch(Exception exception) {
- exception.printStackTrace();
- }
记得要添加上 try catch ,不加可能会 crash。这种方式就不需要适配各个厂商的不同版本 rom,缺点是,用户只能跳转到系统设置页,然后去找对应 app 的权限管理( 总会有一些用户找不到 )
引用前人经验: Android 各大手机品牌手机跳转到权限管理界面 (未一一验证,毕竟没那么多手机)
那是不是前人经验一定对了,那就不一定了,在当时可能是对的,在现在可能就行不通了,现在以 MIUI 跳转到 app 权限管理页面为例进行说明。
- Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
- localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
- localIntent.putExtra("extra_pkgname", context.getPackageName());
- try {
- startActivity(intent);
- } catch(Exception exception) {
- exception.printStackTrace();
- }
- Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
- localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
- localIntent.putExtra("extra_pkgname", context.getPackageName());
- try {
- startActivity(intent);
- } catch(Exception exception) {
- exception.printStackTrace();
- }
对比 1)和 2)发现,在 MIUI 6/7 和 MIUI 8 上面,权限管理页面的 activity 名字不一样了,因此使用 MIUI6/7 的方法在 MIUI8 上就会失效,如果没有加上 try catch ,就会直接 crash。
对于上述变化,作为一个开发者一般都是不知道的,即便通过反馈发现了这个问题,也有可能不知道对应的 activity 是什么,此刻要么搜索网上有没有类似解决方案,要么求助于对应 rom 开发厂商的开发者论坛 ( 有时解决回应速度相当慢 ),那有没有更好的办法了,方法详见(3)部分。
这里以华为 p8 为例简要说明,详细步骤如下:
1)通过设置找到对应 app 的权限管理页面,如下:
企鹅 fm 在华为 p8 上的权限管理页面
2)找到对应页面的 activity
- adb shell dumpsys activity | grep "mFocusedActivity"
企鹅 fm 在华为 p8 上的权限管理页面对应的 activity
更为详细的堆栈信息
详细的堆栈信息
使用方法:可参见我之前的文章 : Android 开发—— 小工具,大效率
使用 Activity Tracer 查看权限管理页面对应的 activity
来源: http://www.tuicool.com/articles/EbayqeJ