Android動態權限管理模型(4.3-6.0)

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

  • 兩種權限的特色與區別異步

Android6.0以前的動態權限管理模型(官方預演)-- AppOpsManager(4.3源碼)

AppOpsManager是Google在Android4.3引入的動態權限管理方式,不過,Google以爲不成熟,在每一個發行版的時候,總會將這個功能給屏蔽掉。該功能跟國產ROM的動態權限管理表現相似,這裏用CyanogenMod12的源碼進行分析,(國內的ROM源碼拿不到,不過從表現來看,實現應該相似)。AppOpsManager實現的動態管理的本質是:將鑑權放在每一個服務內部,好比,若是App要申請定位權限,定位服務LocationManagerService會向AppOpsService查詢是否授予了當前App定位權限,若是須要受權,就彈出一個系統對話框讓用戶操做,並根據用戶的操做將結果持久化在文件中,若是用戶主動在Setting裏更新了相應的權限,也會去更新,並持久化到文件/data/system/appops.xml,下次再次申請服務的時候,服務便可以選擇性鑑定權限,具體看以下分析:ide

舉個栗子:定位服務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是查看該精度是否被支持,不深究,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推出後,這套不成熟的權限管理也算被遺棄了。其大概流程以下圖,

AppOpsManager動態權限管理流程

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 app
    if (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

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

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

相關文章
相關標籤/搜索