Android权限管理原理(含6.0)

移动开发 Android
Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的。在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限。

前言

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.     <!--关键函数 1 ,查询Manifest文件,是否进行了权限声明 -->  
  7.     int allowedResolutionLevel = getCallerAllowedResolutionLevel();  
  8.     checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,  
  9.             request.getProvider());  
  10.     。。。  
  11.     <!--获取调用app的pid跟uid-->  
  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.     <!--关键函数 2 检查是否动态授权了权限,或者拒绝了权限-->  
  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.     }  
  31. }  

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
  10.  

进而通过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; 
  11.  

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.           <!--关键点 1--> 
  10.         if (switchOp.mode == AppOpsManager.MODE_IGNORED || 
  11.             switchOp.mode == AppOpsManager.MODE_ERRORED) { 
  12.  
  13.             op.rejectTime = System.currentTimeMillis(); 
  14.             op.ignoredCount++; 
  15.             return switchOp.mode; 
  16.            <!--关键点 2--> 
  17.         } else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) { 
  18.  
  19.             op.time = System.currentTimeMillis(); 
  20.             op.rejectTime = 0; 
  21.             op.allowedCount++; 
  22.             return AppOpsManager.MODE_ALLOWED; 
  23.         } else { 
  24.             op.noteOpCount++; 
  25.             <!--关键函数 3--> 
  26.             userDialogResult = askOperationLocked(code, uid, packageName, 
  27.                 switchOp); 
  28.         } 
  29.     } 
  30.     return userDialogResult.get(); 
  31.  

在上面的代码里面,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<String, Object> data = 
  6.                         (HashMap<String, Object>) 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.     } 
  7.  

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

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

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.  
  7.     String op = AppOpsManagerCompat.permissionToOp(permission); 
  8.     if (op == null) { 
  9.         return PERMISSION_GRANTED; 
  10.     } 
  11.  
  12.     if (packageName == null) { 
  13.         String[] packageNames = context.getPackageManager().getPackagesForUid(uid); 
  14.         if (packageNames == null || packageNames.length <= 0) { 
  15.             return PERMISSION_DENIED; 
  16.         } 
  17.         packageName = packageNames[0]; 
  18.     } 
  19.  
  20.     if (AppOpsManagerCompat.noteProxyOp(context, op, packageName) 
  21.             != AppOpsManagerCompat.MODE_ALLOWED) { 
  22.         return PERMISSION_DENIED_APP_OP; 
  23.     } 
  24.  
  25.     return PERMISSION_GRANTED; 
  26.  

这里我们只关心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.     } 
  13.  

接着往下看

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; 
  15.  

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); 
  6.  

进而调用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.     <!--root及System能获取所有权限--> 
  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; 
  20.  

最终调用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.         <!--PackageManagerService.Setting.mUserIds数组中,根据uid查找uid(也就是package)的权限列表--> 
  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.             <!--mSystemPermissions记录一些系统级的应用的 uid 对应的 permission-> 
  13.                 HashSet<String> perms = mSystemPermissions.get(uid); 
  14.                 if (perms != null && perms.contains(permName)) { 
  15.                     return PackageManager.PERMISSION_GRANTED; 
  16.                 } 
  17.             } 
  18.             if (!isPermissionEnforcedLocked(permName, enforcedDefault)) { 
  19.                 return PackageManager.PERMISSION_GRANTED; 
  20.             } 
  21.         } 
  22.         return PackageManager.PERMISSION_DENIED; 
  23.     }  

Android6.0+的checkUidPermission

  1. @Override 
  2.     public int checkUidPermission(String permName, int uid) { 
  3.         final int userId = UserHandle.getUserId(uid); 
  4.  
  5.         if (!sUserManager.exists(userId)) { 
  6.             return PackageManager.PERMISSION_DENIED; 
  7.         } 
  8.  
  9.         synchronized (mPackages) { 
  10.             Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); 
  11.             if (obj != null) { 
  12.                 final SettingBase ps = (SettingBase) obj; 
  13.                 final PermissionsState permissionsState = ps.getPermissionsState(); 
  14.                 if (permissionsState.hasPermission(permName, userId)) { 
  15.                     return PackageManager.PERMISSION_GRANTED; 
  16.                 } 
  17.                 // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION 
  18.                 if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState 
  19.                         .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { 
  20.                     return PackageManager.PERMISSION_GRANTED; 
  21.                 } 
  22.             } else { 
  23.                 ArraySet<String> perms = mSystemPermissions.get(uid); 
  24.                 if (perms != null) { 
  25.                     if (perms.contains(permName)) { 
  26.                         return PackageManager.PERMISSION_GRANTED; 
  27.                     } 
  28.                     if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms 
  29.                             .contains(Manifest.permission.ACCESS_FINE_LOCATION)) { 
  30.                         return PackageManager.PERMISSION_GRANTED; 
  31.                     } 
  32.                 } 
  33.             } 
  34.         } 
  35.  
  36.         return PackageManager.PERMISSION_DENIED; 
  37.     }  

可以看到Android6.0之后,对权限的操作是PermissionsState

PermissionsState.java (android-6.0frameworksbaseservicescorejavacomandroidserverpm)

  1. public boolean hasPermission(String nameint userId) { 
  2.     enforceValidUserId(userId); 
  3.  
  4.     if (mPermissions == null) { 
  5.         return false
  6.     } 
  7.  
  8.     PermissionData permissionData = mPermissions.get(name); 
  9.     return permissionData != null && permissionData.isGranted(userId); 
  10.  

从上面的代码可以很清晰看出,6.0之后,除了声明了权限之外,还必须是授权了的。运行时权限跟install权限有所不同,对于install权限isGranted一直返回是True,这里先不必深究PermissionsState是怎么存进内存,先记住,后面会将讲。  

 

 

Android6.0动态申请权限

申请权限可以通过V4包里面的ActivityCompat,它已经对不同版本做了兼容

ActivityCompat.java

  1. public static void requestPermissions(final @NonNull Activity activity, 
  2.             final @NonNull String[] permissions, final int requestCode) { 
  3.         if (Build.VERSION.SDK_INT >= 23) { 
  4.             ActivityCompatApi23.requestPermissions(activity, permissions, requestCode); 
  5.         } else if (activity instanceof OnRequestPermissionsResultCallback) { 
  6.          
  7.             Handler handler = new Handler(Looper.getMainLooper()); 
  8.             handler.post(new Runnable() { 
  9.                 @Override 
  10.                 public void run() { 
  11.                     final int[] grantResults = new int[permissions.length]; 
  12.  
  13.                     PackageManager packageManager = activity.getPackageManager(); 
  14.                     String packageName = activity.getPackageName(); 
  15.  
  16.                     final int permissionCount = permissions.length; 
  17.                     for (int i = 0; i < permissionCount; i++) { 
  18.                         grantResults[i] = packageManager.checkPermission( 
  19.                                 permissions[i], packageName); 
  20.                     } 
  21.  
  22.                     ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult( 
  23.                             requestCode, permissions, grantResults); 
  24.                 } 
  25.             }); 
  26.         } 
  27.     }  

可以看到,如果是6.0以下,直接通过PKMS查询是否在Manifest里面申请了权限,并把查询结果通过onRequestPermissionsResult回调传给Activity或者Fragment。其实这里只要在Manifest中声明了,就会默认是Granted。接着往下看:ActivityCompatApi23最终会调用activity.requestPermissions去请求权限。

Activity

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

Intent其实是通过PackageManager(ApplicationPackageManager实现类)获取的Intent

  1.  public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { 
  2.     if (ArrayUtils.isEmpty(permissions)) { 
  3.        throw new NullPointerException("permission cannot be null or empty"); 
  4.     } 
  5.     Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); 
  6.     intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions); 
  7.     intent.setPackage(getPermissionControllerPackageName()); 
  8.     return intent; 
  9.  

这里首先是隐式的获取授权Activity组件相关信息(GrantPermissionsActivity),其实就是对话框样式的授权Activity,它是PackageInstaller系统应用里面的一个Activity。这里的getPermissionControllerPackageName其实就是获取相应的包名,

ApplicationPackageManager.java (android-6.0frameworksbasecorejavaandroidapp)

  1. @Override 
  2. public String getPermissionControllerPackageName() { 
  3.     synchronized (mLock) { 
  4.         if (mPermissionsControllerPackageName == null) { 
  5.             try { 
  6.                 mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName(); 
  7.             } catch (RemoteException e) { 
  8.                 throw new RuntimeException("Package manager has died", e); 
  9.             } 
  10.         } 
  11.         return mPermissionsControllerPackageName; 
  12.     } 
  13.  

最终通过PackageManagerService获取包名

PackageManagerService.java (android-6.0frameworksbaseservicescorejavacomandroidserverpm)

  1. @Override 
  2. public String getPermissionControllerPackageName() { 
  3.     synchronized (mPackages) { 
  4.         return mRequiredInstallerPackage; 
  5.     } 
  6.  

mRequiredInstallerPackage这个变量具体赋值是在PMS的构造器中:对于原生Android 6.0,权限管理的APP跟安装器是同一个

  1. mRequiredInstallerPackage = getRequiredInstallerLPr(); 

这里会得到PackageInstaller应用的相关信息,PackageInstaller负责应用的安装与卸载,里面还包含了对授权管理的一些逻辑。startActivityForResult启动的就是PackageInstaller中的GrantPermissionsActivity,该Activity主要负责权限的授予工作。

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

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

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

之后就是动态更新权限流程:  

 

 

如何动态更新RuntimePermission

通过上面的流程,我们进入了GrantPermissionsActivity,在这个Activity里面,如果一开始没有获得权限,就会弹出权限申请对话框,根据用户的操作去更新PKMS中的权限信息,同时将更新的结构持久化到runtime-permissions.xml中去。

GrantPermissionsActivity

GrantPermissionsActivity其实是利用GroupState对象与PKMS通信,远程更新权限的,当然,如果权限都已经授予了,那么就不需要再次弹出权限申请对话框。

  1. public class GrantPermissionsActivity extends OverlayTouchActivity 
  2.         implements GrantPermissionsViewHandler.ResultListener { 
  3.          
  4.     private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>(); 
  5.     .... 
  6.      
  7.     @Override 
  8.     public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) { 
  9.         GroupState groupState = mRequestGrantPermissionGroups.get(name); 
  10.         if (groupState.mGroup != null) { 
  11.             if (granted) { 
  12.              
  13.             <!--权限更新时机--> 
  14.                 groupState.mGroup.grantRuntimePermissions(doNotAskAgain); 
  15.                 groupState.mState = GroupState.STATE_ALLOWED; 
  16.             } else { 
  17.                 groupState.mGroup.revokeRuntimePermissions(doNotAskAgain); 
  18.                 groupState.mState = GroupState.STATE_DENIED; 
  19.             } 
  20.             updateGrantResults(groupState.mGroup); 
  21.         } 
  22.         if (!showNextPermissionGroupGrantRequest()) { 
  23.             setResultAndFinish(); 
  24.         } 
  25.     }  

具体更新流程:

  1. public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { 
  2.     final int uid = mPackageInfo.applicationInfo.uid; 
  3.  
  4.     // We toggle permissions only to apps that support runtime 
  5.     // permissions, otherwise we toggle the app op corresponding 
  6.     // to the permission if the permission is granted to the app. 
  7.     for (Permission permission : mPermissions.values()) { 
  8.         if (filterPermissions != null 
  9.                 && !ArrayUtils.contains(filterPermissions, permission.getName())) { 
  10.             continue
  11.         } 
  12.             ... 
  13.             <!--一些关键点--> 
  14.  
  15.             // Grant the permission if needed. 
  16.             if (!permission.isGranted()) { 
  17.                 permission.setGranted(true); 
  18.                 mPackageManager.grantRuntimePermission(mPackageInfo.packageName, 
  19.                         permission.getName(), mUserHandle); 
  20.             } 
  21.             // Update the permission flags. 
  22.             if (!fixedByTheUser) { 
  23.                 // Now the apps can ask for the permission as the user 
  24.                 // no longer has it fixed in a denied state. 
  25.                 if (permission.isUserFixed() || permission.isUserSet()) { 
  26.                     permission.setUserFixed(false); 
  27.                     permission.setUserSet(true); 
  28.                     mPackageManager.updatePermissionFlags(permission.getName(), 
  29.                             mPackageInfo.packageName, 
  30.                             PackageManager.FLAG_PERMISSION_USER_FIXED 
  31.                                     | PackageManager.FLAG_PERMISSION_USER_SET, 
  32.                             0, mUserHandle);  

可以看到最终还是调用PackageManager去更新App的运行时权限,最终走进PackageManagerService服务,

PackageManagerService

  1.  @Override 
  2.     public void grantRuntimePermission(String packageName, String name, final int userId) { 
  3.         if (!sUserManager.exists(userId)) { 
  4.             Log.e(TAG, "No such user:" + userId); 
  5.             return
  6.         } 
  7.          
  8.         ...一些检查 
  9.          
  10.         mContext.enforceCallingOrSelfPermission( 
  11.                 android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, 
  12.                 "grantRuntimePermission"); 
  13.  
  14.         enforceCrossUserPermission(Binder.getCallingUid(), userId, 
  15.                 true /* requireFullPermission */, true /* checkShell */, 
  16.                 "grantRuntimePermission"); 
  17.                 。。。。。 
  18.                 ... 
  19.             uid = UserHandle.getUid(userId, pkg.applicationInfo.uid); 
  20.             sb = (SettingBase) pkg.mExtras; 
  21.             if (sb == null) { 
  22.                 throw new IllegalArgumentException("Unknown package: " + packageName); 
  23.             } 
  24.  
  25.             final PermissionsState permissionsState = sb.getPermissionsState(); 
  26.                
  27.               ... 
  28.               ...授权 
  29.              
  30.             final int result = permissionsState.grantRuntimePermission(bp, userId); 
  31.             switch (result) { 
  32.                 case PermissionsState.PERMISSION_OPERATION_FAILURE: { 
  33.                     return
  34.                 } 
  35.  
  36.                 case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: { 
  37.                     final int appId = UserHandle.getAppId(pkg.applicationInfo.uid); 
  38.                     mHandler.post(new Runnable() { 
  39.                         @Override 
  40.                         public void run() { 
  41.                             killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED); 
  42.                         } 
  43.                     }); 
  44.                 } 
  45.                 break; 
  46.             } 
  47.  
  48.             mOnPermissionChangeListeners.onPermissionsChanged(uid); 
  49.              
  50.              
  51.             <!--持久化-->     
  52.              
  53.             // Not critical if that is lost - app has to request again. 
  54.             mSettings.writeRuntimePermissionsForUserLPr(userId, false); 
  55.         }   

 <!--查询是否在Manifest声明过权限-->  

  1. private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(PackageParser.Package pkg, 
  2.         BasePermission bp) { 
  3.     int index = pkg.requestedPermissions.indexOf(bp.name); 
  4.     if (index == -1) { 
  5.         throw new SecurityException("Package " + pkg.packageName 
  6.                 + " has not requested permission " + bp.name); 
  7.     } 
  8.     if (!bp.isRuntime() && !bp.isDevelopment()) { 
  9.         throw new SecurityException("Permission " + bp.name 
  10.                 + " is not a changeable permission type"); 
  11.     } 
  12.  

首先要更新内存中的权限授予情况

PermissionsState.java 

  1. private int grantPermission(BasePermission permission, int userId) { 
  2.     if (hasPermission(permission.name, userId)) { 
  3.         return PERMISSION_OPERATION_FAILURE; 
  4.     } 
  5.  
  6.     final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); 
  7.     final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; 
  8.  
  9.     PermissionData permissionData = ensurePermissionData(permission); 
  10.  
  11.     if (!permissionData.grant(userId)) { 
  12.         return PERMISSION_OPERATION_FAILURE; 
  13.     } 
  14.  
  15.     if (hasGids) { 
  16.         final int[] newGids = computeGids(userId); 
  17.         if (oldGids.length != newGids.length) { 
  18.             return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; 
  19.         } 
  20.     } 
  21.  
  22.     return PERMISSION_OPERATION_SUCCESS; 
  23.  

<!--动态添加更新内存Permison -->

  1. private PermissionData ensurePermissionData(BasePermission permission) { 
  2.     if (mPermissions == null) { 
  3.         mPermissions = new ArrayMap<>(); 
  4.     } 
  5.     PermissionData permissionData = mPermissions.get(permission.name); 
  6.     if (permissionData == null) { 
  7.         permissionData = new PermissionData(permission); 
  8.         mPermissions.put(permission.name, permissionData); 
  9.     } 
  10.     return permissionData; 

 

下一步,要将更新的权限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr

RuntimePermission持久化

Settings.java

  1. public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { 
  2.     if (sync) { 
  3.         mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId); 
  4.     } else { 
  5.         mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); 
  6.     } 

 

Settings.getPackageLPw这个方法,这是在安装应用扫描的时候scanPackageDirtyLI方法调用的,里面可以看到Settings类中的mUserIds、mPackages里面存的value还有PackageManagerService中的mPackages.pkg. mExtras都是同一个玩意奏是个PackageSetting。

  1. private File getUserRuntimePermissionsFile(int userId) { 
  2.     // TODO: Implement a cleaner solution when adding tests. 
  3.     // This instead of Environment.getUserSystemDirectory(userId) to support testing. 
  4.     File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId)); 
  5.     return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME); 

 

在目录data/system/0/runtime-permissions.xml存放需要运行时申请的权限,Android6.0以上才有

  1. <pkg name="com.snail.labaffinity"
  2.    <item name="android.permission.CALL_PHONE" granted="true" flags="0" /> 
  3.    <item name="android.permission.CAMERA" granted="false" flags="1" /> 
  4.  </pkg>  

 

 

 

RuntimePermission恢复(其实这里也包含普通权限)

这些持久化的数据会在手机启动的时候由PMS读取,开机启动,PKMS扫描Apk,并更新package信息,检查/data/system/packages.xml是否存在,这个文件是在解析apk时由writeLP()创建的,里面记录了系统的permissions,以及每个apk的name,codePath,flags,ts,version,uesrid等信息,这些信息主要通过apk的AndroidManifest.xml解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机直接从里面读取相关信息添加到内存相关列表中,当有apk升级,安装或删除时会更新这个文件,packages.xml放的只包括installpermission,runtimepermissiono由runtime-permissions.xml存放。

 

  1. public PackageManagerService(Context context, Installer installer, 
  2.         boolean factoryTest, boolean onlyCore) { 
  3.     .... 
  4.     mSettings = new Settings(mPackages); 
  5.      
  6.           //汇总并更新和Permission相关的信息 
  7.  
  8.   updatePermissionsLPw(nullnulltrue
  9.  
  10.                            regrantPermissions,regrantPermissions); 
  11.  
  12.    //将信息写到package.xml、package.list及package-stopped.xml文件中 
  13.    mSettings.writeLPr(); 
  14.     
  15.     .... 
  16.     mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false)); 
  17.  
  18. Settings(File dataDir, Object lock) { 
  19.  
  20.     mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); 
  21.  
  22. <!--加载package信息-->  

根据SettingsFile或者BackupSettingsFile读取相应的设置信息 生成PackageSetting对象,里面有权限列表字段protected final PermissionsState mPermissionsState;,之后再运行中,动态权限的操作都是针对这个对象 

  1. boolean readLPw(@NonNull List<UserInfo> users) { 
  2.     FileInputStream str = null
  3.     if (mBackupSettingsFilename.exists()) { 
  4.         try { 
  5.             str = new FileInputStream(mBackupSettingsFilename); 
  6.             mReadMessages.append("Reading from backup settings file\n"); 
  7.      ... 
  8.         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 
  9.                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 
  10.          
  11.             String tagName = parser.getName(); 
  12.             if (tagName.equals("package")) { 
  13.              
  14.        !--读取package信息,包括install权限信息(对于Android6.0package.xml)--> 
  15.      
  16.     readPackageLPw(parser);  
  17.          ... 
  18.           
  19.       <!--读取runtime权限信息--> 
  20.        
  21.     for (UserInfo user : users) { 
  22.         mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id); 
  23.     } 
  24.  
  25.  
  26. private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException { 
  27.     String name = null
  28.      ... 
  29.     (tagName.equals(TAG_PERMISSIONS)) { 
  30.             readInstallPermissionsLPr(parser, 
  31.                         packageSetting.getPermissionsState());  

之后就可以checkpermission了

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

 

 

 

原来的权限存放位置在哪?不会都从Android Manifest清单去读取,只会在启动时读取一次。Android6.0之前会吧所有的权限都放置在data/system/packages.xml文件中。Android6.0之后,分为运行时权限跟普通权限,普通权限还是放在data/system/packages.xml中,运行时权限防止在data/system/users/0/runtime-permissions.xml文件中。根据运行时是否动态申请去更新权限。

Android6.0申请普通权限会怎么样

Android6.0里,普通权限仍然按照运行时权限的模型,只是granted="true",就是永远是取得授权的。所以可以直接获得权限申请成功的回调。如果查看packages.xml,就会发现:如下信息:

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

Android的关键节点,在哪里?

关键节点并不是查询是否具有该权限,Android6.0之前的 权限查询是不会触发权限申请与授权的,只有在请求系统服务的时候,由系统服务调用AppopsManager去查询是否赋予了该权限,第一次未操作肯定是null,未赋予就可能会触发权限申请逻辑,这个点在各个系统服务内部,由AppOpsService服务统一管理,不过对于官方的Release版本,其实只有系统通知APP才有动态权限管理的能力,其他都没有操作能力。

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2023-11-07 16:24:49

成员权限管理

2019-04-11 18:25:29

Android Q权限位置

2011-02-25 13:23:19

Proftpd

2015-08-25 08:57:57

android6.0技术总结

2021-07-21 09:03:53

GoogleChrome权限

2021-04-16 10:35:14

MySQL权限管理

2018-02-09 11:05:42

Java代码框架

2015-10-08 09:03:18

Android6.0源代码

2015-10-20 09:49:25

Android 6.0加密全盘

2013-03-26 13:38:12

Android per

2019-12-05 15:45:51

SpringSecur权限系统

2010-05-17 17:44:56

IIS6.0

2011-04-13 09:53:20

2009-12-23 15:55:53

Linux权限管理

2013-11-21 09:10:27

MongoDB

2017-03-20 19:01:20

Linux管理员系统用户

2010-04-09 17:35:22

2013-05-14 10:37:10

AIR Android设置访问权限

2019-10-16 16:31:59

权限Android程序

2023-12-20 10:14:24

点赞
收藏

51CTO技术栈公众号