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端能夠先查詢一下本身是否有某種權限,若是沒有再去申請,避免服務端參與權限管理的混淆,更加清晰靈活。