Android權(quán)限管理原理

Android系統(tǒng)在MarshMallow之前,權(quán)限都是在安裝的時候授予的,雖然在4.3時,Google就試圖在源碼里面引入AppOpsManager來達到動態(tài)控制權(quán)限的目的,但由于不太成熟,在Release版本中都是把這個功能給隱藏掉的。在6.0之后,Google為了簡化安裝流程且方便用戶控制權(quán)限,正式引入了runtime-permission,允許用戶在運行的時候動態(tài)控制權(quán)限。對于開發(fā)而言就是將targetSdkVersion設(shè)置為23,并且在相應(yīng)的時機動態(tài)申請權(quán)限,在適配了Android6.0的App運行在Android 6.0+的手機上時,就會調(diào)用6.0相關(guān)的API,不過在低版本的手機上,仍然是按安裝時權(quán)限處理。

AppOpsManager動態(tài)權(quán)限管理:官方預(yù)演的權(quán)限管理

AppOpsManager是Google在Android4.3引入的動態(tài)權(quán)限管理方式,不過,Google覺得不成熟,所以在每個發(fā)行版的時候,總是會將這個功能給屏蔽掉。該功能跟國內(nèi)的權(quán)限動態(tài)管理表現(xiàn)類似,這里用CyanogenMod12里面的實現(xiàn)講述一下,(國內(nèi)的ROM源碼拿不到,不過從表現(xiàn)來看,實現(xiàn)應(yīng)該類似)。AppOpsManager實現(xiàn)的動態(tài)管理的本質(zhì)是:將鑒權(quán)放在每個服務(wù)內(nèi)部,比如,如果App要申請定位權(quán)限,定位服務(wù)LocationManagerService會向AppOpsService查詢是否授予了App定位權(quán)限,如果需要授權(quán),就彈出一個系統(tǒng)對話框讓用戶操作,并根據(jù)用戶的操作將結(jié)果持久化在文件中,如果在Setting里設(shè)置了響應(yīng)的權(quán)限,也會去更新相應(yīng)的權(quán)限操作持久化文件/data/system/appops.xml,下次再次申請服務(wù)的時候,服務(wù)會再次鑒定權(quán)限。

舉個栗子-定位服務(wù)LocationManagerService: CM12源碼

App在使用定位服務(wù)的時候,一般是通過LocationManager的requestLocationUpdates獲取定位,其實是通過Binder請求LocationManagerService去定位。

/android/location/LocationManager.java

private void requestLocationUpdates(LocationRequest request, LocationListener listener,
        Looper looper, PendingIntent intent) {
     ...
    try {
        mService.requestLocationUpdates(request, transport, intent, packageName);
     ...

/com/android/server/LocationManagerService.java

@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
        PendingIntent intent, String packageName) {
    if (request == null) request = DEFAULT_LOCATION_REQUEST;
    checkPackageName(packageName);
    <!--關(guān)鍵函數(shù) 1 ,查詢Manifest文件,是否進行了權(quán)限聲明 -->
    int allowedResolutionLevel = getCallerAllowedResolutionLevel();
    checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
            request.getProvider());
    。。。
    <!--獲取調(diào)用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 {
    <!--關(guān)鍵函數(shù) 2 檢查是否動態(tài)授權(quán)了權(quán)限,或者拒絕了權(quán)限-->
        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主要通過調(diào)用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這里才是動態(tài)鑒權(quán)的入口,在checkLocationAccess函數(shù)中,會調(diào)用mAppOps.checkOp去鑒權(quán),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服務(wù)發(fā)送鑒權(quán)請求

 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負責(zé)動態(tài)權(quán)限的鑒定跟更新,接著看noteOperation代碼

@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);
        ...
          <!--關(guān)鍵點 1-->
        if (switchOp.mode == AppOpsManager.MODE_IGNORED ||
            switchOp.mode == AppOpsManager.MODE_ERRORED) {

            op.rejectTime = System.currentTimeMillis();
            op.ignoredCount++;
            return switchOp.mode;
           <!--關(guān)鍵點 2-->
        } else if(switchOp.mode == AppOpsManager.MODE_ALLOWED) {

            op.time = System.currentTimeMillis();
            op.rejectTime = 0;
            op.allowedCount++;
            return AppOpsManager.MODE_ALLOWED;
        } else {
            op.noteOpCount++;
            <!--關(guān)鍵函數(shù) 3-->
            userDialogResult = askOperationLocked(code, uid, packageName,
                switchOp);
        }
    }
    return userDialogResult.get();
}

在上面的代碼里面,1、2是對已經(jīng)處理過的場景直接返回已授權(quán),或者已經(jīng)拒絕,而3就是我們常見授權(quán)入口對話框,這里是統(tǒng)一在AppOpsServie中進行授權(quán)處理的。askOperationLocked會顯示一個系統(tǒng)對話框,用戶選擇授權(quán)或者拒絕后,AppOpsServie會將選擇記錄在案,并通知申請服務(wù)提供或者拒絕服務(wù)。askOperationLocked通過mHandler發(fā)送鑒權(quán)Message,看一下實現(xiàn)其實就是新建了一個PermissionDialog授權(quán)對話框,并且將AppOpsService的引用傳了進去,授權(quán)后會通過mService.notifyOperation通知授權(quán)結(jié)果。

  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;
                }
            }
        };
AppOpsManager動態(tài)權(quán)限管理流程

Android發(fā)行版源碼對于動態(tài)權(quán)限管理的支持(幾乎為零)

在Android4.3到5.1之間,雖然App可以獲得AppOpsManager的實例,但是真正動態(tài)操作權(quán)限的接口setMode卻被隱藏,如下

/** @hide */
public void setMode(int code, int uid, String packageName, int mode) {
    try {
        mService.setMode(code, uid, packageName, mode);
    } catch (RemoteException e) {
    }
}

遍歷源碼也只有NotificationManagerService這個系統(tǒng)應(yīng)用使用了setMode,也就是說發(fā)行版,只有通知是通過系統(tǒng)的通知管理進行動態(tài)管理的。

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

對于6.0以下的手機權(quán)限如何檢測

對于Android6.0以下的手機,不需要關(guān)心targetVersion。先說個自己驗證的結(jié)果:基本沒法檢測,同時也不需要檢測,就算檢測出來也沒有多大意義,因為,觸發(fā)時機是在真正的調(diào)用服務(wù)時候。對于4.3到6.0之前的國產(chǎn)ROM,雖然采用AppopsManagerService,但是并未按照Google的模型對所有權(quán)限進行適配,在這個模型下,也就適配了兩個權(quán)限,(懸浮窗還不一定有)

  • 通知權(quán)限 public static final int OP_POST_NOTIFICATION = 11;
  • 懸浮窗權(quán)限 public static final int OP_SYSTEM_ALERT_WINDOW = 24;

Google發(fā)行版的APPOpsService,基本是把整個鑒權(quán)邏輯給屏蔽了,通過CM的源碼,課對這部分代碼窺探一斑,如果整個權(quán)限都采用4.3權(quán)限管理模型,在拒絕一項權(quán)限的時候,這個操作會被持久化到appops.xml中去,但是具體看下去,其實并不是如此,這種機制只對以上兩個權(quán)限生效:

    <pkg n="com.xxx">
    <uid n="10988">
    <!--關(guān)鍵點1-->
    <op n="11" m="1" t="1513145979969" r="1521550658067" />
    <op n="12" t="1521550651593" />
    <op n="29" t="1521550682769" />

    <pkg n="com.wandoujia.phoenix2.usbproxy">
    <uid n="10969">
    <op n="4" t="1517279031173" />
     <!--關(guān)鍵點2-->
    <op n="11" m="1" t="1510889291834" r="1517279030708" />
    <op n="14" t="1517293452801" />
    <!--關(guān)鍵點3-->
    <op n="24" m="1" />
    <op n="40" t="1513599239364" d="600011" />

國產(chǎn)rom中,假如你拒絕授權(quán)位置權(quán)限,按照AppOpsService模型,該操作應(yīng)該被持久化到appops.xml中去,但是,結(jié)果并非如此,也就是說,對于其他權(quán)限,國產(chǎn)ROM應(yīng)該是自己糊弄了一套持久管理,持久化Android系統(tǒng)API無法訪問的地方,僅僅為自身ROM可見。appops.xml真正被系統(tǒng)使用時從Android6.0開始,其實Android6.0是有兩套權(quán)限管理的,這其實很混亂,不知道Google怎么想的,不過6.0似乎也有漏洞:權(quán)限的授予跟回收權(quán)限好像并不配對。

那么這就帶來了一個問題,在Android4.3到Android6.0之間的版本,并沒有同一個API來檢測是否獲取了某種權(quán)限,因為你動態(tài)更新的權(quán)限并未持久化到appops.xml中去。對于Android6.0之前的ROM,雖然不能檢測,但完全可以直接用服務(wù),不會崩潰,因為如果真需要鑒權(quán),它的鑒權(quán)時機其實是在服務(wù)使用的時候。AppopsManager在6.0之前,只能用來檢測通知,可能還有懸浮窗。

Android 6.0權(quán)限管理原理

Android6.0的runtime-permission機制讓用戶在任何時候都可以取消授權(quán),因此,每次在申請系統(tǒng)服務(wù)的時候,都要動態(tài)查詢是否獲取了相應(yīng)的權(quán)限,如果沒有獲取,就需要動態(tài)去申請,首先先看一下權(quán)限的查詢:

Android6.0權(quán)限查詢

support-v4兼容包里面提供了一個工具類PermissionChecker,可以用來檢查權(quán)限獲取情況。

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

這里我們只關(guān)心context.checkPermission,從上面對于4.3-5.1的APPOpsManager的分析,我們知道AppOpsManagerCompat本身的一些操作對于權(quán)限管理并沒有實際意義,只是用來做一些標記,最多就是對于通知權(quán)限有些用,接下來看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);
}

進而調(diào)用ActivityManager.checkComponentPermission,調(diào)用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能獲取所有權(quán)限-->
    if (uid == 0 || uid == Process.SYSTEM_UID) {
        return PackageManager.PERMISSION_GRANTED;
    }
        。。。
    <!--普通的權(quán)限查詢-->
    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;
}

最終調(diào)用PackageManagerService.java去查看是否有權(quán)限,到這里,我們只需要知道權(quán)限的查詢其實是通過PKMS來進行的。心里先有個底,權(quán)限的更新,持久化,恢復(fù)都是通過PKMS來進行的。

PKMS不同版本的權(quán)限查詢

Android5.0的checkUidPermission

 public int checkUidPermission(String permName, int uid) {
        final boolean enforcedDefault = isPermissionEnforcedDefault(permName);
        synchronized (mPackages) {
        <!--PackageManagerService.Setting.mUserIds數(shù)組中,根據(jù)uid查找uid(也就是package)的權(quán)限列表-->
            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記錄一些系統(tǒng)級的應(yīng)用的 uid 對應(yīng)的 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;
    }

Android6.0+的checkUidPermission

 @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之后,對權(quán)限的操作是PermissionsState

PermissionsState.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)

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之后,除了聲明了權(quán)限之外,還必須是授權(quán)了的。運行時權(quán)限跟install權(quán)限有所不同,對于install權(quán)限isGranted一直返回是True,這里先不必深究PermissionsState是怎么存進內(nèi)存,先記住,后面會將講。

權(quán)限檢查流程

Android6.0動態(tài)申請權(quán)限

申請權(quán)限可以通過V4包里面的ActivityCompat,它已經(jīng)對不同版本做了兼容

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里面申請了權(quán)限,并把查詢結(jié)果通過onRequestPermissionsResult回調(diào)傳給Activity或者Fragment。其實這里只要在Manifest中聲明了,就會默認是Granted。接著往下看:ActivityCompatApi23最終會調(diào)用activity.requestPermissions去請求權(quán)限。

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實現(xiàn)類)獲取的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;
}

這里首先是隱式的獲取授權(quán)Activity組件相關(guān)信息(GrantPermissionsActivity),其實就是對話框樣式的授權(quán)Activity,它是PackageInstaller系統(tǒng)應(yīng)用里面的一個Activity。這里的getPermissionControllerPackageName其實就是獲取相應(yīng)的包名,

ApplicationPackageManager.java (android-6.0\frameworks\base\core\java\android\app)

@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.0\frameworks\base\services\core\java\com\android\server\pm)

@Override
public String getPermissionControllerPackageName() {
    synchronized (mPackages) {
        return mRequiredInstallerPackage;
    }
}

mRequiredInstallerPackage這個變量具體賦值是在PMS的構(gòu)造器中:對于原生Android 6.0,權(quán)限管理的APP跟安裝器是同一個

mRequiredInstallerPackage = getRequiredInstallerLPr();

這里會得到PackageInstaller應(yīng)用的相關(guān)信息,PackageInstaller負責(zé)應(yīng)用的安裝與卸載,里面還包含了對授權(quán)管理的一些邏輯。startActivityForResult啟動的就是PackageInstaller中的GrantPermissionsActivity,該Activity主要負責(zé)權(quán)限的授予工作。

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

之后就是動態(tài)更新權(quán)限流程:

權(quán)限申請流程

如何動態(tài)更新RuntimePermission

通過上面的流程,我們進入了GrantPermissionsActivity,在這個Activity里面,如果一開始沒有獲得權(quán)限,就會彈出權(quán)限申請對話框,根據(jù)用戶的操作去更新PKMS中的權(quán)限信息,同時將更新的結(jié)構(gòu)持久化到runtime-permissions.xml中去。

GrantPermissionsActivity

GrantPermissionsActivity其實是利用GroupState對象與PKMS通信,遠程更新權(quán)限的,當(dāng)然,如果權(quán)限都已經(jīng)授予了,那么就不需要再次彈出權(quán)限申請對話框。

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) {
            
            <!--權(quán)限更新時機-->
                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;
        }
            ...
            <!--一些關(guān)鍵點-->

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

可以看到最終還是調(diào)用PackageManager去更新App的運行時權(quán)限,最終走進PackageManagerService服務(wù),

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();
              
              ...
              ...授權(quán)
            
            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);
        }
 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");
    }
}

首先要更新內(nèi)存中的權(quán)限授予情況

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

下一步,要將更新的權(quán)限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr

RuntimePermission持久化

Settings.java

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

Settings.getPackageLPw這個方法,這是在安裝應(yīng)用掃描的時候scanPackageDirtyLI方法調(diào)用的,里面可以看到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存放需要運行時申請的權(quán)限,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>
權(quán)限更新及持久化

RuntimePermission恢復(fù)(其實這里也包含普通權(quán)限)

這些持久化的數(shù)據(jù)會在手機啟動的時候由PMS讀取,開機啟動,PKMS掃描Apk,并更新package信息,檢查/data/system/packages.xml是否存在,這個文件是在解析apk時由writeLP()創(chuàng)建的,里面記錄了系統(tǒng)的permissions,以及每個apk的name,codePath,flags,ts,version,uesrid等信息,這些信息主要通過apk的AndroidManifest.xml解析獲取,解析完apk后將更新信息寫入這個文件并保存到flash,下次開機直接從里面讀取相關(guān)信息添加到內(nèi)存相關(guān)列表中,當(dāng)有apk升級,安裝或刪除時會更新這個文件,packages.xml放的只包括installpermission,runtimepermissiono由runtime-permissions.xml存放。

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    ....
    mSettings = new Settings(mPackages);
    
          //匯總并更新和Permission相關(guān)的信息

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

根據(jù)SettingsFile或者BackupSettingsFile讀取相應(yīng)的設(shè)置信息 生成PackageSetting對象,里面有權(quán)限列表字段protected final PermissionsState mPermissionsState;,之后再運行中,動態(tài)權(quán)限的操作都是針對這個對象

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權(quán)限信息(對于Android6.0package.xml)-->
    
    readPackageLPw(parser); 
        ...
        
      <!--讀取runtime權(quán)限信息-->
      
    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;
                }
權(quán)限恢復(fù)流程

原來的權(quán)限存放位置在哪?不會都從Android Manifest清單去讀取,只會在啟動時讀取一次。Android6.0之前會吧所有的權(quán)限都放置在data/system/packages.xml文件中。Android6.0之后,分為運行時權(quán)限跟普通權(quán)限,普通權(quán)限還是放在data/system/packages.xml中,運行時權(quán)限防止在data/system/users/0/runtime-permissions.xml文件中。根據(jù)運行時是否動態(tài)申請去更新權(quán)限。

Android6.0申請普通權(quán)限會怎么樣

Android6.0里,普通權(quán)限仍然按照運行時權(quán)限的模型,只是granted="true",就是永遠是取得授權(quán)的。所以可以直接獲得權(quán)限申請成功的回調(diào)。如果查看packages.xml,就會發(fā)現(xiàn):如下信息:

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

Android的關(guān)鍵節(jié)點,在哪里?

關(guān)鍵節(jié)點并不是查詢是否具有該權(quán)限,Android6.0之前的 權(quán)限查詢是不會觸發(fā)權(quán)限申請與授權(quán)的,只有在請求系統(tǒng)服務(wù)的時候,由系統(tǒng)服務(wù)調(diào)用AppopsManager去查詢是否賦予了該權(quán)限,第一次未操作肯定是null,未賦予就可能會觸發(fā)權(quán)限申請邏輯,這個點在各個系統(tǒng)服務(wù)內(nèi)部,由AppOpsService服務(wù)統(tǒng)一管理,不過對于官方的Release版本,其實只有系統(tǒng)通知APP才有動態(tài)權(quán)限管理的能力,其他都沒有操作能力。

作者:看書的小蝸牛
原文鏈接: Android權(quán)限管理原理

參考文檔

1、Android 安全機制概述 Permission
2、android permission權(quán)限與安全機制解析
3、android6.0權(quán)限管理原理
4、深入理解 PackageManagerService
5、Android 4.3 隱藏功能 App Ops 分析
6、Android 權(quán)限機制,你真的了解嗎?
7、Android原生權(quán)限管理:AppOps
8、 Android 5.1 AppOps總結(jié)
9、CM12源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,319評論 25 708
  • 我不害怕老去 而是害怕老去的路上 每個虛度的自己 虛度的自己 在晚風(fēng)里追憶 追憶意氣風(fēng)發(fā)的曾經(jīng) 夏蟲也為我悲鳴 悲...
    煙雨心清閱讀 711評論 3 9
  • 最佳藥引:蒼蠅的掙扎,有你也有他 十一假期回老家休息,有天,晴空萬里,閑來無事,盯著面前一張黏蒼蠅貼紙觀...
    紫竹閱讀 579評論 14 6
  • 看《螢火蟲小巷》,說閨中密友。 第一部分寫七十年代“舞后”。讀起來有種看好萊塢勵志友情電影,一位美麗無雙卻父母不愛...
    最近想努力記錄閱讀 578評論 0 0
  • 睡不著睡不著睡不著?。。?!你知道嗎?你知道嗎?你知道嗎?
    萍心波動閱讀 319評論 1 0

友情鏈接更多精彩內(nèi)容