问题与需求
在业务线在使用过程中,发生了一些权限状态丢失的情况。而在我们的群控平台中,如果某些权限的设置状态丢失,则群控平台绝大多数功能不能执行。尤其是HRG业务人员,它们不仅需要群控自动执行功能指令,还不能影响手动操作。所以在权限状态丢失后,产品提出了一个权限检测的需求。这个需求就是在我们的平台中检测应用的权限列表,获取权限的当前状态。用于提示使用者,当前的群控在权限设置上是否已经被允许。
权限介绍
一个Android应用默认情况下是不拥有任何权限的, 在默认情况下, 一个应用是没有权利去进行一些可能会造成不好影响的操作的. 这些不好的影响可能是对其它应用,操作系统,或者是用户.
如果应用需要某些额外的能力,则需要在AndroidManifest.xml中静态地声明相应的权限.
也就是我们在AndroidManifest.xml注册了一系列uses-permission,这些permission就是我们需要申请的权限。
在Android 6.0发布之前, 所有的权限都在安装应用的时候显示给用户,用户选择安装则表示全部接受这些权限, 之后无法撤销对这些权限的授权.
但是从Android 6.0开始, 一部分比较危险的权限需要在程序运行请求用户授权.
至于什么时候需要授权,由应用程序自己决定.而这些权限,就是我们所说的运行时权限。
而对于其他权限,认为不是很危险,所以仍然保持原来的做法,在用户安装应用程序时就予以授权.
需要注意的是,在设置中,对于应用的危险权限,用户可以选择性地进行授权或者关闭.
Dangerous Permissions主要有:
Permission Group | Permissions |
---|---|
CALENDAR |
|
CAMERA |
|
CONTACTS |
|
LOCATION |
|
MICROPHONE |
|
PHONE |
|
SENSORS |
|
SMS |
|
STORAGE |
而通过上述危险权限来看,是有了组的概念,只要我们授权了其中一个权限,那就授权当前组的所有全新啊。而我们可以通过下面的方法获取到所有的申请权限:
1 | PackageManager packageManager = this.getPackageManager(); |
实现历程
App Ops
Google在SDK19中引入了AppOps的权限管理方式,AppOpsManager是对外的管理接口,真正实现功能的是AppOpsService。AppOpsManager里面有两个比较重要的方法:
1 | AppOpsManager::checkOp(int op ,int uid ,String packageName) (hide方法) |
所以我们可以用第二个方式检测权限的状态,uid和packageName我们可以拿到,但是op是什么呢,通过查找我们发现AppOpsManager提供了一个函数permissionToOp,通过这个函数我们可以,我们可以把标题1中获取到的uses-permission转换成op,然后我们就可以利用checkOp获取到权限的状态结果了。
但是在实际的操作中我们发现。发生了一个SecurityException异常。这个是为什么呢,我们通过查看源码发现。permissionToOp可能为空,这个是因为,我们标题1中获取到的permission,不一定有对应的op值。部分展示如下所示:我们发现其中有好多null值。
1 | /** |
结论:
1、通过permissionToOp和checkOp将不能满足我们的需求。
2、我们发现AndroidManifest.xml中的权限要比小米手机权限列表中的要多。
SecurityCenter
SecurityCenter是什么,它是小米手机权限管理的apk,通过反编译,我们可以一步步的发现小米手机获取权限列表状态的实现方式。粘一下小米的实现核心代码:
1 | public PermissionList loadInBackground() { |
其中,packagePermisson,mo5980boo,mo5981bop,都是通过ContentResolver跨进程获取到的,然后把获取到的权限分组拼装即可。所以,通过这种方式,我们获取到的权限状态和小米手机的权限列表是一致的。
结论:
因为上述实现是基于note 4x手机的安全中心获取的,所以没有问题。但是我们的手机型号不止是4x手机,还有其他小米手机,但我在其他手机上运行的时候,发现报错,出现没有miui.permission.READ_AND_WIRTE_PERMISSION_MANAGER的异常。所以,这种实现方式,不能支撑所有手机。
AppOpsX
是一个第三方权限管理的实现,通过调用appops的权限管理器,控制权限管理。
[Github:https://github.com/8enet/AppOpsX] (https://github.com/8enet/AppOpsX),接入AppOpsX后,可以支撑所有小米手机,但是获取到的权限列表不能和小米手机的权限中心一致,这个会导致产品和运营有一定的误解,并且接入比较困难,可能对今天版本的兼容也会有问题。
checkSelfPermission
ContextCompat
Android 6.0 变更:
此版本引入了一种新的权限模式,如今,用户可直接在运行时管理应用权限。这种模式让用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。用户可为所安装的各个应用分别授予或撤销权限。
对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。
如需了解有关在您的应用中支持新权限模式的详情,请参阅使用系统权限。如需了解有关如何评估新模式对应用的影响的提示,请参阅权限最佳做法。
从上图可知,最终的check会到PackageManagerService. checkComponentPermission,而checkComponentPermission最终调用了
1 | @Override |
这里最重要的就是mSettings,所有的权限信息都通过mSettings来获取,但是mSettings是都做了什么操作呢?看下边的图:
Android6.0之前会吧所有的权限都放置在data/system/packages.xml文件中。Android6.0之后,分为运行时权限跟普通权限,普通权限还是放在data/system/packages.xml中,运行时权限防止在data/system/users/0/runtime-permissions.xml文件中。根据运行时是否动态申请去更新权限。而对于普通权限,一直都为true。
具体的实例信息如下:
packages.xml
1 | <package name="com.permisison.naga" codePath="/data/app/com.wuba.tinyhand-2" nativeLibraryPath="/data/app/com.wuba.tinyhand-2/lib" primaryCpuAbi="armeabi" publicFlags="944291398" privateFlags="0" ft="166817e48c8" it="16615425c49" ut="166817e51fc" version="2540" userId="10141"> |
runtime-permissions.xml
1 | <pkg name="com.permisison.naga"> |
结论:
1、checkSelfPermission会分别判断两个xml文件,packages.xml是普通权限,并且一直返回true,runtime-permissions.xml返回的是动态权限的状态,会根据当前设置返回0和-1,0是授予,-1是未被授予权限。
2、国内手机厂商的rom都是修改过了,并且增加了不少自定义的权限,所以这个方法获取的结果是不全的。
PermissionChecker
直接看下关键代码:
1 | public static int checkPermission(@NonNull Context context, @NonNull String permission, |
里面有这么一句判断
1 | String op = AppOpsManagerCompat.permissionToOp(permission); |
AppOpsManager返回为null的permission,都返回PERMISSION_GRANTED=0;这个判断就不准确了,如果一个运行时权限,没有在这个里面,那就永远为o了。即使设置了拒绝,也获取不到PERMISSION_DENIED=1。并且,小米手机设置好多10000以上都op自定义权限,肯定是获取不到,并且有些权限还没有permission。
结论:完全不可用。继续尝试
AuthManager
通过上述一些列的验证,都是不可以采用的结论。最终我们再次回到note 4x的实现方案上来。由于再标题2的时候,我们只是调研了获取权限状态的方式,但是没有深入的调研底层实现。所以我们通过一些列跟踪,最终找到具体的实现地方在AuthManager这个app下。
结论:
1、由上图可以看出,小米手机完全自己封装了一层权限管理,它的权限状态都存储在了miui_ngen.db数据库里,并且通过PermissionManager管理并定义了权限的op和android.permission值。
2、既然update时完全操作了db和xml三个文件。那我们是不是可以用反射的方法实现呢?经过实现,利用反射checkOpNoThrow虽然可以获取到具体状态值,但是我们只能获取到当前的权限,不能获取到第三方app的权限,报
java.lang.SecurityException: uid 10141 does not have android.permission.UPDATE_APP_OPS_STATS异常。
3、开发过程中,发现某些运行权限op=59的权限不能改变,一致获取到的是0.即授权的状态。
所以利用反射我们也不能实现我们的需求,继续。
runtime-permissions.xml与appops.xml
经过调研后发现,当我们在修改权限的状态值时,不仅appops.xml里面的状态值在该比那,而且运行权限runtime-permissions.xml的值也在改变,那我们是不是可以比较两个文件的内容进行获取状态值呢。 首先,我们需要读取文件,但是我们没有读取文件权限,我们需要copy到sdcard进行解析。解析过程如下图:
miui_ngen.db
miui_ngen.db是什么?我们都知道是一个数据库文件,而且这个是miui的权限状态管理文件。任何的App权限状态都一一对应的存在了这个文件里。从而由上述6种的操作,我们受到了一定的启发,既然能够拿到xml文件,那我们是不是也可以直接拿到这个db文件,然后直接读取db,获取状态信息呢?经过尝试,好吧,权限检测的最终方案决定下来了,我们完全可以直接读取这个db文件,获取到准确的,和miui权限管理中心一一对应,完全匹配的权限状态。
总结:通过以上步骤的实现过程,我们发现由于国内厂商对rom的定制,可能完全修改或者封装了权限的实现和检测机制,我们通过一步步的测试和添坑,最终还是回到了手机原本自身的检测机制上来,并且这种方式是最准确的方式。
对权限的处理场景
如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。
如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。
如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 22 或更低版本, 此时Android系统会把你申请的全部权限都给你 。 用户依然可以进入App的设置界面把权限关闭 !
参考资料
https://developer.android.com/about/versions/marshmallow/android-6.0-changes?hl=zh-cn#behavior-runtime-permissions
https://blog.csdn.net/lewif/article/details/49124757
https://blog.csdn.net/happylishang/article/details/53813779
https://mp.weixin.qq.com/s/OQRHEufCUXBA3d3DMZXMKQ
https://developer.android.com/reference/android/support/v4/content/ContextCompat#checkSelfPermission(android.content.Context,%20java.lang.String)
https://developer.android.com/reference/android/content/pm/PackageManager#PERMISSION_DENIED
https://www.jianshu.com/p/0ddd129dd32b
https://blog.csdn.net/happylishang/article/details/78222788
https://www.cnblogs.com/neo-java/p/7117482.html
Ursprünglicher Link: http://nunu03.github.io/2019/07/02/permissioncheck/
Copyright-Erklärung: 转载请注明出处.