Android权限检测申请源码流程分析

Quibbler 2021-1-5 3639

Android权限检测申请源码流程分析


        Android系统的权限开发用起来很方便,知识点不多,详见Android Permission 权限总结。但是它的系统校验检测机制比较复杂,从framework层PackageManagerPermissionManagerPermissionManagerInternal到系统服务PackageManagerServicePermissionManagerServicePermissionManagerServiceInternal

        只是浅显的看一下大致的源码逻辑,有兴趣和时间的可以从下面两大块源码入手完整的分析一些Android权限机制:

        /frameworks/base/core/java/android/permission

        /frameworks/base/services/core/java/com/android/server/pm/permission



1、权限检测源码流程

        检查程序是否获得某一个权限方法详见Android Permission 权限总结


1.1、从Context开始

        不管通过什么方法最后都会调用到ContextcheckPermission(String permission, int pid, int uid)方法

    @CheckResult(suggest="#enforcePermission(String,int,int,String)")
    @PackageManager.PermissionResult
    public abstract int checkPermission(@NonNull String permission, int pid, int uid);

        在各种Context及其初始化流程一文中,我们知道Context的实现类是ContextImpl。实现了检测权限的抽象方法:

    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }
        return PermissionManager.checkPermission(permission, pid, uid);
    }


1.2、借助PermissionManager

        理所应当的由专门负责权限管理的PermissionManager来校验权限

    /** @hide */
    public static int checkPermission(@Nullable String permission, int pid, int uid) {
        return sPermissionCache.query(new PermissionQuery(permission, pid, uid));
    }

       看到sPermissionCache这个变量就应该立马联想到权限查询先查cache,而且会缓存到这里

    private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache =
            new PropertyInvalidatedCache<PermissionQuery, Integer>(
                    16, CACHE_KEY_PACKAGE_INFO) {
                @Override
                protected Integer recompute(PermissionQuery query) {
                    return checkPermissionUncached(query.permission, query.pid, query.uid);
                }
            };

        PropertyInvalidatedCache类在android.app包中,是Android 30新增的用来实现LRU缓存,以前的源码中并没有查到这个类。当缓存中没有数据就会调用下面的方法并将其结果缓存起来:

    /**
     * Fetch a result from scratch in case it's not in the cache at all.  Called unlocked: may
     * block. If this function returns null, the result of the cache query is null. There is no
     * "negative cache" in the query: we don't cache null results at all.
     */
    protected abstract Result recompute(Query query);

        sPermissionCache中没有对应的权限时就会调用PermissionManager中的checkPermissionUncached(String permission, int pid, int uid)方法

    private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) {
        final IActivityManager am = ActivityManager.getService();
        ...
        try {
            return am.checkPermission(permission, pid, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }


1.3、AMS服务

        熟悉Activity启动流程的对ActivityManagerService一定不陌生,它实现了IActivityManager.Stub远程接口,检查权限的方法具体实现如下:

    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            return PackageManager.PERMISSION_DENIED;
        }
        return checkComponentPermission(permission, pid, uid, -1, true);
    }

        调用下面的checkComponentPermission()方法,注释太皮了:如果有一个显式的权限正在被检查,并且这个权限来自一个被拒绝访问该权限的进程,那么就拒绝它。最终,这可能不是很正确——这意味着即使调用者因为其他原因(例如作为它试图访问的组件的所有者)拥有访问权,它仍然会失败。这也意味着系统和根uid能够拒绝自己的权限访问,这…嗯好的。¯\ _(ツ)_ /¯

    public static int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If there is an explicit permission being checked, and this is coming from a process
        // that has been denied access to that permission, then just deny.  Ultimately this may
        // not be quite right -- it means that even if the caller would have access for another
        // reason (such as being the owner of the component it is trying to access), it would still
        // fail.  This also means the system and root uids would be able to deny themselves
        // access to permissions, which...  well okay. ¯\_(ツ)_/¯
        if (permission != null) {
            synchronized (sActiveProcessInfoSelfLocked) {
                ProcessInfo procInfo = sActiveProcessInfoSelfLocked.get(pid);
                if (procInfo != null && procInfo.deniedPermissions != null
                        && procInfo.deniedPermissions.contains(permission)) {
                    return PackageManager.PERMISSION_DENIED;
                }
            }
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }


1.4、绕道ActivityManager

        接着又绕到了ActivityManager,调用ActivityManager的checkComponentPermission()方法:

    public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        ... ...
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

        最后调用AppGlobals类中的getPackageManager()方法,获取IPackageManager远程服务。这个类提供了与流程相关的某些全局变量的特殊私有访问方法,其实是多此一举封装了一下,这很Android。

    /**
     * Return the raw interface to the package manager.
     * @return The package manager.
     */
    @UnsupportedAppUsage
    public static IPackageManager getPackageManager() {
        return ActivityThread.getPackageManager();
    }


1.5、PackageManagerService

        进入PackageManagerService包管理系统服务,它的检查权限方法啥也不干,封装了PermissionManagerService权限管理服务:

    // NOTE: Can't remove without a major refactor. Keep around for now.
    @Override
    public int checkUidPermission(String permName, int uid) {
        try {
            // Because this is accessed via the package manager service AIDL,
            // go through the permission manager service AIDL
            return mPermissionManagerService.checkUidPermission(permName, uid);
        } catch (RemoteException ignore) { }
        return PackageManager.PERMISSION_DENIED;
    }


1.6、PermissionManagerService

        通过mPermissionManagerService也就是PermissionManagerService权限管理服务检查权限:

    public int checkUidPermission(String permName, int uid) {
        // Not using Objects.requireNonNull() here for compatibility reasons.
        if (permName == null) {
            return PackageManager.PERMISSION_DENIED;
        }
        final int userId = UserHandle.getUserId(uid);
        if (!mUserManagerInt.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }
        final CheckPermissionDelegate checkPermissionDelegate;
        synchronized (mLock) {
            checkPermissionDelegate = mCheckPermissionDelegate;
        }
        if (checkPermissionDelegate == null)  {
            return checkUidPermissionImpl(permName, uid);
        }
        return checkPermissionDelegate.checkUidPermission(permName, uid,
                this::checkUidPermissionImpl);
    }

        

1.7、虚晃一下PermissionManagerInternal

        前面方法中的CheckPermissionDelegate是定义在PermissionManagerInternal中的interface

    /** Interface to override permission checks via composition */
    public interface CheckPermissionDelegate {
        /**
         * Checks whether the given package has been granted the specified permission.
         */
        int checkPermission(String permName, String pkgName, int userId,
                TriFunction<String, String, Integer, Integer> superImpl);
        /**
        /**
         * Checks whether the given uid has been granted the specified permission.
         */
        int checkUidPermission(String permName, int uid,
                BiFunction<String, Integer, Integer> superImpl);
    }

        我翻遍了Service、internal、framework层的代码,没有知道它的实现类,而且网上关于这个类的信息极少。难道线索就此断了?不!


1.8、回到PMS服务

        在1.6节的方法中,仔细看发现不会调用CheckPermissionDelegate类的方法,而是直接走PMS内部的权限检测方法checkUidPermissionImpl(String permName, int uid)

    private int checkUidPermissionImpl(String permName, int uid) {
        final AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
        return checkUidPermissionInternal(pkg, uid, permName);
    }

        经过中间的回调,最终通过下面的checkUidPermissionInternal(AndroidPackage pkg, int uid,String permissionName)方法校验权限:

    private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid,
            @NonNull String permissionName) {
        if (pkg != null) {
            final int userId = UserHandle.getUserId(uid);
            return checkPermissionInternal(pkg, false, permissionName, userId);
        }
        if (checkSingleUidPermissionInternal(uid, permissionName)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
        if (fullerPermissionName != null
                && checkSingleUidPermissionInternal(uid, fullerPermissionName)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return PackageManager.PERMISSION_DENIED;
    }

        这其中涉及到几个全局变量保存系统全部权限的数组、保存权限的映射,说到底权限检测也就是String匹配校验...

    /**
     * Built-in permissions. Read from system configuration files. Mapping is from
     * UID to permission name.
     */
    private final SparseArray<ArraySet<String>> mSystemPermissions;
    
    /** If the permission of the value is granted, so is the key */
    private static final Map<String, String> FULLER_PERMISSION_MAP = new HashMap<>();
    ... ...



2、申请权限源码流程

        如果检测出来没有某项权限,需要用requestPermissions(String[] permissions, int requestCode)动态申请权限。在Activity、Fragment中都有请求权限的方法,在Activity和Fragment中都可以调用requestPermissions,请求权限的方法名字相同,但是实现不同。


2.1、在Activity中请求权限

        在Activity中,有调用了一个隐藏的方法startActivityForResult(String who, Intent intent, int requestCode,Bundle options)方法

    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (requestCode < 0) {
            throw new IllegalArgumentException("requestCode should be >= 0");
        }
        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can request only one set of permissions at a time");
            // Dispatch the callback with empty arrays which means a cancellation.
            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }

         启动系统Activity请求权限,也就是出现授权的弹框:

    private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";


2.2、在Fragment中请求权限

        在Fragment中较为繁琐,先借助一个FragmentHostCallback回调对象mHost中的onRequestPermissionsFromFragment(this, permissions, requestCode)方法。

    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (mHost == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
    }

        而FragmentActivity实现了FragmentHostCallback接口

        @Override
        public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
                @NonNull String[] permissions, int requestCode) {
            FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
                    requestCode);
        }

        进而又调用了FragmentActivity中的requestPermissionsFromFragment()方法

    /**
     * Called by Fragment.requestPermissions() to implement its behavior.
     */
    void requestPermissionsFromFragment(@NonNull Fragment fragment, @NonNull String[] permissions,
            int requestCode) {
        if (requestCode == -1) {
            ActivityCompat.requestPermissions(this, permissions, requestCode);
            return;
        }
        checkForValidRequestCode(requestCode);
        try {
            mRequestedPermissionsFromFragment = true;
            int requestIndex = allocateRequestIndex(fragment);
            ActivityCompat.requestPermissions(this, permissions,
                    ((requestIndex + 1) << 16) + (requestCode & 0xffff));
        } finally {
            mRequestedPermissionsFromFragment = false;
        }
    }

        在该方法中终于快看到头了ActivityCompat.requestPermissions(this, permissions, requestCode);

    public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
            ... ...
            activity.requestPermissions(permissions, requestCode);
            ... ...
    }

        这其中的activity.requestPermissions(permissions, requestCode)调用的正式第一种Activity中的requestPermissions()申请权限的方法。


2.3、权限申请回调

        ActivityCompat类中定义的接口OnRequestPermissionsResultCallback

    public interface OnRequestPermissionsResultCallback {
        /**
         * 请求权限的结果的回调。 每次调用时都会调用此方法
         *{@link #requestPermissions(android.app.Activity,String[], int)}
         *权限请求与用户的交互可能会中断。 在这种情况下,您将收到空的权限和结果数组,应将其视为取消
         *
         * @param requestCode 传入的请求代码 {@link #requestPermissions(android.app.Activity, String[], int)}
         * @param permissions 请求的权限,永远不会为空。
         * @param grantResults 授予相应权限的结果:要么拒绝,要么授予。
         *     android.content.pm.PackageManager#PERMISSION_GRANTED
         *  or 
         *     android.content.pm.PackageManager#PERMISSION_DENIED 永远不会为空
         * @see #requestPermissions(android.app.Activity, String[], int)
         */
        void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                @NonNull int[] grantResults);
    }

        AppCompatActivity继承自FragmentActivity,在FragmentActivity中可以看到实现了ActivityCompat.OnRequestPermissionsResultCallback接口。我们就可以在Activity去重写这个请求权限的回调方法,系统会回调该方法。

    public class FragmentActivity extends ComponentActivity implements ActivityCompat.OnRequestPermissionsResultCallback...{
        ...
    }



推荐资料:

        浅析Android权限机制

        Android权限校验过程

        Android 9.0 activity启动源码分析

        Android9.0动态运行时权限源码分析及封装改造

        Android权限系统:运行时权限检查和申请

        

不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com