Google从4.3开始就试图引入AppOpsManager动态权限管理模型,可是,因为感受技术不太成熟,在Release版本中,这个功能都是被隐藏掉的,因此官方Rom一直没有动态权限管理机制。直到Android6.0,为了简化安装流程且方便用户控制权限,Google正式推出了runtime permission机制,至此,Android才算有了一套成熟的动态权限管理机制。正如咱们看到的,在MarshMallow以前,全部的权限都是在安装的时候授予,而在6.0以后,容许用户在运行的时候动态控制权限。java
但国产手机厂商比较另类,虽然6.0以前,Google的正式版本没有动态权限管理,国内手机厂商却将Google隐藏的权限管理给用了起来,若是不了解清楚权限管理的原理,在开发过程当中对6.0作权限适配的时候就没法彻底放心。所以本文主要涉及如下几部份内容:android
Android6.0以前的动态权限管理模型及原理--AppOpsManager数组
Android6.0及以后的动态权限管理原理--runtime permissionapp
两种权限的特色与区别异步
AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google以为不成熟,在每一个发行版的时候,总会将这个功能给屏蔽掉。该功能跟国产ROM的动态权限管理表现相似,这里用CyanogenMod12的源码进行分析,(国内的ROM源码拿不到,不过从表现来看,实现应该相似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每一个服务内部,好比,若是App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了当前App定位权限,若是须要受权,就弹出一个系统对话框让用户操做,并根据用户的操做将结果持久化在文件中,若是用户主动在Setting里更新了相应的权限,也会去更新,并持久化到文件/data/system/appops.xml,下次再次申请服务的时候,服务便可以选择性鉴定权限,具体看以下分析:ide
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是查看该精度是否被支持,不深究,oop
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是否须要受权以及是否被受权过:post
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(); }
关键点一、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推出后,这套不成熟的权限管理也算被遗弃了。其大概流程以下图,
在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 app if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) { cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid)); } }
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
@Override public 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() { @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); } }); } }
若是系统是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服务,
@Override public 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。
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>
既然有持久化,那就必定有恢复,持久化的数据会在手机从新启动的时候由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里,普通权限也支持运行时权限的模型,只不过,普通权限在安装时就已经算是获取了,其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>
对于Android6.0以前的不完善的权限管理模型,其鉴权与申请权限的触点都发生在请求系统服务的时候,由系统服务统一请求AppopsManager去鉴权,这个点在各个系统服务内部,由AppOpsService服务统一管理,但这种操做方式系统干预太多,不太利于APP自主控制权限。而6.0采用了鉴权与申请分开的作法,APP端能够先查询一下本身是否有某种权限,若是没有再去申请,避免服务端参与权限管理的混淆,更加清晰灵活。