Android系統在MarshMallow以前,權限都是在安裝的時候授予的,雖然在4.3時,Google就試圖在源碼裏面引入AppOpsManager來達到動態控制權限的目的,但因爲不太成熟,在Release版本中都是把這個功能給隱藏掉的。在6.0以後,Google爲了簡化安裝流程且方便用戶控制權限,正式引入了runtime-permission,容許用戶在運行的時候動態控制權限。對於開發而言就是將targetSdkVersion設置爲23,而且在相應的時機動態申請權限,在適配了Android6.0的App運行在Android 6.0+的手機上時,就會調用6.0相關的API,不過在低版本的手機上,仍然是按安裝時權限處理。html
AppOpsManager是Google在Android4.3引入的動態權限管理方式,不過,Google以爲不成熟,因此在每一個發行版的時候,老是會將這個功能給屏蔽掉。該功能跟國內的權限動態管理表現相似,這裏用CyanogenMod12裏面的實現講述一下,(國內的ROM源碼拿不到,不過從表現來看,實現應該相似)。AppOpsManager實現的動態管理的本質是:將鑑權放在每一個服務內部,好比,若是App要申請定位權限,定位服務LocationManagerService會向AppOpsService查詢是否授予了App定位權限,若是須要受權,就彈出一個系統對話框讓用戶操做,並根據用戶的操做將結果持久化在文件中,若是在Setting裏設置了響應的權限,也會去更新相應的權限操做持久化文件/data/system/appops.xml,下次再次申請服務的時候,服務會再次鑑定權限。java
App在使用定位服務的時候,通常是經過LocationManager的requestLocationUpdates獲取定位,實際上是經過Binder請求LocationManagerService去定位。android
/android/location/LocationManager.javagit
private void requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper, PendingIntent intent) { ... try { mService.requestLocationUpdates(request, transport, intent, packageName); .../com/android/server/LocationManagerService.javagithub
@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); } }
getCallerAllowedResolutionLevel主要經過調用getAllowedResolutionLevel查詢APP是否在Manifest中進行了聲明數組
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; } }
checkLocationAccess這裏纔是動態鑑權的入口,在checkLocationAccess函數中,會調用mAppOps.checkOp去鑑權,mAppOps就是AppOpsManager實例,安全
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; }
進而經過Binder向AppOpsService服務發送鑑權請求app
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負責動態權限的鑑定跟更新,接着看noteOperation代碼ide
@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(); }
在上面的代碼裏面,一、2是對已經處理過的場景直接返回已受權,或者已經拒絕,而3就是咱們常見受權入口對話框,這裏是統一在AppOpsServie中進行受權處理的。askOperationLocked會顯示一個系統對話框,用戶選擇受權或者拒絕後,AppOpsServie會將選擇記錄在案,並通知申請服務提供或者拒絕服務。askOperationLocked經過mHandler發送鑑權Message,看一下實現其實就是新建了一個PermissionDialog受權對話框,而且將AppOpsService的引用傳了進去,受權後會經過mService.notifyOperation通知受權結果。函數
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; } } };
在Android4.3到5.1之間,雖然App能夠得到AppOpsManager的實例,可是真正動態操做權限的接口setMode卻被隱藏,以下
/** @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(); 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)); } }
Android6.0的runtime-permission機制讓用戶在任什麼時候候均可以取消受權,所以,每次在申請系統服務的時候,都要動態查詢是否獲取了相應的權限,若是沒有獲取,就須要動態去申請,首先先看一下權限的查詢:
support-v4兼容包裏面提供了一個工具類PermissionChecker,能夠用來檢查權限獲取狀況。
PermissionChecker
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; }
這裏咱們只關心context.checkPermission,從上面對於4.3-5.1的APPOpsManager的分析,咱們知道AppOpsManagerCompat自己的一些操做對於權限管理並無實際意義,只是用來作一些標記,最多就是對於通知權限有些用,接下來看checkPermission:
ContextImple.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; } }
接着往下看
ActivityManagerNative.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; }
ActivityManagerService
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); }進而調用ActivityManager.checkComponentPermission,調用AppGlobals.getPackageManager().checkUidPermission(permission, uid);
ActivityManager.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; }最終調用PackageManagerService.java去查看是否有權限,到這裏,咱們只須要知道權限的查詢實際上是經過PKMS來進行的。內心先有個底,權限的更新,持久化,恢復都是經過PKMS來進行的。
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; }
@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; }
能夠看到Android6.0以後,對權限的操做是PermissionsState
PermissionsState.java (android-6.0frameworksbaseservicescorejavacomandroidserverpm)
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以後,除了聲明瞭權限以外,還必須是受權了的。運行時權限跟install權限有所不一樣,對於install權限isGranted一直返回是True,這裏先沒必要深究PermissionsState是怎麼存進內存,先記住,後面會將講。
申請權限能夠經過V4包裏面的ActivityCompat,它已經對不一樣版本作了兼容
ActivityCompat.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); } }); } }
能夠看到,若是是6.0如下,直接經過PKMS查詢是否在Manifest裏面申請了權限,並把查詢結果經過onRequestPermissionsResult回調傳給Activity或者Fragment。其實這裏只要在Manifest中聲明瞭,就會默認是Granted。接着往下看:ActivityCompatApi23最終會調用activity.requestPermissions去請求權限。
Activity
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) { 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; }
這裏首先是隱式的獲取受權Activity組件相關信息(GrantPermissionsActivity),其實就是對話框樣式的受權Activity,它是PackageInstaller系統應用裏面的一個Activity。這裏的getPermissionControllerPackageName其實就是獲取相應的包名,
ApplicationPackageManager.java (android-6.0frameworksbasecorejavaandroidapp)
@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; } }
最終經過PackageManagerService獲取包名
PackageManagerService.java (android-6.0frameworksbaseservicescorejavacomandroidserverpm)
@Override public String getPermissionControllerPackageName() { synchronized (mPackages) { return mRequiredInstallerPackage; } }
mRequiredInstallerPackage這個變量具體賦值是在PMS的構造器中:對於原生Android 6.0,權限管理的APP跟安裝器是同一個
mRequiredInstallerPackage = getRequiredInstallerLPr();
這裏會獲得PackageInstaller應用的相關信息,PackageInstaller負責應用的安裝與卸載,裏面還包含了對受權管理的一些邏輯。startActivityForResult啓動的就是PackageInstaller中的GrantPermissionsActivity,該Activity主要負責權限的授予工做。
<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,在這個Activity裏面,若是一開始沒有得到權限,就會彈出權限申請對話框,根據用戶的操做去更新PKMS中的權限信息,同時將更新的結構持久化到runtime-permissions.xml中去。
GrantPermissionsActivity
GrantPermissionsActivity實際上是利用GroupState對象與PKMS通訊,遠程更新權限的,固然,若是權限都已經授予了,那麼就不須要再次彈出權限申請對話框。
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(); } }
具體更新流程:
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);
能夠看到最終仍是調用PackageManager去更新App的運行時權限,最終走進PackageManagerService服務,
PackageManagerService
@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); }<!--查詢是否在Manifest聲明過權限-->
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"); } }
首先要更新內存中的權限授予狀況
PermissionsState.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; }
<!--動態添加更新內存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; }
下一步,要將更新的權限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr
Settings.java
public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { if (sync) { mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId); } else { mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); } }
Settings.getPackageLPw這個方法,這是在安裝應用掃描的時候scanPackageDirtyLI方法調用的,裏面能夠看到Settings類中的mUserIds、mPackages裏面存的value還有PackageManagerService中的mPackages.pkg. mExtras都是同一個玩意奏是個PackageSetting。
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); }
在目錄data/system/0/runtime-permissions.xml存放須要運行時申請的權限,Android6.0以上纔有
<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>
這些持久化的數據會在手機啓動的時候由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存放。
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信息-->
根據SettingsFile或者BackupSettingsFile讀取相應的設置信息 生成PackageSetting對象,裏面有權限列表字段protected final PermissionsState mPermissionsState;,以後再運行中,動態權限的操做都是針對這個對象
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());
以後就能夠checkpermission了
@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; }
原來的權限存放位置在哪?不會都從Android Manifest清單去讀取,只會在啓動時讀取一次。Android6.0以前會吧全部的權限都放置在data/system/packages.xml文件中。Android6.0以後,分爲運行時權限跟普通權限,普通權限仍是放在data/system/packages.xml中,運行時權限防止在data/system/users/0/runtime-permissions.xml文件中。根據運行時是否動態申請去更新權限。
Android6.0裏,普通權限仍然按照運行時權限的模型,只是granted="true",就是永遠是取得受權的。因此能夠直接得到權限申請成功的回調。若是查看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去查詢是否賦予了該權限,第一次未操做確定是null,未賦予就可能會觸發權限申請邏輯,這個點在各個系統服務內部,由AppOpsService服務統一管理,不過對於官方的Release版本,其實只有系統通知APP纔有動態權限管理的能力,其餘都沒有操做能力。
一、Android 安全機制概述 Permission
二、android permission權限與安全機制解析
三、android6.0權限管理原理
四、深刻理解 PackageManagerService
五、Android 4.3 隱藏功能 App Ops 分析
六、Android 權限機制,你真的瞭解嗎?
七、Android原生權限管理:AppOps
八、 Android 5.1 AppOps總結
九、CM12源碼