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