Android动态权限管理原理(6.0及以上)
在6.0之前,所有权限都是在安装时候授予的,在6.0之后,允许用户动态控制权限。
国内手机厂商却将Google隐藏的权限管理用了起来,本文主要涉及一下几个部分内容:
- Android6.0之前的动态权限管理模型及原理-AppOpsManager(这个不做具体分析,了解即可,毕竟已经弃用)
- Android6.0及之后的动态权限管理原理-runtime permission
- 两种权限的特点与区别
Android 6.0权限管理原理
从Android6.0开始,原生支持runtime-permission机制,用户在任何时候都可以授权/取消授权,并且APP能够在请求服务之前知道是否已经获得所需要的权限。
工具类PermissionChecker,可以用来检查权限获取情况。
1 | public static int checkPermission(@NonNull Context context, @NonNull String permission, |
最终会通过ActivityManagerNative将请求发送给AMS
1 | public static int checkPermission(@NonNull Context context, @NonNull String permission, |
AMS端对应的处理是:
1 | int checkComponentPermission(String permission, int pid, int uid, |
进而调用ActivityManager.checkComponentPermission,调用AppGlobals.getPackageManager().checkUidPermission(permission, uid)
1 | /** @hide */ |
最终调用PackageManagerService.java去查看是否有某种权限,到这里,只需要关心权限的查询其实是通过PKMS来进行的,后面还会看到权限的更新,持久化,恢复也是通过PKMS来进行的。checkUidPermission在不同的版本都是支持的,只不过Android6.0的实现跟之前的版本有很大不同,看一下6.0之前的, Android5.0的checkUidPermission:主要是通过Setting获取当前APP的权限列表,对于6.0之前的APP,这些权限都是静态申请的,或者说只要在Menifest文件中声明了,这里就认为是申请了。
1 | //6.0之前的 |
GrantedPermissions是一个APP所对应权限的集合,内部有一个权限列表 HashSet grantedPermissions = new HashSet(),只要权限在Menifest中申请了,该列表中就会包含其对应的字符串,完全是静态的。但是6.0的runtime-permmison就不同了,Android6.0+的checkUidPermission.
这也是为什么Android6.0之后改成动态获取权限的原因。
1 | //6.0之后的 |
hasPermission如下:
1 | public boolean hasPermission(String name, int userId) { |
动态权限申请如下:
动态申请权限
Android 6.0动态申请权限通过ActivityCompat来进行
1 | public static void requestPermissions(final @NonNull Activity activity, |
如果是6.0以下,直接通过ActivityCompatPKMS查询是否在Manifest中申请权限,如果申请了就默认具备该权限,并将onRequestPermissionResult将结果回传给Activity或者Fragment。
对于6.0以上,则会通过activity.requestPermission去申请权限。
1 | public final void requestPermissions(@NonNull String[] permissions, int requestCode) { |
这里的intent其实是PackageManager(ApplicationPackageManager实现类)获取的intent
1 | public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { |
上面函数主要是为了获取悬浮授权的Activity组件信息,GrantPermissionsActivity样式,类似于对话框:
1 | <activity android:name=".permission.ui.GrantPermissionsActivity" |
这是一个类似于对话框的悬浮窗样式Activity
1 | <style name="GrantPermissions" parent"Settings"> |
6.0之前,权限申请(未在release版本发布)是同步的,会阻塞UI线程
在国产ROM中,申请权限的线程会被阻塞(即使是UI线程),这是因为鉴权的Binder通信是同步的,并且,服务端一直等到用户操作后才将结果返回给客户端,这就导致了客户端请求线程一直阻塞,直到用户操作结束。askOperationLocked通过mHandler发送鉴权Message,并返回一个支持阻塞操作的PermissionDialogResult.Result,通过其get函数阻塞等待操作结束。
6.0之后,权限申请是异步的,走的是startActivityForResult流程
流程如下:
动态更新及持久化权限
流程如下:
我们可以看到,最终持久化的文件是data/system/0/runtime-permissions.xml,该文件只有在Android6.0以上才有,应用包名+权限名+授权状态
1 | <pkg name="com.snail.xxx"> |
Runtime-Permission读取
读取时机:在手机重新启动时,由PKMS读取,开机时,PKMS会扫描APK,读取packages.xml文件,而运行时权限,是在启动时读取。
1 | boolean readLPw(@NonNull List<UserInfo> users) { |
流程如下:
Android6.0及以上动态申请普通权限会怎么样?
申请结果永远是取得授权的,因为在安装时就已经获取了。
我们可以看到对应的xml:
1 | <perms> |
Android动态管理权限的关键节点在哪里?
Android6.0之后采用鉴权与申请分开的做法,先查询一下有没有这种权限,没有再去申请,这样可以避免了权限管理的混淆,更加清晰灵活。