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

QuibblerQuibbler 2021-01-05 约 36 分钟 6183 次阅读

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

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

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

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

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

1、权限检测源码流程

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

1.1、从Context开始

不管通过什么方法最后都会调用到Context的checkPermission(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权限系统:运行时权限检查和申请

        

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w