关于Android权限管理原理那些事儿

作者: Android架构 | 来源:发表于2019-06-20 10:20 被阅读6次

    前言

    Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的。在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限。对于开发而言就是将targetSdkVersion设置为23,并且在相应的时机动态申请权限,在适配了Android6.0的App运行在Android 6.0+的手机上时,就会调用6.0相关的API,不过在低版本的手机上,仍然是按安装时权限处理。

    AppOpsManager动态权限管理:官方预演的权限管理

    AppOpsManager是Google在Android4.3引入的动态权限管理方式,不过,Google觉得不成熟,所以在每个发行版的时候,总是会将这个功能给屏蔽掉。该功能跟国内的权限动态管理表现类似,这里用CyanogenMod12里面的实现讲述一下,(国内的ROM源码拿不到,不过从表现来看,实现应该类似)。AppOpsManager实现的动态管理的本质是:将鉴权放在每个服务内部,比如,如果App要申请定位权限,定位服务LocationManagerService会向AppOpsService查询是否授予了App定位权限,如果需要授权,就弹出一个系统对话框让用户操作,并根据用户的操作将结果持久化在文件中,如果在Setting里设置了响应的权限,也会去更新相应的权限操作持久化文件/data/system/appops.xml,下次再次申请服务的时候,服务会再次鉴定权限。

    举个栗子-定位服务LocationManagerService: CM12源码

    App在使用定位服务的时候,一般是通过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);
        <!--关键函数 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服务发送鉴权请求

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

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

    在上面的代码里面,1、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;
                    }
                }
            };
    
    

    Android发行版源码对于动态权限管理的支持(几乎为零)

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

    对于6.0以下的手机权限如何检测

    对于Android6.0以下的手机,不需要关心targetVersion。先说个自己验证的结果:基本没法检测,同时也不需要检测,就算检测出来也没有多大意义,因为,触发时机是在真正的调用服务时候。对于4.3到6.0之前的国产ROM,虽然采用AppopsManagerService,但是并未按照Google的模型对所有权限进行适配,在这个模型下,也就适配了两个权限,(悬浮窗还不一定有)

    • 通知权限 public static final int OP_POST_NOTIFICATION = 11;
    • 悬浮窗权限 public static final int OP_SYSTEM_ALERT_WINDOW = 24;

    Google发行版的APPOpsService,基本是把整个鉴权逻辑给屏蔽了,通过CM的源码,课对这部分代码窥探一斑,如果整个权限都采用4.3权限管理模型,在拒绝一项权限的时候,这个操作会被持久化到appops.xml中去,但是具体看下去,其实并不是如此,这种机制只对以上两个权限生效:

        <pkg n="com.xxx">
        <uid n="10988">
        <!--关键点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" />
         <!--关键点2-->
        <op n="11" m="1" t="1510889291834" r="1517279030708" />
        <op n="14" t="1517293452801" />
        <!--关键点3-->
        <op n="24" m="1" />
        <op n="40" t="1513599239364" d="600011" />
    
    

    国产rom中,假如你拒绝授权位置权限,按照AppOpsService模型,该操作应该被持久化到appops.xml中去,但是,结果并非如此,也就是说,对于其他权限,国产ROM应该是自己糊弄了一套持久管理,持久化Android系统API无法访问的地方,仅仅为自身ROM可见。appops.xml真正被系统使用时从Android6.0开始,其实Android6.0是有两套权限管理的,这其实很混乱,不知道Google怎么想的,不过6.0似乎也有漏洞:权限的授予跟回收权限好像并不配对

    那么这就带来了一个问题,在Android4.3到Android6.0之间的版本,并没有同一个API来检测是否获取了某种权限,因为你动态更新的权限并未持久化到appops.xml中去。对于Android6.0之前的ROM,虽然不能检测,但完全可以直接用服务,不会崩溃,因为如果真需要鉴权,它的鉴权时机其实是在服务使用的时候。AppopsManager在6.0之前,只能用来检测通知,可能还有悬浮窗。

    Android 6.0权限管理原理

    Android6.0的runtime-permission机制让用户在任何时候都可以取消授权,因此,每次在申请系统服务的时候,都要动态查询是否获取了相应的权限,如果没有获取,就需要动态去申请,首先先看一下权限的查询:

    Android6.0权限查询

    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来进行的。

    PKMS不同版本的权限查询

    Android5.0的checkUidPermission

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

    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之后,对权限的操作是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之后,除了声明了权限之外,还必须是授权了的。运行时权限跟install权限有所不同,对于install权限isGranted一直返回是True,这里先不必深究PermissionsState是怎么存进内存,先记住,后面会将讲。

    Android6.0动态申请权限

    申请权限可以通过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.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的构造器中:对于原生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>
    
    

    之后就是动态更新权限流程:

    如何动态更新RuntimePermission

    通过上面的流程,我们进入了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);
            }
    
    
     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;
    }
    
    
        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

    RuntimePermission持久化

    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>
    
    

    RuntimePermission恢复(其实这里也包含普通权限)

    这些持久化的数据会在手机启动的时候由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申请普通权限会怎么样

    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>
    
    

    Android的关键节点,在哪里?

    关键节点并不是查询是否具有该权限,Android6.0之前的 权限查询是不会触发权限申请与授权的,只有在请求系统服务的时候,由系统服务调用AppopsManager去查询是否赋予了该权限,第一次未操作肯定是null,未赋予就可能会触发权限申请逻辑,这个点在各个系统服务内部,由AppOpsService服务统一管理,不过对于官方的Release版本,其实只有系统通知APP才有动态权限管理的能力,其他都没有操作能力。

    自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?

    没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

    资料获取方式:加入Android架构交流QQ群聊:513088520 ,进群即领取资料!!!

    点击链接加入群聊【Android移动架构总群】:加入群聊

    资料大全

    相关文章

      网友评论

        本文标题:关于Android权限管理原理那些事儿

        本文链接:https://www.haomeiwen.com/subject/toukqctx.html