Skip to content

运行时权限请求

追踪 Android 17 从 Activity.requestPermissions 到 PermissionController、授权写入和结果回调的源码路径。

基于android-17.0.0_r1
AndroidRuntime PermissionPermissionControllerPermissionManagerService源码阅读

运行时权限请求 ​

本文承接 权限类型、权限服务架构 和 权限定义与声明。主题限定为应用请求 dangerous/runtime 权限的运行期流程:应用如何决定是否启动授权界面,PermissionController 如何读取当前状态并把用户选择写回,Activity 如何把结果交回调用方,以及“没有任何可请求权限”时为什么不启动界面。本文不展开权限组 UI 的全部文案,也不把 checkSelfPermission 的静态检查等同于用户授权流程。

1. 入口对象 ​

1.1 Activity 状态 ​

源码文件:frameworks/base/core/java/android/app/Activity.java

java
public final void requestPermissions(@NonNull String[] permissions,
        int requestCode) {
    requestPermissions(permissions, requestCode, getDeviceId());
}

@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public final void requestPermissions(@NonNull String[] permissions,
        int requestCode, int deviceId) {
    if (getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) {
        onRequestPermissionsResult(requestCode,
                new String[0], new int[0], deviceId);
    }
    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");
        onRequestPermissionsResult(requestCode,
                new String[0], new int[0], deviceId);
        return;
    }

mHasCurrentPermissionsRequest 是 Activity 实例的 owner,用来阻止同一 Activity 并发启动两组权限请求。空数组回调表示取消,不是“所有权限被拒绝”。targetSdk 低于 M 的应用不进入运行时权限 UI,因为它们的权限在安装阶段处理;源码随后仍继续执行参数检查,这个控制流需要结合完整实现阅读。

1.2 请求前置检查 ​

java
if (!getAttributionSource().getRenouncedPermissions().isEmpty()) {
    for (int i = 0; i < permissions.length; i++) {
        if (getAttributionSource().getRenouncedPermissions()
                .contains(permissions[i])) {
            throw new IllegalArgumentException(
                    "Cannot request renounced permission: "
                            + permissions[i]);
        }
    }
}

final Context context = getDeviceId() == deviceId
        ? this : createDeviceContext(deviceId);

renounced permission 由 attribution source 声明放弃,应用不能再通过 requestPermissions 请求;device-aware API 则为虚拟设备创建对应 context。deviceId 会在 PackageManager/PermissionManager 门面中转换为 persistent device ID,最终成为权限状态查询的维度。

2. 短路路径 ​

2.1 请求状态 ​

源码文件:frameworks/base/core/java/android/app/Activity.java

java
if (Flags.permissionRequestShortCircuitEnabled()) {
    final int[] permissionsState =
            getPermissionRequestStates(context, permissions);
    boolean hasRequestablePermission = false;
    for (int state : permissionsState) {
        if (state == Context.PERMISSION_REQUEST_STATE_REQUESTABLE) {
            hasRequestablePermission = true;
            break;
        }
    }
    if (!hasRequestablePermission) {
        mHasCurrentPermissionsRequest = true;
        final int[] results = new int[permissionsState.length];
        for (int i = 0; i < permissionsState.length; i++) {
            results[i] = permissionsState[i]
                    == Context.PERMISSION_REQUEST_STATE_GRANTED
                    ? PackageManager.PERMISSION_GRANTED
                    : PackageManager.PERMISSION_DENIED;
        }

feature flag 开启时,Activity 逐个调用 Context.getPermissionRequestState。只有至少一个权限处于 requestable 才需要 PermissionController;已授予或不可请求的组合可以在应用进程内决定结果。

2.2 异步回调 ​

java
mHandler.post(() -> {
    mHasCurrentPermissionsRequest = false;
    onRequestPermissionsResult(requestCode,
            permissions, results, deviceId);
});
return;

即使短路,源码也通过 Handler 异步回调,保持与真正启动 Activity 的时序一致。回调前清除 mHasCurrentPermissionsRequest,因此回调处理期间可以发起下一次请求。这个分支不会调用 PermissionController,也不会改变权限状态。

2.3 状态 owner ​

Context.PERMISSION_REQUEST_STATE_GRANTED/REQUESTABLE/UNREQUESTABLE 由 PermissionManagerService 的实现层计算:已 grant 返回 granted;runtime 且允许弹窗返回 requestable;策略固定、永久拒绝、非 runtime 或声明无效返回 unrequestable。Activity 只消费分类,不重新解释 permission flags。

3. 启动授权界面 ​

3.1 构造 Intent ​

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java

java
@UnsupportedAppUsage
public Intent buildRequestPermissionsIntent(
        @NonNull String[] permissions) {
    if (ArrayUtils.isEmpty(permissions)) {
        throw new IllegalArgumentException(
                "permission cannot be null or empty");
    }
    final Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
    intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
    intent.setPackage(getPermissionControllerPackageName());
    return intent;
}

Intent 明确指向当前 PermissionController 包,并携带原始权限数组。Activity 不直接启动一个通用 Settings 页面,而是通过 PackageManager 取得配置的 PermissionController package name,允许系统组件替换实现。

3.2 Activity 启动 ​

java
final PackageManager packageManager = context.getPackageManager();
final Intent intent = packageManager.buildRequestPermissionsIntent(
        permissions);
startActivityForResult(
        REQUEST_PERMISSIONS_WHO_PREFIX,
        intent, requestCode, null);
mHasCurrentPermissionsRequest = true;

设置请求标志发生在 startActivityForResult 之后。若启动失败,Activity 生命周期仍可能返回空结果;mHasCurrentPermissionsRequest 会在结果分发路径中清除。

3.3 Controller owner ​

授权 UI 位于 PermissionController 模块的 GrantPermissionsActivity。它是用户交互 owner,但不拥有最终权限数据库;用户点击允许/拒绝后,Controller 调用 PermissionManager/IPermissionManager 的 runtime grant/revoke API,AccessState 才发生变化。

4. 用户选择 ​

4.1 状态与 rationale ​

源码文件:frameworks/base/core/java/android/app/Activity.java

java
public boolean shouldShowRequestPermissionRationale(
        @NonNull String permission) {
    if (Flags.shouldShowPermissionRationaleInContextEnabled()) {
        return super.shouldShowRequestPermissionRationale(permission);
    }
    return getPackageManager()
            .shouldShowRequestPermissionRationale(permission);
}

Activity 只选择 API 路径:feature flag 开启时由 Context 处理,否则通过 PackageManager 进入 PermissionManagerService。rationale 的具体 flags 解释属于权限服务实现;通常涉及 user-set/user-fixed/policy-fixed,但 UI 是否展示还取决于 PermissionController 的策略。

4.2 允许与拒绝 ​

用户选择后,Controller 对每个 requestable permission 执行 grant 或 revoke。允许写入 runtime grant flags,拒绝可能设置 user-set/user-fixed 或 restriction flags;“拒绝”不是删除 permission definition,也不是修改 Manifest。

4.3 仅本次 ​

“仅本次”先授予 runtime permission,再通过 startOneTimePermissionSession 创建按用户 manager;超时、进程消失或主动停止会调用 revoke。它与永久 grant 使用同一个 PermissionManagerService grant/revoke 接口,但额外拥有 OneTimePermissionUserManager 的计时与 UID 观察状态。

5. 服务端写入 ​

5.1 授予门面 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java

java
public void grantRuntimePermission(String packageName,
        String permissionName, String persistentDeviceId,
        int userId) {
    mPermissionManagerServiceImpl.grantRuntimePermission(
            packageName, permissionName,
            persistentDeviceId, userId);
}

public void revokeRuntimePermission(String packageName,
        String permissionName, String persistentDeviceId,
        int userId, String reason) {
    mPermissionManagerServiceImpl.revokeRuntimePermission(
            packageName, permissionName,
            persistentDeviceId, userId, reason);
}

门面只转发到 PermissionManagerServiceInterface;调用者权限、包存在性、runtime 类型、用户范围、AccessState mutation 和 UID 终止在实现层完成。PermissionController 不能通过 UI 身份绕过服务端检查。

5.2 结果与进程 ​

grant/revoke 返回后,PermissionController 构造每个权限的 PERMISSION_GRANTED/PERMISSION_DENIED 数组。撤销通常会触发 killUid,使已运行进程不能继续依赖旧权限;一次性权限的 revoke 也使用该机制,除测试专用 skip-kill API 外不能假设进程继续存活。

6. Activity 回传 ​

6.1 分发结果 ​

源码文件:frameworks/base/core/java/android/app/Activity.java

java
private void dispatchRequestPermissionsResult(
        int requestCode, Intent data) {
    mHasCurrentPermissionsRequest = false;
    final String[] permissions = data != null
            ? data.getStringArrayExtra(
                    PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES)
            : new String[0];
    final int[] grantResults = data != null
            ? data.getIntArrayExtra(
                    PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS)
            : new int[0];
    final int deviceId = data != null
            ? data.getIntExtra(
                    PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID,
                    Context.DEVICE_ID_DEFAULT)
            : Context.DEVICE_ID_DEFAULT;
    onRequestPermissionsResult(
            requestCode, permissions, grantResults, deviceId);
}

PermissionController 退出后,Activity 从 result Intent 读取权限名、结果和 deviceId。data 为空或授权 Activity 崩溃时使用空数组,调用方应把它当作请求中断。结果数组与权限数组按索引对应,不能只检查数组长度。

6.2 Fragment 分发 ​

java
private void dispatchRequestPermissionsResultToFragment(
        int requestCode, Intent data, Fragment fragment) {
    final String[] permissions = data != null
            ? data.getStringArrayExtra(
                    PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES)
            : new String[0];
    final int[] grantResults = data != null
            ? data.getIntArrayExtra(
                    PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS)
            : new int[0];
    fragment.onRequestPermissionsResult(
            requestCode, permissions, grantResults);
}

Activity 是结果 owner,Fragment 只是沿相同 requestCode 和数组继续分发。deviceId overload 不向旧 Fragment API 暴露,使用虚拟设备的应用应在 Activity/Context 路径处理设备维度。

7. 完整时序 ​

8. 失败与边界 ​

8.1 不可请求 ​

targetSdk < M、权限已固定/不可请求、权限未声明、renounced permission 和空权限数组都可能在 UI 前结束。短路 feature flag 开启时这些状态直接异步回调;关闭时则可能交给 PermissionController 再决定。

8.2 并发请求 ​

同一 Activity 同时请求第二组权限会收到空数组取消回调,第一组仍由当前 UI 处理。应用若把空结果当作“用户拒绝所有权限”,会错误地覆盖自己的状态机。

8.3 用户中断 ​

授权 Activity 被销毁、进程重启或系统中断时,Activity 结果 data 可能为空,最终得到空权限/结果数组。该路径不代表 PermissionManagerService 写入了 DENIED;应用应重新查询当前权限。

8.4 AppOps 与策略 ​

即使 runtime grant 成功,AppOps、hard/soft restricted、设备策略和 attribution 仍可能阻止真实数据访问。请求结果只反映 permission grant,不是传感器、位置或存储 API 的最终成功保证。

9. 验证方法 ​

9.1 短路与 UI ​

对一个已授予权限和一个 requestable 权限组合发起请求。短路 flag 开启时,混合请求仍应启动 UI;全部已授予时不启动 UI 但异步回调;并发第二次请求返回空数组。通过日志和 Activity 生命周期确认三条分支。

9.2 用户选择 ​

分别点击允许、拒绝和仅本次,读取 onRequestPermissionsResult 与 checkSelfPermission。仅本次还需等待超时/进程消失后复查 revoke;结果回调的 granted 不代表一次性 session 已永久授权。

9.3 device-aware ​

在虚拟设备上使用带 deviceId 的 overload,确认 PermissionManager 查询和回调携带同一 deviceId;默认设备使用 Context.DEVICE_ID_DEFAULT。设备不存在或无效时应在 context/服务端检查阶段失败,而不是写入错误用户的状态。

9.4 中断与恢复 ​

在授权 UI 显示时旋转、停止或杀死应用进程,观察回调是否为空数组并重新查询权限。该实验验证 Activity 结果恢复边界,不证明 PermissionController 是否已经完成某个 grant,最终状态必须通过服务查询确认。

bash
adb shell dumpsys package <package.name>
adb shell pm check-permission <permission> <package.name> --user 0
adb shell appops get <package.name>

10. 源码路线 ​

  1. Activity.requestPermissions:targetSdk、并发、renounced、deviceId 和短路。
  2. Activity.getPermissionRequestStates:requestable/granted/unrequestable 分类。
  3. PackageManager.buildRequestPermissionsIntent:PermissionController Intent。
  4. GrantPermissionsActivity:读取请求、用户选择和结果 Intent。
  5. PermissionManagerService.grantRuntimePermission/revokeRuntimePermission:门面委托。
  6. PermissionService/AccessState:实际 flags mutation、限制和持久化。
  7. Activity.dispatchRequestPermissionsResult:结果数组、空 data 和 deviceId 回传。

运行时权限请求是一条跨进程、跨状态域的协议:Activity 负责请求节流和结果生命周期,PermissionController 负责用户决策,PermissionManagerService 负责权限 API 门面,PermissionService/AccessState 负责持久状态,AppOps 和资源服务再决定真实数据访问。只有把短路、用户中断、一次性权限和 device-aware 分支一起考虑,才能正确解释“回调已成功但 API 仍被拒绝”或“没有弹窗却收到结果”的现象。