Android动态权限管理原理(6.0及以上)

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
2
3
4
5
6
7
8
9
public static int checkPermission(@NonNull Context context, @NonNull String permission,
int pid, int uid, String packageName) {
<!--关键点1 -->
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
...
return PERMISSION_GRANTED;
}

最终会通过ActivityManagerNative将请求发送给AMS

1
2
3
4
5
6
7
8
9
public static int checkPermission(@NonNull Context context, @NonNull String permission,
int pid, int uid, String packageName) {
<!--关键点1 -->
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
...
return PERMISSION_GRANTED;
}

AMS端对应的处理是:

1
2
3
4
5
6
7
8
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}

进而调用ActivityManager.checkComponentPermission,调用AppGlobals.getPackageManager().checkUidPermission(permission, uid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** @hide */
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {

<!--root及System进程能获取所有权限-->
if (uid == 0 || uid == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
。。。
<!--普通应用的权限查询-->
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
}
return PackageManager.PERMISSION_DENIED;
}

最终调用PackageManagerService.java去查看是否有某种权限,到这里,只需要关心权限的查询其实是通过PKMS来进行的,后面还会看到权限的更新,持久化,恢复也是通过PKMS来进行的。checkUidPermission在不同的版本都是支持的,只不过Android6.0的实现跟之前的版本有很大不同,看一下6.0之前的, Android5.0的checkUidPermission:主要是通过Setting获取当前APP的权限列表,对于6.0之前的APP,这些权限都是静态申请的,或者说只要在Menifest文件中声明了,这里就认为是申请了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//6.0之前的
public int checkUidPermission(String permName, int uid) {
final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
synchronized (mPackages) {
<!--PackageManagerService.Setting.mUserIds数组中,根据uid查找uid(也就是package)的权限列表-->
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
<!--关键点1 -->
GrantedPermissions gp = (GrantedPermissions)obj;
if (gp.grantedPermissions.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
}
...
return PackageManager.PERMISSION_DENIED;
}

GrantedPermissions是一个APP所对应权限的集合,内部有一个权限列表 HashSet grantedPermissions = new HashSet(),只要权限在Menifest中申请了,该列表中就会包含其对应的字符串,完全是静态的。但是6.0的runtime-permmison就不同了,Android6.0+的checkUidPermission.

这也是为什么Android6.0之后改成动态获取权限的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//6.0之后的
@Override
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
...
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
final SettingBase ps = (SettingBase) obj;
final PermissionsState permissionsState = ps.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
}
...
}
return PackageManager.PERMISSION_DENIED;
}

hasPermission如下:

1
2
3
4
5
6
7
8
9
public boolean hasPermission(String name, int userId) {
enforceValidUserId(userId);

if (mPermissions == null) {
return false;
}
PermissionData permissionData = mPermissions.get(name);
return permissionData != null && permissionData.isGranted(userId);
}

动态权限申请如下:

动态申请权限

Android 6.0动态申请权限通过ActivityCompat来进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode) {
if (Build.VERSION.SDK_INT >= 23) {
//大于6.0
ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);
} else if (activity instanceof OnRequestPermissionsResultCallback) {
//小于6.0
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
final int[] grantResults = new int[permissions.length];

PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();

final int permissionCount = permissions.length;
for (int i = 0; i < permissionCount; i++) {
grantResults[i] = packageManager.checkPermission(
permissions[i], packageName);
}

((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
});
}
}

如果是6.0以下,直接通过ActivityCompatPKMS查询是否在Manifest中申请权限,如果申请了就默认具备该权限,并将onRequestPermissionResult将结果回传给Activity或者Fragment。

对于6.0以上,则会通过activity.requestPermission去申请权限。

1
2
3
4
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

这里的intent其实是PackageManager(ApplicationPackageManager实现类)获取的intent

1
2
3
4
5
6
    public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
intent.setPackage(getPermissionControllerPackageName());
return intent;
}

上面函数主要是为了获取悬浮授权的Activity组件信息,GrantPermissionsActivity样式,类似于对话框:

1
2
3
4
5
6
7
8
9
<activity android:name=".permission.ui.GrantPermissionsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true"
android:theme="@style/GrantPermissions">
<intent-filter>
<action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

这是一个类似于对话框的悬浮窗样式Activity

1
2
3
4
5
<style name="GrantPermissions" parent"Settings">
<item name="android:windowIsFloating">true</item>
<item name="android:windowElevation">@dimen/action_dialog_z</item>
<item name="android:windowSwipeToDismiss">false</item>
</style>

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
2
3
4
<pkg name="com.snail.xxx">
<item name="android.permission.CALL_PHONE" granted="true" flags="0" />
<item name="android.permission.CAMERA" granted="false" flags="1" />
</pkg>

Runtime-Permission读取

读取时机:在手机重新启动时,由PKMS读取,开机时,PKMS会扫描APK,读取packages.xml文件,而运行时权限,是在启动时读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
boolean readLPw(@NonNull List<UserInfo> users) {
FileInputStream str = null;
if (mBackupSettingsFilename.exists()) {
try {
str = new FileInputStream(mBackupSettingsFilename);
mReadMessages.append("Reading from backup settings file\n");
...
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
String tagName = parser.getName();
if (tagName.equals("package")) {

<!--关键点1--读取package信息,包括install权限信息(对于Android6.0package.xml)-->
readPackageLPw(parser);
...
<!--关键点2 读取runtime permmsion权限信息-->

for (UserInfo user : users) {
mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
}
}

流程如下:

Android6.0及以上动态申请普通权限会怎么样?

申请结果永远是取得授权的,因为在安装时就已经获取了。

我们可以看到对应的xml:

1
2
3
4
<perms>
<item name="android.permission.INTERNET" granted="true" flags="0" />
<item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
</perms>

Android动态管理权限的关键节点在哪里?

Android6.0之后采用鉴权与申请分开的做法,先查询一下有没有这种权限,没有再去申请,这样可以避免了权限管理的混淆,更加清晰灵活。