Android6.0动态运行时权限源码分析

一.前言

由于政策和应用市场的原因,我们项目需要将targetSdkVersion从19升级到26,这样导致一些API,webview高低版本 https&http混合加载,通知栏显示,敏感权限动态申请,应用内升级以及高低版本崩溃等一些问题。今天重点来说说Android动态运行时权限,我们知道android从targetSdkVersion23就引入了动态权限,主要应该是处于敏感权限的安全考虑。本文基于api 28代码分析。


Android6.0动态运行时权限源码分析_第1张图片
zw_overview.png

二.权限组与敏感权限

普通权限当在AndroidMainfest申请注册了,并且权限组里其中某个权限被授权了,其他同组权限也授权了,但是对于敏感权限,用户有可能随时关闭某个app的敏感权限,所以应用必须需要动态去授权。如下为权限组与敏感权限:


Android6.0动态运行时权限源码分析_第2张图片
zw_permission.png

三.权限授权过程

以存储权限动态授权步骤为例:

if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            //检查权限
            if (ContextCompat.checkSelfPermission(context,
                    Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            //是否需要动态授权
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        (Activity) context,
                        Manifest.permission.READ_EXTERNAL_STORAGE)) {
             //动态授权
                    ActivityCompat.requestPermissions((Activity) context,
                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                            PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
                } else {
                    ActivityCompat
                            .requestPermissions(
                                    (Activity) context,
                                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                                    PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
                }
                return false;
            } else {
                return true;
            }

        } else {
            return true;
        }

动态授权后,系统会弹出权限界面,操作结果会回调给对应的Activity重载onRequestPermisionResult方法:

@Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case AndroidPermissionUtils.PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // todo 用户在授权框同意授权
                } else {
                    //todo 用户在授权框禁止授权
                }
                break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions,
                        grantResults);
        }
    }

小结:
1.检查权限。
2.提示用户是否需要动态授权该权限。
3.进行动态授权。
4.处理用户授权回调。

下面结合上面的授权步骤来具体分析下源码调用的过程:

1.检查权限ContextCompat.checkSelfPermission源码调用过程分析

首先会调用到ContextCompat的checkSelfPermission方法

public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());
    }

看最后一行代码context.checkPermission,checkPermission其实是一个抽象方法,看到context这个实例具体实现肯定实在ContextImpl#checkPermission方法里处理的。ContextImpl是Context的实现类,具体实现如下:

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

        final IActivityManager am = ActivityManager.getService();
        if (am == null) {
            // Well this is super awkward; we somehow don't have an active
            // ActivityManager instance. If we're testing a root or system
            // UID, then they totally have whatever permission this is.
            final int appId = UserHandle.getAppId(uid);
            if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
                Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
                return PackageManager.PERMISSION_GRANTED;
            }
        }

        try {
            return am.checkPermission(permission, pid, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

1.当permission为空就抛出状态异常。反之进入第二步。
2.获取IActivityManager am实例,IActivityManager是一个Binder。当前应用appId是rootUid或者是systemUid,也就是是用root权限或者是系统应用。则直接返回已授权。
3.am不为空则通过Binder跨进程调到AMS中,进入AMS#checkPermission方法中。
com.android.server.am.ActivityManagerService#checkPermission

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

判断permission是否为空,为空就直接拒绝授权,反之调用checkComponentPermission进行检查权限。

int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }

我们看到pid == MY_PID,意思是说当我们的appid如果和AMS是一个进程的话,就直接返回允许授权。否则进入ActivityManager中checkComponentPermission方法继续检查是否授权。

public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        // Root, system server get to do everything.
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // Isolated processes don't get any permissions.
        if (UserHandle.isIsolated(uid)) {
            return PackageManager.PERMISSION_DENIED;
        }
        // If there is a uid that owns whatever is being accessed, it has
        // blanket access to it regardless of the permissions it requires.
        if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the target is not exported, then nobody else can get to it.
        if (!exported) {
            /*
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                    here);
            */
            return PackageManager.PERMISSION_DENIED;
        }
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

1.通过uid获取到appid,判断如果是root和系统应用直接返回已授权。
2.通过UserHandle.isIsolated(uid)判断是否是隔离进程,那什么是隔离进程呢?所谓隔离进程在android系统里有进程范围的,就是被拉入黑名单中的应用。那就直接返回没有授权。
3.UserHandle.isSameApp(uid, owningUid),这个是什么意思呢?看方法名称我们应该可以猜到,通过判断当前id和拥有的id是否相等,说明是同一个app。意思是说,当前权限是我们自己app在AndroidMainfest通过permission标签申请的权限当前进程可以用就直接返回已授权。
4.当exported为false的时候直接返回未授权。exported是在AndroidMainfest注册四大组件的时候用到的标签。
接着调用AppGlobals.getPackageManager().checkUidPermission(permission, uid)方法:

public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
        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) {
                if (obj instanceof SharedUserSetting) {
                    if (isCallerInstantApp) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                } else if (obj instanceof PackageSetting) {
                    final PackageSetting ps = (PackageSetting) obj;
                    if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                }
                final SettingBase settingBase = (SettingBase) obj;
                final PermissionsState permissionsState = settingBase.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    if (isUidInstantApp) {
                        BasePermission bp = mSettings.mPermissions.get(permName);
                        if (bp != null && bp.isInstant()) {
                            return PackageManager.PERMISSION_GRANTED;
                        }
                    } else {
                        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 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;
    }

AppGlobals.getPackageManager()获取的是一个IPackageManager Binder实例。最终调到PMS PackageManagerService#checkUidPermission方法中。

checkSelfPermission调用流程:
Android6.0动态运行时权限源码分析_第3张图片
zw_checkUidPermission.png

2.是否需要动态授权提示ActivityCompat.shouldShowRequestPermissionRationale源码调用过程分析

是否需要展示给用户确认授权提示,我们从上面存储动态授权知道,每次动态授权会弹出一个系统的GrantPermissionsActivity提示需要授权,这将会在动态授权的步骤中会提到。

public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
            @NonNull String permission) {
        if (Build.VERSION.SDK_INT >= 23) {
            return activity.shouldShowRequestPermissionRationale(permission);
        }
        return false;
    }

Build.VERSION.SDK_INT >= 23我们看出当api大于等于23就会有确认授权提示,就是说android6.0才会又动态授权提示界面。android6.0以下是没有动态授权的概念的。接着会流转到Activity#shouldShowRequestPermissionRationale方法,然后再流转到PackageManager#shouldShowRequestPermissionRationale方法,PackageManager类中shouldShowRequestPermissionRationale是一个抽象方法,具体实现是在 ApplicationPackageManager#shouldShowRequestPermissionRationale方法中:

public boolean shouldShowRequestPermissionRationale(String permission) {
        try {
            return mPM.shouldShowRequestPermissionRationale(permission,
                    mContext.getPackageName(), mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

mPM这个实例是Binder类型的实例,这里跨进程最终流转到PackageManagerService#shouldShowRequestPermissionRationale方法中。

public boolean shouldShowRequestPermissionRationale(String permissionName,
            String packageName, int userId) {
        if (UserHandle.getCallingUserId() != userId) {
            mContext.enforceCallingPermission(
                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                    "canShowRequestPermissionRationale for user " + userId);
        }

        final int uid = getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
        if (UserHandle.getAppId(getCallingUid()) != UserHandle.getAppId(uid)) {
            return false;
        }

        if (checkPermission(permissionName, packageName, userId)
                == PackageManager.PERMISSION_GRANTED) {
            return false;
        }

        final int flags;

        final long identity = Binder.clearCallingIdentity();
        try {
            flags = getPermissionFlags(permissionName,
                    packageName, userId);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
                | PackageManager.FLAG_PERMISSION_POLICY_FIXED
                | PackageManager.FLAG_PERMISSION_USER_FIXED;

        if ((flags & fixedFlags) != 0) {
            return false;
        }

        return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
    }

1.当Binder IPC跨进程调用方的UID不等于现在的UID就直接返回false表示不需要弹出授权提示,反之需要。
2.checkPermission 通过权限名称,包名,uid去检查当前是否又权限,如果有权限则不需要弹出授权提示,反之需要。
3.当flags为系统固定设置,权限策略固定,用户主动设置不允许修改的则不需要弹出授权提示,反之需要。
是否展示授权提示流程:


Android6.0动态运行时权限源码分析_第4张图片
zw_shouldShowRequestPermissionRationale.png

3.动态请求权限ActivityCompat.requestPermissions源码调用过程分析

我们通过上面第二步ActivityCompat.shouldShowRequestPermissionRationale方法知道是否需要进行动态授权,通过ActivityCompat#requestPermissions方法可以实现动态授权:

 public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
        if (sDelegate != null
                && sDelegate.requestPermissions(activity, permissions, requestCode)) {
            // Delegate has handled the permission request.
            return;
        }

        if (Build.VERSION.SDK_INT >= 23) {
            if (activity instanceof RequestPermissionsRequestCodeValidator) {
                ((RequestPermissionsRequestCodeValidator) activity)
                        .validateRequestPermissionsRequestCode(requestCode);
            }
            activity.requestPermissions(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);
                }
            });
        }
    }

通过上面我们可以看出api 6.0以下系统授权过程,通过PackageManager去检查在Androidmanifest注册的权限,只要有就默认grant并放入grantResults数组里,然后通过onRequestPermissionsResult回调到Activity中。所以只要在manifest有注册。接下来我们重点分析api 6.0以上的动态授权过程
ActivityCompat#requestPermissions中流转到Activity#requestPermissions方法:

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

PackageManager#buildRequestPermissionsIntent方法:

public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
        if (ArrayUtils.isEmpty(permissions)) {
           throw new IllegalArgumentException("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;
    }

当mHasCurrentPermissionsRequest为true的时候我们就直接回调到对应的Activity的重载方法onRequestPermissionsResult中。mHasCurrentPermissionsRequest为true表示我们已经发起过动态授权,我们看到最后一行代码mHasCurrentPermissionsRequest 设置为 true。否则就通过buildRequestPermissionsIntent构建一个Intent启动一个名叫GrantPermissionsActivity的Activity。没错这个GrantPermissionsActivity就是我们经常看到的系统授权弹窗。
GrantPermissionsActivity源码(https://android.googlesource.com/platform/packages/apps/PackageInstaller/+/refs/tags/android-vts-8.0_r12/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
):
包路径:packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
我们从上面PackageManager#buildRequestPermissionsIntent方法看出Intent带EXTRA_REQUEST_PERMISSIONS_NAMES为key,value为permissions权限数组传给GrantPermissionsActivity弹窗界面。下面重点介绍下授权界面交互的几部分:

1.GrantPermissionsActivity通过Intent拿到权限数组。
mRequestedPermissions = getIntent().getStringArrayExtra(
                PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
        if (mRequestedPermissions == null) {
            mRequestedPermissions = new String[0];
        }
。。。。
for (String requestedPermission : mRequestedPermissions)
 AppPermissionGroup group = null;
            for (AppPermissionGroup nextGroup : mAppPermissions.getPermissionGroups()) {
                if (nextGroup.hasPermission(requestedPermission)) {
                    group = nextGroup;
                    break;
                }
            }
            if (group == null) {
                continue;
            }
。。。

进行一波权限判断,接着往下看。

2. GrantPermissionsViewHandler
@Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
       。。。
        if (DeviceUtils.isTelevision(this)) {
            mViewHandler = new com.android.packageinstaller.permission.ui.television
                    .GrantPermissionsViewHandlerImpl(this,
                    getCallingPackage()).setResultListener(this);
        } else if (DeviceUtils.isWear(this)) {
            mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
        } else if (DeviceUtils.isAuto(this)) {
            mViewHandler = new GrantPermissionsAutoViewHandler(this, getCallingPackage())
                    .setResultListener(this);
        } else {
            mViewHandler = new com.android.packageinstaller.permission.ui.handheld
                    .GrantPermissionsViewHandlerImpl(this, getCallingPackage())
                    .setResultListener(this);
        }
。。。
setContentView(mViewHandler.createView());

GrantPermissionsViewHandler#mViewHandler是一个接口,我们通过上面mViewHandler.createView()已经设置result回调应该可以猜到是处理UI相关的。

3. onPermissionGrantResult: GrantPermissionsViewHandler#ResultListener

GrantPermissionsViewHandler#ResultListener是一个接口,回调如下方法:

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.affectedPermissions);
                groupState.mState = GroupState.STATE_ALLOWED;
            } else {
                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
                        groupState.affectedPermissions);
                groupState.mState = GroupState.STATE_DENIED;
                int numRequestedPermissions = mRequestedPermissions.length;
                for (int i = 0; i < numRequestedPermissions; i++) {
                    String permission = mRequestedPermissions[i];
                    if (groupState.mGroup.hasPermission(permission)) {
                        EventLogger.logPermissionDenied(this, permission,
                                mAppPermissions.getPackageInfo().packageName);
                    }
                }
            }
            updateGrantResults(groupState.mGroup);
        }
        if (!showNextPermissionGroupGrantRequest()) {
            setResultAndFinish();
        }
    }

通过上面处理是否允许授权逻辑。通过GroupState最终跨进程调用到PMS中远程更新权限。在PMS中做一些检查,授权,持久化等相关的事情,具体可以自己去看看源码。
这里提下持久化,持久化会将permissions存入到xml文件中,权限会按包名持久化相应的permissions,xml文件位置:data/system/0/runtime-permissions.xml。
以上是动态授权私密权限,普通权限会和api23以下一样存到data/system/packages.xml中,api23以下所有的权限都存放在packages.xml文件中。应用一起来会从这里面加载权限。
小结:
GrantPermissionsActivity中主要是用户获取哪些权限以及处理授权界面监听事件,最终会流转回PMS中进行授权。最终授权在PMS中完成。
授权流程:


Android6.0动态运行时权限源码分析_第5张图片
zw_requestPermissions.png

4.处理权限onRequestPermissionsResult回调过程

api23以上会通过GrantPermissionsActivity界面中通过用户操作跨进程至PMS回调具体onRequestPermissionsResult方法。api23以下通过PMS检查权限是否在manifest文件中注册,然后回调onRequestPermissionsResult方法到对应的Activity中。这里就不赘述了,具体可以看源码。

四.总结

以上为运行时授权的过程:
1.检查是否有需要权限。
2.判断是否需要动态授权提示。
3.进行动态授权。
4.处理用户授权回调。

建议

通过这次权限频繁出现问题,在google官网发现其实每个版本跟新都会有相应的changelist。我们平时可以关注Android新版本更新哪些功能,做了哪些限制,优化了哪些功能都可以在这官方文档上可以查看到。
Android 8.0 changes
google官方每个版本更新说明

Other

平时看查看源码由于Android的Hide标注使得看源码非常不方便跳转,顺便分享平时我是怎样看源码的,其实就是去掉源码Hide标注,建议下载去掉hide方法的android.jar包,方便在IDE中跳转。
android-hidden-api

That's All

你可能感兴趣的:(Android6.0动态运行时权限源码分析)