美文网首页
Android 权限管理--01:RuntimePermissi

Android 权限管理--01:RuntimePermissi

作者: DarcyZhou | 来源:发表于2023-12-17 08:27 被阅读0次

本文转载自:Android Framework权限篇一之RuntimePermission整体流程

本文基于Android 9.0源码分析

1.概述

  权限的作用是保护Android用户的隐私。如果设备搭载的是Android 6.0(API 级别 23)或更高版本,并且应用的targetSdkVersion是23或更高版本,用户在安装时不会收到任何应用权限的通知。您的应用必须在运行时请求用户授予危险权限。当应用请求权限时,用户会看到一个系统对话框,告知用户应用正在尝试访问的权限组。该对话框包括拒绝和允许按钮。如果用户选中不再询问复选框并点按拒绝,当您以后尝试请求相同权限时,系统不会再提示用户。

  如果设备搭载的是Android 5.1.1(API级别22)或更低版本,或者应用在任何版本的Android上运行时其targetSdkVersion是22或更低版本,系统将在安装时自动请求用户向应用授予所有危险权限。

权限管理1-1.PNG

2.权限保护级别

权限的分类总共有如下几种,先有个总览,本篇文章后面是展开讨论dangerous类型即运行时权限的整体流程。

级别 描述
normal 如果应用在清单中声明需要普通权限,系统会在安装时自动向应用授予该权限。系统不会提示用户授予普通权限,用户也无法撤消这些权限
dangerous 为了使用危险权限,应用必须在运行时提示用户授予权限
signature 在安装时授予这些应用权限,但仅会在尝试使用某权限的应用签名证书为定义该权限的同一证书时才会授予(即相同签名)
signatureOrSystem signature|privileged 同signature权限;如果没有使用相同签名,在/system/priv-app目录下的app也可以获得该权限

app在/system/app目录下,并且需要signature级别的权限;为什么在AndroidManifest声明了之后没有默认授予?可以结合下图总结看下。

权限管理1-2.PNG

3.运行时权限申请

  这里开始看下运行时权限的整体流程;正常应用的权限申请过程如下,先看下应用是如何申请运行时权限,再从应用侧到Framework侧看下去; 更多应用权限请求细节可以参考这里

    private fun requestPermmission() {
        // 判断是否需要运行时申请权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            // 判断是否需要对用户进行提醒,用户点击过拒绝&&没有勾选不再提醒时,即拒绝一次时进行提示
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
                // 给用户予以权限解释, 对于已经拒绝过的情况,先提示申请理由,再通过点击弹出的Dialog进行申请
                showDialog("需要打开电话权限直接进行拨打电话,方便您的操作")
            } else {
                // 无需说明理由的情况下,直接进行申请。如:1.第一次使用该功能(第一次申请权限),2.用户拒绝权限并勾选了不再提醒,3.已授权 这3种情况shouldShowRequestPermissionRationale()返回false
                ActivityCompat.requestPermissions(
        this, new String[]{Manifest.permission. CALL_PHONE,}, 1);
            }
        } else {
            // 拥有权限直接进行功能调用
            callPhone();
        }
    }

    /**
     * 权限申请回调
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        // 根据requestCode判断是那个权限请求的回调
        if (requestCode == REQUEST_PERMISSION_CODE_CALL_PHONE) {
            // 判断用户是否同意了请求
            if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                callPhone()
            } else {
                // 未同意的情况
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
                    // 判断是拒绝一次的情况,则同样先是弹Dialog提示用户,再重新申请权限
                    showDialog("需要打开电话权限直接进行拨打电话,方便您的操作")
                } else {
                   // 用户勾选了不再提醒,引导用户进入设置界面进行开启权限
                   // 如果是用户点击了拒绝(拒绝并且设置不再询问)则requestPermissions完会直接回调onRequestPermissionsResult返回结果PERMISSION_DENIED, 走到这里的逻辑去提示用户去设置打开权限
                   showDialog("需要跳转去到设置打开权限");
                }
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

}

4.检查权限流程

  从上面的ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) 看下源码的检查流程。

权限管理1-3.png

这里在源码环境下加断点的形式可以看到调用堆栈,如上图最终是调用到PermissionManagerService.checkUidPermission()一起看下这个方法里的具体逻辑,这里先对PersmissionsState数据类有个印象,权限授权持久化是通过此类进行操作,先过下流程,后面再展开。

// frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
...
if (pkg != null) {
   ...
    //1.这里获取pkg对应的PermissionsState
    final PermissionsState permissionsState =
            ((PackageSetting) pkg.mExtras).getPermissionsState();
    //2.再判断该权限是否授权
    if (permissionsState.hasPermission(permName, userId)) {
        if (isUidInstantApp) {
            if (mSettings.isPermissionInstant(permName)) {
                return PackageManager.PERMISSION_GRANTED;
            }
        } else {
            //正常流程如果权限授权了是走到这里进行返回
            return PackageManager.PERMISSION_GRANTED;
        }
    }
    if (isImpliedPermissionGranted(permissionsState, permName, userId)) {
        return PackageManager.PERMISSION_GRANTED;
    }
...
//3.最后以上条件都没满足则返回拒绝
return PackageManager.PERMISSION_DENIED;

5.请求权限过程

  接着从之前的ActivityCompat.requestPermissions(this, new String[]{Manifest.permission. CALL_PHONE,}, 1);看下源码的请求过程,会调用到Activity.requestPermissions()方法,这里实际是启动一个权限请求弹窗页面。

// frameworks/base/core/java/android/app/Activity.java
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;
    }
    //1\. 生成指定action的intent,该intent会启动权限管理的权限弹窗界面
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    //2\. 通过startActivityForResult启动,结果会回调到onRequestPermissionsResult()
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}

根据生成的intent的action为:android.content.pm.action.REQUEST_PERMISSIONS,最终这里会启动GrantPermissionsActivity。

<!-- packages/apps/PackageInstaller/AndroidManifest.xml -->
        <activity android:name=".permission.ui.GrantPermissionsActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/GrantPermissions"
                android:visibleToInstantApps="true">
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

6.权限授权流程

  应用请求权限弹窗后,如果点击允许或者拒绝,此时底下源码会发生什么,一起往下看。

权限管理1-4.png

如果权限弹窗点击允许,最终会走到PermissionManagerService.grantRuntimePermission(),这里我们看下核心代码,可以看到这里还是通过PermissionsState.grantRuntimePermission()进行授权,具体关于权限内存状态和持久化存储在后续文章讲解,这里先有个印象。

// frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,int callingUid, final int userId, PermissionCallback callback) {
    ...
    final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
    ...
    //1\. 以上省略了很多判断条件,这里会先获取对应包名的权限状态PermissionsState和其flag
    final PackageSetting ps = (PackageSetting) pkg.mExtras;
    final PermissionsState permissionsState = ps.getPermissionsState();
    final int flags = permissionsState.getPermissionFlags(permName, userId);
    ...
    //2\. 这里对权限进行授权并返回结果
    final int result = permissionsState.grantRuntimePermission(bp, userId);
    ...
    //3\. 权限授权成功后,除了更新内存中的状态值,还会持久化到本地xml
    if (callback != null) {
        callback.onPermissionGranted(uid, userId);
    }
    ...
}

同样看下撤销权限流程,对应权限弹窗上的拒绝按钮,这里可以看到是通过PermissionsState.revokeRuntimePermission()进行撤销授权。

// frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
private void revokeRuntimePermission(String permName, String packageName,boolean overridePolicy, int userId, PermissionCallback callback) {
    final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
    ...
    //1\. 以上省略了很多判断条件,这里会先获取对应包名的权限状态PermissionsState和其flag
    final PackageSetting ps = (PackageSetting) pkg.mExtras;
    final PermissionsState permissionsState = ps.getPermissionsState();
    final int flags = permissionsState.getPermissionFlags(permName, userId);
    ...
    //2\. 撤销权限并判断返回结果
    if (permissionsState.revokeRuntimePermission(bp, userId) ==
            PERMISSION_OPERATION_FAILURE) {
        return;
    }
    ...
    //3\. 撤销成功后,除了更新内存中的状态值,还会持久化到本地xml
    if (callback != null) {
        callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
    }
    ...
}

7.flag更新

  上述权限授权流程的123执行完之后,从4开始会更新flag,这个flag的作用是什么?这里先说下结论,这个flag对应用户选择权限弹窗的允许、拒绝并勾选了不再提醒、拒绝并未勾选不再提醒。下一次权限弹窗时会通过此标志位判断是否不再弹窗。

// frameworks/base/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
//允许的标志位为0
packageManager.updatePermissionFlags(permission,
                            packageName,
                            PackageManager.FLAG_PERMISSION_USER_FIXED
                                    | PackageManager.FLAG_PERMISSION_USER_SET,
                            0, userHandle);
//拒绝并未勾选不再提醒的标志位为PackageManager.FLAG_PERMISSION_USER_SET
packageManager.updatePermissionFlags(permission,
                            packageName,
                            PackageManager.FLAG_PERMISSION_USER_SET
                                    | PackageManager.FLAG_PERMISSION_USER_FIXED,
                            PackageManager.FLAG_PERMISSION_USER_SET,
                            userHandle);
//拒绝并勾选了不再提醒的标志位为PackageManager.FLAG_PERMISSION_USER_FIXED
packageManager.updatePermissionFlags(permission,
                            packageName,
                            PackageManager.FLAG_PERMISSION_USER_SET
                                    | PackageManager.FLAG_PERMISSION_USER_FIXED,
                            PackageManager. FLAG_PERMISSION_USER_FIXED,
                            userHandle);

接下来看下具体方法调用下去是如何作用的,这里关于flag更新的地方单独画了个图看下比较好理解,flagMask参数对应前面传的

PackageManager.FLAG_PERMISSION_USER_SET | PackageManager.FLAG_PERMISSION_USER_FIXED

而FLAG_PERMISSION_USER_SET对应的值是1,FLAG_PERMISSION_USER_FIXED对应的值是2;两个值二进制相或是11。

权限管理1-5.png
// frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
private void updatePermissionFlags(String permName, String packageName, int flagMask,int flagValues, int callingUid, int userId, boolean overridePolicy,
        PermissionCallback callback) {
    ...
    //1.获取对应包名的权限状态PermissionsState
    final PackageSetting ps = (PackageSetting) pkg.mExtras;
    final PermissionsState permissionsState = ps.getPermissionsState();
    final boolean hadState =
            permissionsState.getRuntimePermissionState(permName, userId) != null;
    //2.更新flag
    final boolean permissionUpdated =
            permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
    ...
}
//-----------------------------------
// PermissionsState.java下的PermissionData内部类
//真正更新flag的地方
public boolean updateFlags(int userId, int flagMask, int flagValues) {
    //flagMask与flagValues相与后为新的flag
    final int newFlags = flagValues & flagMask;
    PermissionState userState = mUserStates.get(userId);
    if (userState != null) {
        final int oldFlags = userState.mFlags;
        //userState.mFlags & ~flagMask: 将旧的falg上关于flagMask的标志位清0
        //| newFlags再与上新的flag,即在关于flagMask上的标志位上赋值
        userState.mFlags = (userState.mFlags & ~flagMask) | newFlags;
        if (userState.isDefault()) {
            mUserStates.remove(userId);
        }
        return userState.mFlags != oldFlags;
    } else if (newFlags != 0) {
        userState = new PermissionState(mPerm.getName());
        //如果是新的状态则直接将newFlags赋值即可
        userState.mFlags = newFlags;
        mUserStates.put(userId, userState);
        return true;
    }
    return false;
}

8.总结

  到这里,权限的级别、运行时权限的应用申请,源码检查权限流程、授权和撤销权限流程和flag更新流程都已经讲完了;下一篇我们继续讲下应用安装时权限授权流程、以及运行时权限的数据结构。

相关文章

网友评论

      本文标题:Android 权限管理--01:RuntimePermissi

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