Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的。在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,并且在相应的时机动态申请权限,在适配了Android6.0的App运行在Android 6.0+的手机上时,就会调用6.0相关的API,不过在低版本的手机上,仍然是按安装时权限处理。
AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google觉得不成熟,所以在每个发行版的时候,总是会将这个功能给屏蔽掉。该功能跟国内的权限动态管理表现类似,这里用CyanogenMod12里面的实现讲述一下,(国内的ROM源码拿不到,不过从表现来看,实现应该类似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每个服务内部,比如,如果App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了App定位权限,如果需要授权,就弹出一个系统对话框让用户操作,并根据用户的操作将结果持久化在文件中,如果在Setting里设置了响应的权限,也会去更新相应的权限操作持久化文件/data/system/appops.xml,下次再次申请服务的时候,服务会再次鉴定权限。
App在使用定位服务的时候,一般是通过LocationManager的requestLocationUpdates获取定位,其实是通过Binder请求LocationManagerService去定位。
/android/location/LocationManager.java
private void requestLocationUpdates(LocationRequest request, LocationListener listener,
Looper looper, PendingIntent intent) {
...
try {
mService.requestLocationUpdates(request, transport, intent, packageName);
...
/com/android/server/LocationManagerService.java
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
PendingIntent intent, String packageName) {
if (request == null) request = DEFAULT_LOCATION_REQUEST;
checkPackageName(packageName);
<!--关键函数 1 ,查询Manifest文件,是否进行了权限声明 -->
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
request.getProvider());
。。。
<!--获取调用app的pid跟uid-->
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
// providers may use public location API's, need to clear identity
long identity = Binder.clearCallingIdentity();
try {
<!--关键函数 2 检查是否动态授权了权限,或者拒绝了权限-->
checkLocationAccess(uid, packageName, allowedResolutionLevel);
synchronized (mLock) {
Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
packageName, workSource, hideFromAppOps);
if (receiver != null) {
requestLocationUpdatesLocked(sanitizedRequest, receiver, pid,
uid, packageName);
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
getCallerAllowedResolutionLevel主要通过调用getAllowedResolutionLevel查询APP是否在Manifest中进行了声明
private int getCallerAllowedResolutionLevel() {
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
pid, uid) == PackageManager.PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_FINE;
} else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
pid, uid) == PackageManager.PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_COARSE;
} else {
return RESOLUTION_LEVEL_NONE;
}
}
checkLocationAccess这里才是动态鉴权的入口,在checkLocationAccess函数中,会调用mAppOps.checkOp去鉴权,mAppOps就是AppOpsManager实例,
boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
int mode = mAppOps.checkOp(op, uid, packageName);
if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_ASK ) {
return false;
}
}
return true;
}
进而通过Binder向AppOpsService服务发送鉴权请求
public int noteOp(int op, int uid, String packageName) {
try {
int mode = mService.noteOperation(op, uid, packageName);
if (mode == MODE_ERRORED) {
throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
return mode;
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
AppOpsService负责动态权限的鉴定跟更新,接着看noteOperation代码
@Override
public int noteOperation(int code, int uid, String packageName) {
final Result userDialogResult;
verifyIncomingUid(uid);
verifyIncomingOp(code);
synchronized (this) {
Ops ops = getOpsLocked(uid, packageName, true);
...
<!--关键点 1-->
if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
switchOp.mode == AppOpsManager.MODE_ERRORED) {
op.rejectTime = System.currentTimeMillis();
op.ignoredCount++;
return switchOp.mode;
<!--关键点 2-->
} else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {
op.time = System.currentTimeMillis();
op.rejectTime = 0;
op.allowedCount++;
return AppOpsManager.MODE_ALLOWED;
} else {
op.noteOpCount++;
<!--关键函数 3-->
userDialogResult = askOperationLocked(code, uid, packageName,
switchOp);
}
}
return userDialogResult.get();
}
在上面的代码里面,1、2是对已经处理过的场景直接返回已授权,或者已经拒绝,而3就是我们常见授权入口对话框,这里是统一在AppOpsServie中进行授权处理的。askOperationLocked会显示一个系统对话框,用户选择授权或者拒绝后,AppOpsServie会将选择记录在案,并通知申请服务提供或者拒绝服务。askOperationLocked通过mHandler发送鉴权Message,看一下实现其实就是新建了一个PermissionDialog授权对话框,并且将AppOpsService的引用传了进去,授权后会通过mService.notifyOperation通知授权结果。
mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PERMISSION_DIALOG: {
HashMap<String, Object> data =
(HashMap<String, Object>) msg.obj;
synchronized (this) {
Op op = (Op) data.get("op");
Result res = (Result) data.get("result");
op.dialogResult.register(res);
if(op.dialogResult.mDialog == null) {
Integer code = (Integer) data.get("code");
Integer uid = (Integer) data.get("uid");
String packageName =
(String) data.get("packageName");
Dialog d = new PermissionDialog(mContext,
AppOpsService.this, code, uid,
packageName);
op.dialogResult.mDialog = (PermissionDialog)d;
d.show();
}
}
}break;
}
}
};
在Android4.3到5.1之间,虽然App可以获得AppOpsManager的实例,但是真正动态操作权限的接口setMode却被隐藏,如下
/** @hide */
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
}
}
遍历源码也只有NotificationManagerService这个系统应用使用了setMode,也就是说发行版,只有通知是通过系统的通知管理进行动态管理的。
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
checkCallerIsSystem();
Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
}
}
Android6.0的runtime-permission机制让用户在任何时候都可以取消授权,因此,每次在申请系统服务的时候,都要动态查询是否获取了相应的权限,如果没有获取,就需要动态去申请,首先先看一下权限的查询:
support-v4兼容包里面提供了一个工具类PermissionChecker,可以用来检查权限获取情况。
PermissionChecker
public static int checkPermission(@NonNull Context context, @NonNull String permission,
int pid, int uid, String packageName) {
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
String op = AppOpsManagerCompat.permissionToOp(permission);
if (op == null) {
return PERMISSION_GRANTED;
}
if (packageName == null) {
String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
if (packageNames == null || packageNames.length <= 0) {
return PERMISSION_DENIED;
}
packageName = packageNames[0];
}
if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
!= AppOpsManagerCompat.MODE_ALLOWED) {
return PERMISSION_DENIED_APP_OP;
}
return PERMISSION_GRANTED;
}
这里我们只关心context.checkPermission,从上面对于4.3-5.1的APPOpsManager的分析,我们知道AppOpsManagerCompat本身的一些操作对于权限管理并没有实际意义,只是用来做一些标记,最多就是对于通知权限有些用,接下来看checkPermission:
ContextImple.java
/** @hide */
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
try {
return ActivityManagerNative.getDefault().checkPermissionWithToken(
permission, pid, uid, callerToken);
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
}
接着往下看
ActivityManagerNative.java
public int checkPermission(String permission, int pid, int uid)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(permission);
data.writeInt(pid);
data.writeInt(uid);
mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
data.recycle();
reply.recycle();
return res;
}
ActivityManagerService
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
}
进而调用ActivityManager.checkComponentPermission,调用AppGlobals.getPackageManager().checkUidPermission(permission, uid);
ActivityManager.java
/** @hide */
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
<!--root及System能获取所有权限-->
if (uid == 0 || uid == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
。。。
<!--普通的权限查询-->
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
// Should never happen, but if it does... deny!
Slog.e(TAG, "PackageManager is dead?!?", e);
}
return PackageManager.PERMISSION_DENIED;
}
最终调用PackageManagerService.java去查看是否有权限,到这里,我们只需要知道权限的查询其实是通过PKMS来进行的。心里先有个底,权限的更新,持久化,恢复都是通过PKMS来进行的。
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) {
GrantedPermissions gp = (GrantedPermissions)obj;
if (gp.grantedPermissions.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
<!--mSystemPermissions记录一些系统级的应用的 uid 对应的 permission->
HashSet<String> perms = mSystemPermissions.get(uid);
if (perms != null && perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
}
if (!isPermissionEnforcedLocked(permName, enforcedDefault)) {
return PackageManager.PERMISSION_GRANTED;
}
}
return PackageManager.PERMISSION_DENIED;
}
@Override
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
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;
}
// Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
}
return PackageManager.PERMISSION_DENIED;
}
可以看到Android6.0之后,对权限的操作是PermissionsState
PermissionsState.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)
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);
}
从上面的代码可以很清晰看出,6.0之后,除了声明了权限之外,还必须是授权了的。运行时权限跟install权限有所不同,对于install权限isGranted一直返回是True,这里先不必深究PermissionsState是怎么存进内存,先记住,后面会将讲。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者李尚授权发布。