Android权限管理原理(含6.0)

前言

在德惠等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供成都网站建设、成都网站制作 网站设计制作定制开发,公司网站建设,企业网站建设,成都品牌网站建设,成都营销网站建设,外贸网站建设,德惠网站建设费用合理。

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

 
 
 
 
  1. private void requestLocationUpdates(LocationRequest request, LocationListener listener,
  2.         Looper looper, PendingIntent intent) {
  3.      ...
  4.     try {
  5.         mService.requestLocationUpdates(request, transport, intent, packageName);
  6.      ...   

/com/android/server/LocationManagerService.java

 
 
 
 
  1. @Override 
  2. public void requestLocationUpdates(LocationRequest request, ILocationListener listener, 
  3.         PendingIntent intent, String packageName) { 
  4.     if (request == null) request = DEFAULT_LOCATION_REQUEST; 
  5.     checkPackageName(packageName); 
  6.      
  7.     int allowedResolutionLevel = getCallerAllowedResolutionLevel(); 
  8.     checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, 
  9.             request.getProvider()); 
  10.     。。。 
  11.      
  12.     final int pid = Binder.getCallingPid(); 
  13.     final int uid = Binder.getCallingUid(); 
  14.     // providers may use public location API's, need to clear identity 
  15.     long identity = Binder.clearCallingIdentity(); 
  16.     try { 
  17.      
  18.         checkLocationAccess(uid, packageName, allowedResolutionLevel); 
  19.  
  20.         synchronized (mLock) { 
  21.             Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, 
  22.                     packageName, workSource, hideFromAppOps); 
  23.             if (receiver != null) { 
  24.                     requestLocationUpdatesLocked(sanitizedRequest, receiver, pid, 
  25.                                                  uid, packageName); 
  26.             } 
  27.         } 
  28.     } finally { 
  29.         Binder.restoreCallingIdentity(identity); 
  30.     } 

getCallerAllowedResolutionLevel主要通过调用getAllowedResolutionLevel查询APP是否在Manifest中进行了声明

 
 
 
 
  1. private int getCallerAllowedResolutionLevel() {
  2.     return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
  3. }
  4.  private int getAllowedResolutionLevel(int pid, int uid) {
  5.      if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
  6.              pid, uid) == PackageManager.PERMISSION_GRANTED) {
  7.          return RESOLUTION_LEVEL_FINE;
  8.      } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
  9.              pid, uid) == PackageManager.PERMISSION_GRANTED) {
  10.          return RESOLUTION_LEVEL_COARSE;
  11.      } else {
  12.          return RESOLUTION_LEVEL_NONE;
  13.      }
  14.  } 

checkLocationAccess这里才是动态鉴权的入口,在checkLocationAccess函数中,会调用mAppOps.checkOp去鉴权,mAppOps就是AppOpsManager实例,

 
 
 
 
  1. boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
  2.     int op = resolutionLevelToOp(allowedResolutionLevel);
  3.     if (op >= 0) {
  4.         int mode = mAppOps.checkOp(op, uid, packageName);
  5.         if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_ASK ) {
  6.             return false;
  7.         }
  8.     }
  9.     return true;

进而通过Binder向AppOpsService服务发送鉴权请求

 
 
 
 
  1.  public int noteOp(int op, int uid, String packageName) {
  2.     try {
  3.         int mode = mService.noteOperation(op, uid, packageName);
  4.         if (mode == MODE_ERRORED) {
  5.             throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
  6.         }
  7.         return mode;
  8.     } catch (RemoteException e) {
  9.     }
  10.     return MODE_IGNORED;

AppOpsService负责动态权限的鉴定跟更新,接着看noteOperation代码

 
 
 
 
  1. @Override
  2. public int noteOperation(int code, int uid, String packageName) {
  3.     final Result userDialogResult;
  4.     verifyIncomingUid(uid);
  5.     verifyIncomingOp(code);
  6.     synchronized (this) {
  7.         Ops ops = getOpsLocked(uid, packageName, true);
  8.           ...
  9.           
  10.         if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
  11.             switchOp.mode == AppOpsManager.MODE_ERRORED) {
  12.             op.rejectTime = System.currentTimeMillis();
  13.             op.ignoredCount++;
  14.             return switchOp.mode;
  15.            
  16.         } else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {
  17.             op.time = System.currentTimeMillis();
  18.             op.rejectTime = 0;
  19.             op.allowedCount++;
  20.             return AppOpsManager.MODE_ALLOWED;
  21.         } else {
  22.             op.noteOpCount++;
  23.             
  24.             userDialogResult = askOperationLocked(code, uid, packageName,
  25.                 switchOp);
  26.         }
  27.     }
  28.     return userDialogResult.get();

在上面的代码里面,1、2是对已经处理过的场景直接返回已授权,或者已经拒绝,而3就是我们常见授权入口对话框,这里是统一在AppOpsServie中进行授权处理的。askOperationLocked会显示一个系统对话框,用户选择授权或者拒绝后,AppOpsServie会将选择记录在案,并通知申请服务提供或者拒绝服务。askOperationLocked通过mHandler发送鉴权Message,看一下实现其实就是新建了一个PermissionDialog授权对话框,并且将AppOpsService的引用传了进去,授权后会通过mService.notifyOperation通知授权结果。

 
 
 
 
  1. mHandler = new Handler() {
  2.             public void handleMessage(Message msg) {
  3.                 switch (msg.what) {
  4.                 case SHOW_PERMISSION_DIALOG: {
  5.                     HashMap data =
  6.                         (HashMap) msg.obj;
  7.                     synchronized (this) {
  8.                         Op op = (Op) data.get("op");
  9.                         Result res = (Result) data.get("result");
  10.                         op.dialogResult.register(res);
  11.                         if(op.dialogResult.mDialog == null) {
  12.                             Integer code = (Integer) data.get("code");
  13.                             Integer uid  = (Integer) data.get("uid");
  14.                             String packageName =
  15.                                 (String) data.get("packageName");
  16.                             Dialog d = new PermissionDialog(mContext,
  17.                                 AppOpsService.this, code, uid,
  18.                                 packageName);
  19.                             op.dialogResult.mDialog = (PermissionDialog)d;
  20.                             d.show();
  21.                         }
  22.                     }
  23.                 }break;
  24.                 }
  25.             }
  26.         }; 

Android发行版源码对于动态权限管理的支持(几乎为零)

在Android4.3到5.1之间,虽然App可以获得AppOpsManager的实例,但是真正动态操作权限的接口setMode却被隐藏,如下

 
 
 
 
  1. /** @hide */
  2. public void setMode(int code, int uid, String packageName, int mode) {
  3.     try {
  4.         mService.setMode(code, uid, packageName, mode);
  5.     } catch (RemoteException e) {
  6.     }

遍历源码也只有NotificationManagerService这个系统应用使用了setMode,也就是说发行版,只有通知是通过系统的通知管理进行动态管理的。

 
 
 
 
  1. public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
  2.     checkCallerIsSystem();
  3.     Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
  4.     mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
  5.             enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
  6.     // Now, cancel any outstanding notifications that are part of a just-disabled app
  7.     if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
  8.         cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
  9.     }

Android 6.0权限管理原理

Android6.0的runtime-permission机制让用户在任何时候都可以取消授权,因此,每次在申请系统服务的时候,都要动态查询是否获取了相应的权限,如果没有获取,就需要动态去申请,首先先看一下权限的查询:

Android6.0权限查询

support-v4兼容包里面提供了一个工具类PermissionChecker,可以用来检查权限获取情况。

PermissionChecker

 
 
 
 
  1. public static int checkPermission(@NonNull Context context, @NonNull String permission,
  2.         int pid, int uid, String packageName) {
  3.     if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
  4.         return PERMISSION_DENIED;
  5.     }
  6.     String op = AppOpsManagerCompat.permissionToOp(permission);
  7.     if (op == null) {
  8.         return PERMISSION_GRANTED;
  9.     }
  10.     if (packageName == null) {
  11.         String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
  12.         if (packageNames == null || packageNames.length <= 0) {
  13.             return PERMISSION_DENIED;
  14.         }
  15.         packageName = packageNames[0];
  16.     }
  17.     if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
  18.             != AppOpsManagerCompat.MODE_ALLOWED) {
  19.         return PERMISSION_DENIED_APP_OP;
  20.     }
  21.     return PERMISSION_GRANTED;

这里我们只关心context.checkPermission,从上面对于4.3-5.1的APPOpsManager的分析,我们知道AppOpsManagerCompat本身的一些操作对于权限管理并没有实际意义,只是用来做一些标记,最多就是对于通知权限有些用,接下来看checkPermission:

ContextImple.java

 
 
 
 
  1. /** @hide */
  2. @Override
  3. public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
  4.     if (permission == null) {
  5.         throw new IllegalArgumentException("permission is null");
  6.     }
  7.     try {
  8.         return ActivityManagerNative.getDefault().checkPermissionWithToken(
  9.                 permission, pid, uid, callerToken);
  10.     } catch (RemoteException e) {
  11.         return PackageManager.PERMISSION_DENIED;
  12.     }

接着往下看

ActivityManagerNative.java

 
 
 
 
  1. public int checkPermission(String permission, int pid, int uid)
  2.         throws RemoteException {
  3.     Parcel data = Parcel.obtain();
  4.     Parcel reply = Parcel.obtain();
  5.     data.writeInterfaceToken(IActivityManager.descriptor);
  6.     data.writeString(permission);
  7.     data.writeInt(pid);
  8.     data.writeInt(uid);
  9.     mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
  10.     reply.readException();
  11.     int res = reply.readInt();
  12.     data.recycle();
  13.     reply.recycle();
  14.     return res;

ActivityManagerService

 
 
 
 
  1. public int checkPermission(String permission, int pid, int uid) {
  2.     if (permission == null) {
  3.         return PackageManager.PERMISSION_DENIED;
  4.     }
  5.     return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);

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

ActivityManager.java

 
 
 
 
  1. /** @hide */
  2. public static int checkComponentPermission(String permission, int uid,
  3.         int owningUid, boolean exported) {
  4.     // Root, system server get to do everything.
  5.     
  6.     
  7.     if (uid == 0 || uid == Process.SYSTEM_UID) {
  8.         return PackageManager.PERMISSION_GRANTED;
  9.     }
  10.         。。。
  11.     
  12.     try {
  13.         return AppGlobals.getPackageManager()
  14.                 .checkUidPermission(permission, uid);
  15.     } catch (RemoteException e) {
  16.         // Should never happen, but if it does... deny!
  17.         Slog.e(TAG, "PackageManager is dead?!?", e);
  18.     }
  19.     return PackageManager.PERMISSION_DENIED;

最终调用PackageManagerService.java去查看是否有权限,到这里,我们只需要知道权限的查询其实是通过PKMS来进行的。心里先有个底,权限的更新,持久化,恢复都是通过PKMS来进行的。

PKMS不同版本的权限查询

Android5.0的checkUidPermission

 
 
 
 
  1. public int checkUidPermission(String permName, int uid) {
  2.         final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
  3.         synchronized (mPackages) {
  4.         
  5.             Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
  6.             if (obj != null) {
  7.                 GrantedPermissions gp = (GrantedPermissions)obj;
  8.                 if (gp.grantedPermissions.contains(permName)) {
  9.                     return PackageManager.PERMISSION_GRANTED;
  10.                 }
  11.             } else {
  12.