Android权限管理原理

liuchuan 7年前
   <h2>前言</h2>    <p>Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的。在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,并且在相应的时机动态申请权限,在适配了Android6.0的App运行在Android 6.0+的手机上时,就会调用6.0相关的API,不过在低版本的手机上,仍然是按安装时权限处理。</p>    <h2>AppOpsManager动态权限管理:官方预演的权限管理</h2>    <p>AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google觉得不成熟,所以在每个发行版的时候,总是会将这个功能给屏蔽掉。该功能跟国内的权限动态管理表现类似,这里用CyanogenMod12里面的实现讲述一下,(国内的ROM源码拿不到,不过从表现来看,实现应该类似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每个服务内部,比如,如果App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了App定位权限,如果需要授权,就弹出一个系统对话框让用户操作,并根据用户的操作将结果持久化在文件中,如果在Setting里设置了响应的权限,也会去更新相应的权限操作持久化文件/data/system/appops.xml,下次再次申请服务的时候,服务会再次鉴定权限。</p>    <h2>举个栗子-定位服务LocationManagerService: CM12源码</h2>    <p>App在使用定位服务的时候,一般是通过LocationManager的requestLocationUpdates获取定位,其实是通过Binder请求LocationManagerService去定位。</p>    <p>/android/location/LocationManager.java</p>    <pre>  <code class="language-java">private void requestLocationUpdates(LocationRequest request, LocationListener listener,          Looper looper, PendingIntent intent) {       ...      try {          mService.requestLocationUpdates(request, transport, intent, packageName);       ...</code></pre>    <p>/com/android/server/LocationManagerService.java</p>    <pre>  <code class="language-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);      }  }</code></pre>    <p>getCallerAllowedResolutionLevel主要通过调用getAllowedResolutionLevel查询APP是否在Manifest中进行了声明</p>    <pre>  <code class="language-java">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;       }   }</code></pre>    <p>checkLocationAccess这里才是动态鉴权的入口,在checkLocationAccess函数中,会调用mAppOps.checkOp去鉴权,mAppOps就是AppOpsManager实例,</p>    <pre>  <code class="language-java">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;  }</code></pre>    <p>进而通过Binder向AppOpsService服务发送鉴权请求</p>    <pre>  <code class="language-java">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;  }</code></pre>    <p>AppOpsService负责动态权限的鉴定跟更新,接着看noteOperation代码</p>    <pre>  <code class="language-java">@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();  }</code></pre>    <p>在上面的代码里面,1、2是对已经处理过的场景直接返回已授权,或者已经拒绝,而3就是我们常见授权入口对话框,这里是统一在AppOpsServie中进行授权处理的。askOperationLocked会显示一个系统对话框,用户选择授权或者拒绝后,AppOpsServie会将选择记录在案,并通知申请服务提供或者拒绝服务。askOperationLocked通过mHandler发送鉴权Message,看一下实现其实就是新建了一个PermissionDialog授权对话框,并且将AppOpsService的引用传了进去,授权后会通过mService.notifyOperation通知授权结果。</p>    <pre>  <code class="language-java">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;                  }              }          };</code></pre>    <p><img src="https://simg.open-open.com/show/37453a7f5789b714e27739f81830cf15.png"></p>    <p>整体流程</p>    <h2>Android发行版源码对于动态权限管理的支持(几乎为零)</h2>    <p>在Android4.3到5.1之间,虽然App可以获得AppOpsManager的实例,但是真正动态操作权限的接口setMode却被隐藏,如下</p>    <pre>  <code class="language-java">/** @hide */  public void setMode(int code, int uid, String packageName, int mode) {      try {          mService.setMode(code, uid, packageName, mode);      } catch (RemoteException e) {      }  }</code></pre>    <p>遍历源码也只有NotificationManagerService这个系统应用使用了setMode,也就是说发行版,只有通知是通过系统的通知管理进行动态管理的。</p>    <pre>  <code class="language-java">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));      }  }</code></pre>    <h2>Android 6.0权限管理原理</h2>    <p>Android6.0的runtime-permission机制让用户在任何时候都可以取消授权,因此,每次在申请系统服务的时候,都要动态查询是否获取了相应的权限,如果没有获取,就需要动态去申请,首先先看一下权限的查询:</p>    <h2>Android6.0权限查询</h2>    <p>support-v4兼容包里面提供了一个工具类PermissionChecker,可以用来检查权限获取情况。</p>    <p>PermissionChecker</p>    <pre>  <code class="language-java">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;  }</code></pre>    <p>这里我们只关心context.checkPermission,从上面对于4.3-5.1的APPOpsManager的分析,我们知道AppOpsManagerCompat本身的一些操作对于权限管理并没有实际意义,只是用来做一些标记,最多就是对于通知权限有些用,接下来看checkPermission:</p>    <p>ContextImple.java</p>    <pre>  <code class="language-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;      }  }</code></pre>    <p>接着往下看</p>    <p>ActivityManagerNative.java</p>    <pre>  <code class="language-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;  }</code></pre>    <p>ActivityManagerService</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>进而调用ActivityManager.checkComponentPermission,调用AppGlobals.getPackageManager().checkUidPermission(permission, uid);</p>    <p>ActivityManager.java</p>    <pre>  <code class="language-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;  }</code></pre>    <p>最终调用PackageManagerService.java去查看是否有权限,到这里,我们只需要知道权限的查询其实是通过PKMS来进行的。心里先有个底,权限的更新,持久化,恢复都是通过PKMS来进行的。</p>    <h2>PKMS不同版本的权限查询</h2>    <h3>Android5.0的checkUidPermission</h3>    <pre>  <code class="language-java">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;      }</code></pre>    <h3>Android6.0+的checkUidPermission</h3>    <pre>  <code class="language-java">@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;      }</code></pre>    <p>可以看到Android6.0之后,对权限的操作是PermissionsState</p>    <p>PermissionsState.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>从上面的代码可以很清晰看出,6.0之后,除了声明了权限之外,还必须是授权了的。运行时权限跟install权限有所不同,对于install权限isGranted一直返回是True,这里先不必深究PermissionsState是怎么存进内存,先记住,后面会将讲。</p>    <p><img src="https://simg.open-open.com/show/af0ee91a6f149b1effa5388e346d0c1c.png"></p>    <p>权限恢复流程</p>    <h2>Android6.0动态申请权限</h2>    <p>申请权限可以通过V4包里面的ActivityCompat,它已经对不同版本做了兼容</p>    <p>ActivityCompat.java</p>    <pre>  <code class="language-java">public static void requestPermissions(final @NonNull Activity activity,              final @NonNull String[] permissions, final int requestCode) {          if (Build.VERSION.SDK_INT >= 23) {              ActivityCompatApi23.requestPermissions(activity, permissions, requestCode);          } else if (activity instanceof OnRequestPermissionsResultCallback) {                Handler handler = new Handler(Looper.getMainLooper());              handler.post(new Runnable() {                  @Override                  public void run() {                      final int[] grantResults = new int[permissions.length];                        PackageManager packageManager = activity.getPackageManager();                      String packageName = activity.getPackageName();                        final int permissionCount = permissions.length;                      for (int i = 0; i < permissionCount; i++) {                          grantResults[i] = packageManager.checkPermission(                                  permissions[i], packageName);                      }                        ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(                              requestCode, permissions, grantResults);                  }              });          }      }</code></pre>    <p>可以看到,如果是6.0以下,直接通过PKMS查询是否在Manifest里面申请了权限,并把查询结果通过onRequestPermissionsResult回调传给Activity或者Fragment。其实这里只要在Manifest中声明了,就会默认是Granted。接着往下看:ActivityCompatApi23最终会调用activity.requestPermissions去请求权限。</p>    <p>Activity</p>    <pre>  <code class="language-java">public final void requestPermissions(@NonNull String[] permissions, int requestCode) {      Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);      startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);  }</code></pre>    <p>Intent其实是通过PackageManager(ApplicationPackageManager实现类)获取的Intent</p>    <pre>  <code class="language-java">public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {      if (ArrayUtils.isEmpty(permissions)) {         throw new NullPointerException("permission cannot be null or empty");      }      Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);      intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);      intent.setPackage(getPermissionControllerPackageName());      return intent;  }</code></pre>    <p>这里首先是隐式的获取授权Activity组件相关信息(GrantPermissionsActivity),其实就是对话框样式的授权Activity,它是PackageInstaller系统应用里面的一个Activity。这里的getPermissionControllerPackageName其实就是获取相应的包名,</p>    <p>ApplicationPackageManager.java (android-6.0\frameworks\base\core\java\android\app)</p>    <pre>  <code class="language-java">@Override  public String getPermissionControllerPackageName() {      synchronized (mLock) {          if (mPermissionsControllerPackageName == null) {              try {                  mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();              } catch (RemoteException e) {                  throw new RuntimeException("Package manager has died", e);              }          }          return mPermissionsControllerPackageName;      }  }</code></pre>    <p>最终通过PackageManagerService获取包名</p>    <p>PackageManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)</p>    <pre>  <code class="language-java">@Override  public String getPermissionControllerPackageName() {      synchronized (mPackages) {          return mRequiredInstallerPackage;      }  }</code></pre>    <p>mRequiredInstallerPackage这个变量具体赋值是在PMS的构造器中:对于原生Android 6.0,权限管理的APP跟安装器是同一个</p>    <pre>  <code class="language-java">mRequiredInstallerPackage = getRequiredInstallerLPr();</code></pre>    <p>这里会得到PackageInstaller应用的相关信息,PackageInstaller负责应用的安装与卸载,里面还包含了对授权管理的一些逻辑。startActivityForResult启动的就是PackageInstaller中的GrantPermissionsActivity,该Activity主要负责权限的授予工作。</p>    <pre>  <code class="language-java"><activity android:name=".permission.ui.GrantPermissionsActivity"              android:configChanges="orientation|keyboardHidden|screenSize"              android:excludeFromRecents="true"              android:theme="@style/GrantPermissions">          <intent-filter>              <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />              <category android:name="android.intent.category.DEFAULT" />          </intent-filter>      </activity></code></pre>    <p>这是一个类似于对话框的悬浮窗样式的Activity</p>    <pre>  <code class="language-java"><style name="GrantPermissions" parent="Settings">      <item name="android:windowIsFloating">true</item>      <item name="android:windowElevation">@dimen/action_dialog_z</item>      <item name="android:windowSwipeToDismiss">false</item>  </style></code></pre>    <p>之后就是动态更新权限流程:</p>    <p><img src="https://simg.open-open.com/show/c8294132af7af765c0f762461a030b1f.png"></p>    <p>权限恢复流程</p>    <h2>如何动态更新RuntimePermission</h2>    <p>通过上面的流程,我们进入了GrantPermissionsActivity,在这个Activity里面,如果一开始没有获得权限,就会弹出权限申请对话框,根据用户的操作去更新PKMS中的权限信息,同时将更新的结构持久化到runtime-permissions.xml中去。</p>    <p>GrantPermissionsActivity</p>    <p>GrantPermissionsActivity其实是利用GroupState对象与PKMS通信,远程更新权限的,当然,如果权限都已经授予了,那么就不需要再次弹出权限申请对话框。</p>    <pre>  <code class="language-java">public class GrantPermissionsActivity extends OverlayTouchActivity          implements GrantPermissionsViewHandler.ResultListener {        private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>();      ....        @Override      public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {          GroupState groupState = mRequestGrantPermissionGroups.get(name);          if (groupState.mGroup != null) {              if (granted) {                <!--权限更新时机-->                  groupState.mGroup.grantRuntimePermissions(doNotAskAgain);                  groupState.mState = GroupState.STATE_ALLOWED;              } else {                  groupState.mGroup.revokeRuntimePermissions(doNotAskAgain);                  groupState.mState = GroupState.STATE_DENIED;              }              updateGrantResults(groupState.mGroup);          }          if (!showNextPermissionGroupGrantRequest()) {              setResultAndFinish();          }      }</code></pre>    <p>具体更新流程:</p>    <pre>  <code class="language-java">public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {      final int uid = mPackageInfo.applicationInfo.uid;        // We toggle permissions only to apps that support runtime      // permissions, otherwise we toggle the app op corresponding      // to the permission if the permission is granted to the app.      for (Permission permission : mPermissions.values()) {          if (filterPermissions != null                  && !ArrayUtils.contains(filterPermissions, permission.getName())) {              continue;          }              ...              <!--一些关键点-->                // Grant the permission if needed.              if (!permission.isGranted()) {                  permission.setGranted(true);                  mPackageManager.grantRuntimePermission(mPackageInfo.packageName,                          permission.getName(), mUserHandle);              }              // Update the permission flags.              if (!fixedByTheUser) {                  // Now the apps can ask for the permission as the user                  // no longer has it fixed in a denied state.                  if (permission.isUserFixed() || permission.isUserSet()) {                      permission.setUserFixed(false);                      permission.setUserSet(true);                      mPackageManager.updatePermissionFlags(permission.getName(),                              mPackageInfo.packageName,                              PackageManager.FLAG_PERMISSION_USER_FIXED                                      | PackageManager.FLAG_PERMISSION_USER_SET,                              0, mUserHandle);</code></pre>    <p>可以看到最终还是调用PackageManager去更新App的运行时权限,最终走进PackageManagerService服务,</p>    <p>PackageManagerService</p>    <pre>  <code class="language-java">@Override      public void grantRuntimePermission(String packageName, String name, final int userId) {          if (!sUserManager.exists(userId)) {              Log.e(TAG, "No such user:" + userId);              return;          }            ...一些检查            mContext.enforceCallingOrSelfPermission(                  android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,                  "grantRuntimePermission");            enforceCrossUserPermission(Binder.getCallingUid(), userId,                  true /* requireFullPermission */, true /* checkShell */,                  "grantRuntimePermission");                  。。。。。                  ...              uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);              sb = (SettingBase) pkg.mExtras;              if (sb == null) {                  throw new IllegalArgumentException("Unknown package: " + packageName);              }                final PermissionsState permissionsState = sb.getPermissionsState();                  ...                ...授权                final int result = permissionsState.grantRuntimePermission(bp, userId);              switch (result) {                  case PermissionsState.PERMISSION_OPERATION_FAILURE: {                      return;                  }                    case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {                      final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);                      mHandler.post(new Runnable() {                          @Override                          public void run() {                              killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);                          }                      });                  }                  break;              }                mOnPermissionChangeListeners.onPermissionsChanged(uid);                  <!--持久化-->                    // Not critical if that is lost - app has to request again.              mSettings.writeRuntimePermissionsForUserLPr(userId, false);          }</code></pre>    <p><!--查询是否在Manifest声明过权限--></p>    <pre>  <code class="language-java">private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(PackageParser.Package pkg,          BasePermission bp) {      int index = pkg.requestedPermissions.indexOf(bp.name);      if (index == -1) {          throw new SecurityException("Package " + pkg.packageName                  + " has not requested permission " + bp.name);      }      if (!bp.isRuntime() && !bp.isDevelopment()) {          throw new SecurityException("Permission " + bp.name                  + " is not a changeable permission type");      }  }</code></pre>    <p>首先要更新内存中的权限授予情况</p>    <p>PermissionsState.java</p>    <pre>  <code class="language-java">private int grantPermission(BasePermission permission, int userId) {      if (hasPermission(permission.name, userId)) {          return PERMISSION_OPERATION_FAILURE;      }        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));      final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;        PermissionData permissionData = ensurePermissionData(permission);        if (!permissionData.grant(userId)) {          return PERMISSION_OPERATION_FAILURE;      }        if (hasGids) {          final int[] newGids = computeGids(userId);          if (oldGids.length != newGids.length) {              return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;          }      }        return PERMISSION_OPERATION_SUCCESS;  }</code></pre>    <p><!--动态添加更新内存Permison --></p>    <pre>  <code class="language-java">private PermissionData ensurePermissionData(BasePermission permission) {      if (mPermissions == null) {          mPermissions = new ArrayMap<>();      }      PermissionData permissionData = mPermissions.get(permission.name);      if (permissionData == null) {          permissionData = new PermissionData(permission);          mPermissions.put(permission.name, permissionData);      }      return permissionData;  }</code></pre>    <p>下一步,要将更新的权限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr</p>    <h2>RuntimePermission持久化</h2>    <p>Settings.java</p>    <pre>  <code class="language-java">public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {      if (sync) {          mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);      } else {          mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);      }  }</code></pre>    <p>Settings.getPackageLPw这个方法,这是在安装应用扫描的时候scanPackageDirtyLI方法调用的,里面可以看到Settings类中的mUserIds、mPackages里面存的value还有PackageManagerService中的mPackages.pkg. mExtras都是同一个玩意奏是个PackageSetting。</p>    <pre>  <code class="language-java">private File getUserRuntimePermissionsFile(int userId) {      // TODO: Implement a cleaner solution when adding tests.      // This instead of Environment.getUserSystemDirectory(userId) to support testing.      File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));      return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);  }</code></pre>    <p>在目录data/system/0/runtime-permissions.xml存放需要运行时申请的权限,Android6.0以上才有</p>    <pre>  <code class="language-java"><pkg name="com.snail.labaffinity">      <item name="android.permission.CALL_PHONE" granted="true" flags="0" />      <item name="android.permission.CAMERA" granted="false" flags="1" />    </pkg></code></pre>    <h2>RuntimePermission恢复(其实这里也包含普通权限)</h2>    <p>这些持久化的数据会在手机启动的时候由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存放。</p>    <pre>  <code class="language-java">public PackageManagerService(Context context, Installer installer,          boolean factoryTest, boolean onlyCore) {      ....      mSettings = new Settings(mPackages);              //汇总并更新和Permission相关的信息      updatePermissionsLPw(null, null, true,                               regrantPermissions,regrantPermissions);       //将信息写到package.xml、package.list及package-stopped.xml文件中     mSettings.writeLPr();        ....      mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));    Settings(File dataDir, Object lock) {        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);    <!--加载package信息--></code></pre>    <p>根据SettingsFile或者BackupSettingsFile读取相应的设置信息 生成PackageSetting对象,里面有权限列表字段protected final PermissionsState mPermissionsState;,之后再运行中,动态权限的操作都是针对这个对象</p>    <pre>  <code class="language-java">boolean readLPw(@NonNull List<UserInfo> users) {      FileInputStream str = null;      if (mBackupSettingsFilename.exists()) {          try {              str = new FileInputStream(mBackupSettingsFilename);              mReadMessages.append("Reading from backup settings file\n");       ...          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT                  && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {                String tagName = parser.getName();              if (tagName.equals("package")) {           !--读取package信息,包括install权限信息(对于Android6.0package.xml)-->        readPackageLPw(parser);            ...          <!--读取runtime权限信息-->        for (UserInfo user : users) {          mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);      }  }      private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {      String name = null;       ...      (tagName.equals(TAG_PERMISSIONS)) {              readInstallPermissionsLPr(parser,                          packageSetting.getPermissionsState());</code></pre>    <p>之后就可以checkpermission了</p>    <pre>  <code class="language-java">@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;                  }</code></pre>    <p><img src="https://simg.open-open.com/show/dee94b68a9cc9dc488ada866e8e2b854.png"></p>    <p>权限恢复流程</p>    <p>原来的权限存放位置在哪?不会都从Android Manifest清单去读取,只会在启动时读取一次。Android6.0之前会吧所有的权限都放置在data/system/packages.xml文件中。Android6.0之后,分为运行时权限跟普通权限,普通权限还是放在data/system/packages.xml中,运行时权限防止在data/system/users/0/runtime-permissions.xml文件中。根据运行时是否动态申请去更新权限。</p>    <h2>Android6.0申请普通权限会怎么样</h2>    <p>Android6.0里,普通权限仍然按照运行时权限的模型,只是granted="true",就是永远是取得授权的。所以可以直接获得权限申请成功的回调。如果查看packages.xml,就会发现:如下信息:</p>    <pre>  <code class="language-java"><perms>      <item name="android.permission.INTERNET" granted="true" flags="0" />      <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />  </perms></code></pre>    <h2>Android的关键节点,在哪里?</h2>    <p>关键节点并不是查询是否具有该权限,Android6.0之前的 权限查询是不会触发权限申请与授权的,只有在请求系统服务的时候,由系统服务调用AppopsManager去查询是否赋予了该权限,第一次未操作肯定是null,未赋予就可能会触发权限申请逻辑,这个点在各个系统服务内部,由AppOpsService服务统一管理,不过对于官方的Release版本,其实只有系统通知APP才有动态权限管理的能力,其他都没有操作能力。</p>    <h2>参考文档</h2>    <p>1、 <a href="/misc/goto?guid=4959731241741925503" rel="nofollow,noindex">Android 安全機制概述 Permission</a></p>    <p>2、 <a href="/misc/goto?guid=4959731241839232021" rel="nofollow,noindex">android permission权限与安全机制解析</a></p>    <p>3、 <a href="/misc/goto?guid=4959731241919354355" rel="nofollow,noindex">android6.0权限管理原理</a></p>    <p>4、 <a href="/misc/goto?guid=4959731242003429814" rel="nofollow,noindex">深入理解 PackageManagerService</a></p>    <p>5、 <a href="/misc/goto?guid=4959731242095781706" rel="nofollow,noindex">Android 4.3 隐藏功能 App Ops 分析</a></p>    <p>6、 <a href="http://mp.weixin.qq.com/s?__biz=MzIxNzEyMzIzOA==&mid=2652313851&idx=1&sn=a15519b65e7bedefbb566fe6d01935cb&scene=4#wechat_redirect" rel="nofollow,noindex">Android 权限机制,你真的了解吗?</a></p>    <p>7、 <a href="/misc/goto?guid=4959731242278475302" rel="nofollow,noindex">Android原生权限管理:AppOps</a></p>    <p>8、 <a href="/misc/goto?guid=4959731242362822388" rel="nofollow,noindex">Android 5.1 AppOps总结</a></p>    <p>9、 <a href="/misc/goto?guid=4959731242444890773" rel="nofollow,noindex">CM12源码</a></p>    <p> </p>    <p>来自:http://www.jianshu.com/p/9938d367b6db</p>    <p> </p>