Google从4.3开始就试图引入AppOpsManager动态权限管理模型,但是,由于感觉技术不太成熟,在Release版本中,这个功能都是被隐藏掉的,所以官方Rom一直没有动态权限管理机制。直到Android6.0,为了简化安装流程且方便用户控制权限,Google正式推出了runtime permission机制,至此,Android才算有了一套成熟的动态权限管理机制。正如我们看到的,在MarshMallow之前,所有的权限都是在安装的时候授予,而在6.0之后,允许用户在运行的时候动态控制权限。

但国产手机厂商比较另类,虽然6.0之前,Google的正式版本没有动态权限管理,国内手机厂商却将Google隐藏的权限管理给用了起来,如果不了解清楚权限管理的原理,在开发过程中对6.0做权限适配的时候就无法完全放心。因此本文主要涉及以下几部分内容:

  • Android6.0之前的动态权限管理模型及原理--AppOpsManager

  • Android6.0及之后的动态权限管理原理--runtime permission

  • 两种权限的特点与区别

Android6.0之前的动态权限管理模型(官方预演)-- AppOpsManager(4.3源码)

AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google觉得不成熟,在每个发行版的时候,总会将这个功能给屏蔽掉。该功能跟国产ROM的动态权限管理表现类似,这里用CyanogenMod12的源码进行分析,(国内的ROM源码拿不到,不过从表现来看,实现应该类似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每个服务内部,比如,如果App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了当前App定位权限,如果需要授权,就弹出一个系统对话框让用户操作,并根据用户的操作将结果持久化在文件中,如果用户主动在Setting里更新了相应的权限,也会去更新,并持久化到文件/data/system/appops.xml,下次再次申请服务的时候,服务便能够选择性鉴定权限,具体看如下分析:

举个栗子:定位服务LocationManagerService: CM12源码(4.3)

App在使用定位服务的时候,一般是通过LocationManager的requestLocationUpdates获取定位,其实是通过Binder请求LocationManagerService去定位,并将结果回传给APP端,关于Binder服务原理非本文重点,不过多分析。首先看一下定位服务的常用方法:

public void requestLocation() {
<!--关键点1-->LocationManager  locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);try {if (locationManager != null) {
<!--关键点2-->locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 0, mLocationListener);}} catch (Exception e) {}
}

关键点1其实是利用ServiceManager的getService获取LocationManagerService的代理,如果获取成功,就进入关键点2 通过requestLocationUpdates请求LocationManagerService进行定位,定位结果会通过Binder通信传递给APP端,APP端再利用listener获取定位信息,省略中间过程,直接进入LocationManagerService.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();long identity = Binder.clearCallingIdentity();try {<!--关键函数 2 检查是否动态授权了权限,或者拒绝了权限-->checkLocationAccess(uid, packageName, allowedResolutionLevel);...} finally {Binder.restoreCallingIdentity(identity);}
}

首先关注下requestLocationUpdates函数中的ILocationListener参数,这其实是一个Binder对象,用于定位信息的回传。再来看关键点1,Android 4.3的鉴权机制会首先查询是否在Manifest中声明了对应权限,这是第一步,getCallerAllowedResolutionLevel通过调用getAllowedResolutionLevel查询APP是否在Manifest中进行了声明,并获得定位精度,checkResolutionLevelIsSufficientForProviderUse是查看该精度是否被支持,不深究,

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;}}

动态的鉴权动作发生在关键点2, checkLocationAccess才是定位服务动态鉴权的入口,在checkLocationAccess函数中,会向AppOpsService服务发送鉴权请求,AppOpsService 通过checkOp获知当前APP是否需要授权以及是否被授权过:

boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {int op = resolutionLevelToOp(allowedResolutionLevel);if (op >= 0) {<!--关键点1-->int mode = mAppOps.checkOp(op, uid, packageName);if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_ASK ) {return false;}}return true;
}

关键点1就是调用AppOpsService鉴权的入口,mAppOps是LocationManagerService在实例化的时候获取的AppOpsService服务代理,本质还是通过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收到请求后,会对权限进行鉴定跟更新,在国产ROM中,经常遇到一个有倒计时的授权页面,用户可以选择允许、拒绝、提示,其实这正好对应AppOpsService的几种处理方式

@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);}}<!--关键函数 4-->return userDialogResult.get();
}

关键点1、2是针对已经操作过的场景,如果是已授权状态,直接返回已授权成功,如果是拒绝状态,则直接返回授权失败,而3就是我们常见授权入口对话框:askOperationLocked会显示一个系统对话框,等待用户选择,当点击允许或者拒绝后,AppOpsServie会将操作记录在案,并通知Server是继续提供服务还是拒绝。关键点4牵扯到一个同步的问题,在国产ROM中,申请权限的线程会被阻塞(即使是UI线程),这是因为鉴权的Binder通信是同步的,并且,服务端一直等到用户操作后才将结果返回给客户端,这就导致了客户端请求线程一直阻塞,直到用户操作结束。askOperationLocked通过mHandler发送鉴权Message,并返回一个支持阻塞操作的PermissionDialogResult.Result,通过其get函数阻塞等待操作结束,看一下具体的处理

public AppOpsService(File storagePath) {mStrictEnable = AppOpsManager.isStrictEnable();mFile = new AtomicFile(storagePath);mLooper = Looper.myLooper();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");<!--关键点1-->Dialog d = new PermissionDialog(mContext,AppOpsService.this, code, uid,packageName);op.dialogResult.mDialog = (PermissionDialog)d;d.show();}}}break;}}};readWhitelist();readState();
}

关键点1:新建了一个系统PermissionDialog,并显示,而上面的PermissionDialogResult.Result的get()函数会让服务端的Binder线程一直阻塞,这个超时小于系统设置ANR的时间,所以不用担心ANR,直到AppOpsService线程操作完毕,通过notifyAll通知Binder线程操作结束,才会将结果返回APP端,唤醒阻塞等待的APP,简单原理如下

   class PermissionDialogResult {public final static class Result {<!--关键点1 唤醒-->public void set(int res) {synchronized (this) {mHasResult = true;mResult = res;notifyAll();} }<!--关键点2 Binder线程阻塞等待-->public int get() {synchronized (this) {while (!mHasResult) {try {wait();} catch (InterruptedException e) {}  } }return mResult; }boolean mHasResult = false;int mResult;}<!--关键点3 其他线程唤醒Binder线程的入口-->public void notifyAll(int mode) {synchronized(this) {while(resultList.size() != 0) {Result res = resultList.get(0);res.set(mode);resultList.remove(0);}  }}}

这种动态权限管理的模型的缺点是:在真正使用服务之前,并不知道自己是否具备权限,需要先请求服务,由相应的服务向AppOpsService申请鉴权,也就说,权限由服务+AppOpsService来维护,不够灵活自由,这也可能是Google一直没有放开的原因,等到Android 6.0 runtim-permmission推出后,这套不成熟的权限管理也算被遗弃了。其大概流程如下图,

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

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

/** @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();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 appif (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));}
}

Android 6.0权限管理原理

Android6.0开始,原生支持runtime-permission机制,用户在任何时候都可以授权/取消授权,并且APP能够在请求服务之前知晓是否已经获得所需要的权限,如此,APP端能够根据需求,自主控制权限的申请,更加灵活。首先先看一下权限的查询,如何知道自己是否已经获取了某项权限:support-v4兼容包里面提供了一个工具类PermissionChecker,可以用来检查权限获取情况。

public static int checkPermission(@NonNull Context context, @NonNull String permission,int pid, int uid, String packageName) {<!--关键点1 -->if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {return PERMISSION_DENIED;}...return PERMISSION_GRANTED;
}

这里我们只关心比较重要的关键点1 context.checkPermission,它最最终会通过ActivityManagerNative将请求发送给ActivityManagerService,

/** @hide */
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {try {return ActivityManagerNative.getDefault().checkPermissionWithToken(permission, pid, uid, callerToken);} catch (RemoteException e) {return PackageManager.PERMISSION_DENIED;}
}

ActivityManagerService端对应的处理是


int checkComponentPermission(String permission, int pid, int uid,int owningUid, boolean exported) {if (pid == MY_PID) {return PackageManager.PERMISSION_GRANTED;}return ActivityManager.checkComponentPermission(permission, uid,owningUid, exported);
}

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

/** @hide */
public static int checkComponentPermission(String permission, int uid,int owningUid, boolean exported) {<!--root及System进程能获取所有权限-->if (uid == 0 || uid == Process.SYSTEM_UID) {return PackageManager.PERMISSION_GRANTED;}。。。<!--普通应用的权限查询-->try {return AppGlobals.getPackageManager().checkUidPermission(permission, uid);} catch (RemoteException e) {}return PackageManager.PERMISSION_DENIED;
}

最终调用PackageManagerService.java去查看是否有某种权限,到这里,可以知道,权限的查询其实是通过PKMS来进行的,后面还会看到权限的更新,持久化,恢复也是通过PKMS来进行的。权限的查询函数checkUidPermission在不同的版本都是支持的,只不过Android6.0的实现跟之前的版本有很大不同,先看一下Android5.0的checkUidPermission:主要是通过Setting获取当前APP的权限列表,对于6.0之前的APP,这些权限都是静态申请的,或者说只要在Menifest文件中声明了,这里就认为是申请了。

 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) {<!--关键点1 -->GrantedPermissions gp = (GrantedPermissions)obj;if (gp.grantedPermissions.contains(permName)) {return PackageManager.PERMISSION_GRANTED;}} ...return PackageManager.PERMISSION_DENIED;}

GrantedPermissions是一个APP所对应权限的集合,内部有一个权限列表 HashSet<String> grantedPermissions = new HashSet<String>(),只要权限在Menifest中申请了,该列表中就会包含其对应的字符串,完全是静态的。但是6.0的runtime-permmison就不同了,看一下Android6.0+的checkUidPermission

 @Overridepublic int checkUidPermission(String permName, int uid) {final int userId = UserHandle.getUserId(uid);...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;}} ...}return PackageManager.PERMISSION_DENIED;}

Android6.0之后,APP权限状态对应的是PermissionsState对象,判断是否拥有某种权限,仅仅在Menifest中声明了是不够的:


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);
}

从上面的代码可以很清晰看出,6.0之后,除了声明了权限之外,还必须是授权了的,判断流程大概如下,接下来看一下动态权限的申请:

动态申请权限

通过上面的权限查询,可以知道是否具备权限,如果没有则需要申请,Android6.0动态申请权限可以通过V4包里面的ActivityCompat来进行,它已经对不同版本做了兼容:

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() {@Overridepublic 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);}});}}

如果系统是6.0以下,ActivityCompat会直接通过PKMS查询是否在Manifest里面申请了权限,如果申请了就默认具备该权限,并通过onRequestPermissionsResult将结果回传给Activity或者Fragment。对于6.0+的会走下面的分支,调用activity.requestPermissions去申请权限。

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

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

public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);intent.setPackage(getPermissionControllerPackageName());return intent;
}

上面函数的作用主要是获取悬浮授权Activity组件信息:其实就是GrantPermissionsActivity,它是PackageInstaller系统应用里面的一个Activity,细节不在深究,可自己查询。总之这里会获得PackageInstaller的GrantPermissionsActivity,并且启动它。PackageInstaller负责应用的安装与卸载,里面同时包含了对授权管理的一些逻辑,简单看下GrantPermissionsActivity样式,类似于对话框:

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

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

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

GrantPermissionsActivity启动之后就是动态更新权限流程,这里跟之前4.3引入的AppOpsService有所不同,6.0的权限申请一定是异步的,它不会阻塞请求线程,因为它走的是startActivityForResult流程,遵循Activity声明周期。

动态更新权限

通过上面的流程,我们进入了GrantPermissionsActivity,根据用户的操作去更新PKMS中的权限信息,至于为什么要跟PKMS通信,因为PKMS是权限信息的维护者,权限在内存中的管理以及权限的持久化都是由PKMS负责,后面会看到PKMS会将权限持久化到runtime-permissions.xml中去。当然,如果权限都已经授予了,就不需要再次进入GrantPermissionsActivity(内部判断)。直接看一下授权操作:

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {final int uid = mPackageInfo.applicationInfo.uid;for (Permission permission : mPermissions.values()) {...<!--授权-->// Grant the permission if needed.if (!permission.isGranted()) {permission.setGranted(true);mPackageManager.grantRuntimePermission(mPackageInfo.packageName,permission.getName(), mUserHandle);}}

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

 @Overridepublic void grantRuntimePermission(String packageName, String name, final int userId) {<!--关键点1 查询是不是在Menifest中声明过-->enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);sb = (SettingBase) pkg.mExtras;final PermissionsState permissionsState = sb.getPermissionsState();...<!--关键点2授权-->final int result = permissionsState.grantRuntimePermission(bp, userId);...<!--关键点3 持久化-->    // Not critical if that is lost - app has to request again.mSettings.writeRuntimePermissionsForUserLPr(userId, false);}

关键点1 :enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission函数是为了确定申请的敏感权限是在Menifest中声明过,不然会直接抛出异常导致崩溃。关键点2,就是授权操作,其实就是更新内存中App端申请的权限信息,最后的关键点3 是为了将权限持久化到本地文件,这样在手机重启后,才能保证之前保存的权限不丢失,先看下PermissionsState对于权限信息在内存中的操作:

 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;
}

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

 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;
}

最终是将信息更新到Setting对象中,下一步,就是将更新的权限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr。

Runtime-Permission权限的持久化

mSettings.writeRuntimePermissionsForUserLPr会将更新的权限持久化到本地文件,

 public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {if (sync) {mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);} else {mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);}
}

具体持久化到哪里呢?

 private void writePermissionsSync(int userId) {AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));...FileOutputStream out = null;try {out = destination.startWrite();...}}private File getUserRuntimePermissionsFile(int userId) {File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}

getUserRuntimePermissionsFile的值是目录data/system/0/runtime-permissions.xml,运行时权限都存放在该文件中,这个文件只有Android6.0以上才有,内容如下形式:应用包名+权限名+授权状态

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

Runtime-Permission恢复

既然有持久化,那就一定有恢复,持久化的数据会在手机重新启动的时候由PKMS读取。开机时,PKMS扫描Apk,将APK AndroidManifest中的信息按照需求更新到内存或者/data/system/packages.xml文件,在权限管理方面,packages.xml主要包含的是install permission,就是一些不太敏感的权限,只要Menifest中声明了,就默认已经获取,不需要动态申请,之后APK升级、安装、卸载时,都会更新packages.xml,而运行时权限则存放在data/system/0/runtime-permissions.xml中,同样在启动时读取:

boolean readLPw(@NonNull List<UserInfo> users) {FileInputStream str = null;...      <!--关键点1--读取package信息,包括install权限信息(对于Android6.0package.xml)-->readPackageLPw(parser); ...<!--关键点2 读取runtime permmsion权限信息-->for (UserInfo user : users) {mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);}
}

关键点1对应静态APK信息及静态权限 ,关键点2对应动态权限的恢复读取,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",并且没有取消入口,所以永远是取得授权的,在申请intall权限时,会直接走申请成功分支。如果查看packages.xml,会发现与分析对应:

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

Android动态管理权限的关键节点在哪里

对于Android6.0之前的不完善的权限管理模型,其鉴权与申请权限的触点都发生在请求系统服务的时候,由系统服务统一请求AppopsManager去鉴权,这个点在各个系统服务内部,由AppOpsService服务统一管理,但这种操作方式系统干预太多,不太利于APP自主控制权限。而6.0采用了鉴权与申请分开的做法,APP端可以先查询一下自己是否有某种权限,如果没有再去申请,避免服务端参与权限管理的混淆,更加清晰灵活。

Android动态权限管理模型(4.3-6.0)相关推荐

  1. Android 系统(80)---Android 8.0 7.0 6.0 动态权限管理

    Android 8.0 7.0 6.0 动态权限管理 1.Android6.0之后运行时权限策略变化 从Android6.0(API23)开始,对系统权限做了很大的改变,在之前用户安装app前,只是把 ...

  2. 谈谈Android 6.0 的动态权限管理

    Android适配系列: Android 6.0 的动态权限管理 Android 7.0脱坑指南 Android 8.0适配指北 Android 9.0 适配指南 1.前言 大家都知道Android ...

  3. Android 6.0: 动态权限管理的解决方案

    Android 6.0版本(Api 23)推出了很多新的特性, 大幅提升了用户体验, 同时也为程序员带来新的负担. 动态权限管理就是这样, 一方面让用户更加容易的控制自己的隐私, 一方面需要重新适配应 ...

  4. 一个Android动态权限的流式权限管理库EasyPermission,帮你申请动态权限

    转载请注明出处https://blog.csdn.net/yeluofengchui/article/details/91126117 已经出2.0版本了,使用更方便,详情见EasyPermissio ...

  5. Android权限管理原理(含6.0)

    前言 Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在 ...

  6. Spring Security 中最流行的权限管理模型!

    前面和大家说了 ACL,讲了理论,也给了一个完整的案例,相信小伙伴们对于 ACL 权限控制模型都已经比较了解了. 本文我要和大家聊一聊另外一个非常流行的权限管理模型,那就是 RBAC. 1.RBAC ...

  7. Android踩坑日记:Android动态权限分析和解决方案

    关于运行时权限 在旧的权限管理系统中,权限仅仅在APP安装时询问一次,用户同意了这些权限App才能安装,APP一旦安装后就可以偷偷做很多不为人知的事情. 我们知道从Android6.0开始,App可以 ...

  8. Android动态权限详解

    1 什么是动态权限 去年底,上级主管部门为加强国内Android应用隐私管理,出台了一系列规定,我们的App也做了相应的修改.主要一条修改为,隐私提示与权限获取顺序.修改测试过程中,发觉部分同学对An ...

  9. 宅急送项目的第七天笔记!(JBPM工作流和介绍 -- 权限管理模型)

    一.回顾第一天核心内容  1. JBPM是什么? 为我的项目带来什么?  工作流, 就是将业务流程实现自动化,非人工方式,控制任务的执行 -------- 所有业务流程 执行信息 都可以存放到数据库 ...

最新文章

  1. iOS开发之圆角指定
  2. IISExpress Log 文件路径
  3. 前端学习(3176):react-hello-react之脚手架配置2
  4. mysql infobright 缺点_infobright、mongodb优劣以及适用范围
  5. mysql主从有关参数_mysql主从复制配置
  6. python动态图表变化_Python数据可视化 pyecharts实现各种统计图表过程详解
  7. stm32中断优先级_浅谈STM32串口USART1的使用
  8. Component 父子组件关系
  9. javscript DOM基础知识(常用篇)
  10. FP-XH 松下PLC控制器资料下载
  11. 使用Excel批量生成SQL语句
  12. win的反义词_全新整理小学英语常见的120对反义词大全,快来收藏学习吧
  13. 5款优秀的文档管理系统
  14. nginx 区分手机浏览器和pc浏览器
  15. html 毛笔书写效果,canvas 手写毛笔字效果
  16. web前端(HTML的CSS样式和JavaScript)
  17. 第三方数据源大型库| CnOpenData全球专利及引用被引用数据
  18. iOS开发 - App语言国际化
  19. 网易Java开发岗面试分享
  20. 织梦dedecms程序安全设置

热门文章

  1. devc 能优化吗_SEO关键词推广要多少钱?关键词优化选择外包靠谱吗?
  2. Swift监听网络状态
  3. UIView的几个枚举定义
  4. (0047)iOS开发之nil/Nil/NULL的区别
  5. List集合与Array数组之间的互相转换
  6. 二叉树 —— 中序遍历结点的后继
  7. xcode armv6 armv7 armv7s arm64
  8. WebView加载HTML时禁止超链接跳转
  9. sql随机查询数据语句(NewID(),Rnd,Rand(),random())
  10. OAF[1]开发环境的配置