前言

在德惠等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供成都网站建设、成都网站制作 网站设计制作定制开发,公司网站建设,企业网站建设,成都品牌网站建设,成都营销网站建设,外贸网站建设,德惠网站建设费用合理。
Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的。在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,并且在相应的时机动态申请权限,在适配了Android6.0的App运行在Android 6.0+的手机上时,就会调用6.0相关的API,不过在低版本的手机上,仍然是按安装时权限处理。
AppOpsManager动态权限管理:官方预演的权限管理
AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google觉得不成熟,所以在每个发行版的时候,总是会将这个功能给屏蔽掉。该功能跟国内的权限动态管理表现类似,这里用CyanogenMod12里面的实现讲述一下,(国内的ROM源码拿不到,不过从表现来看,实现应该类似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每个服务内部,比如,如果App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了App定位权限,如果需要授权,就弹出一个系统对话框让用户操作,并根据用户的操作将结果持久化在文件中,如果在Setting里设置了响应的权限,也会去更新相应的权限操作持久化文件/data/system/appops.xml,下次再次申请服务的时候,服务会再次鉴定权限。
举个栗子-定位服务LocationManagerService: CM12源码
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);
 - int allowedResolutionLevel = getCallerAllowedResolutionLevel();
 - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
 - request.getProvider());
 - 。。。
 - 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 {
 - 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);
 - ...
 - if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
 - switchOp.mode == AppOpsManager.MODE_ERRORED) {
 - op.rejectTime = System.currentTimeMillis();
 - op.ignoredCount++;
 - return switchOp.mode;
 - } else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {
 - op.time = System.currentTimeMillis();
 - op.rejectTime = 0;
 - op.allowedCount++;
 - return AppOpsManager.MODE_ALLOWED;
 - } else {
 - op.noteOpCount++;
 - 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
 data = - (HashMap
 ) 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;
 - }
 - }
 - };
 
Android发行版源码对于动态权限管理的支持(几乎为零)
在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));
 - }
 - }
 
Android 6.0权限管理原理
Android6.0的runtime-permission机制让用户在任何时候都可以取消授权,因此,每次在申请系统服务的时候,都要动态查询是否获取了相应的权限,如果没有获取,就需要动态去申请,首先先看一下权限的查询:
Android6.0权限查询
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.
 - 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来进行的。
PKMS不同版本的权限查询
Android5.0的checkUidPermission
- public int checkUidPermission(String permName, int uid) {
 - final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
 - synchronized (mPackages) {
 - Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
 - if (obj != null) {
 - GrantedPermissions gp = (GrantedPermissions)obj;
 - if (gp.grantedPermissions.contains(permName)) {
 - return PackageManager.PERMISSION_GRANTED;
 - }
 - } else {