4. 运行时权限请求流程:检查权限状态、请求权限对话框、处理用户选择结果

Android 13 将通知权限(POST_NOTIFICATIONS)纳入运行时权限体系。应用在首次发送通知前,必须先通过标准的运行时权限流程:检查 → 请求 → 处理用户选择。本章详解每一步的原理、API 调用、代码实现及调试方法。

概念要点: 运行时权限在 Android 6.0 (API 23) 引入,Android 13 新增通知权限属于 dangerous permission。系统弹窗由 PackageManager 控制,用户可选择“允许”、“拒绝”或“不再询问”。
环境前提: 确保 targetSdkVersion >= 33,并在 AndroidManifest.xml 声明 <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>。
Android 13 运行时权限请求流程 1. 检查权限状态 ContextCompat.checkSelfPermission() 权限已授予? 直接发送通知 2. 请求权限对话框 ActivityCompat.requestPermissions() 3. 用户选择结果 onRequestPermissionsResult() 同意 → 发送通知 拒绝 → 引导用户开启 跳转系统设置 关键 API • checkSelfPermission • requestPermissions • onRequestPermissionsResult 注意 Android 13 通知权限

4.1 检查权限状态

在发起请求前必须检查当前权限状态,避免重复弹窗。使用 ContextCompat.checkSelfPermission(),返回 PackageManager.PERMISSION_GRANTED (0) 或 PERMISSION_DENIED (-1)。

if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
        == PackageManager.PERMISSION_GRANTED) {
    // 权限已授予,直接发送通知
} else {
    // 权限未授予,准备请求
}

如果当前 API 版本低于 33(Android 13),则直接视为权限已授予(通知权限在低版本系统上由安装时授权管理)。

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
    // 低版本无条件允许发送通知
    return;
}

4.2 请求权限对话框

当权限未授予时,调用 ActivityCompat.requestPermissions() 触发系统弹窗。该方法需要传入当前 Activity、权限数组和请求码。

private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 100;

ActivityCompat.requestPermissions(
    this,
    new String[]{ Manifest.permission.POST_NOTIFICATIONS },
    NOTIFICATION_PERMISSION_REQUEST_CODE
);
重要: 系统弹窗一次只能展示一个权限(单个 Dialog),不可批量请求。用户每次选择后,系统会记住选择(允许/拒绝/不再询问),下次调用 requestPermissions() 可能直接返回结果而不弹窗。

4.3 处理用户选择结果

系统通过 Activity 的 onRequestPermissionsResult() 回调返回结果。需重写该方法,判断请求码、权限名和 grantResults。

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 用户同意 → 发送通知
            sendNotification();
        } else {
            // 用户拒绝
            if (!ActivityCompat.shouldShowRequestPermissionRationale(
                    this, Manifest.permission.POST_NOTIFICATIONS)) {
                // 用户勾选“不再询问”或永久拒绝
                showSettingsRedirectDialog();
            } else {
                // 普通拒绝,可以再次解释后重新请求
                showPermissionExplanationSnackbar();
            }
        }
    }
}
常见陷阱: 在 Fragment 中处理时,使用 requestPermissions() 和 onRequestPermissionsResult() 需通过 Fragment 自身调用,而非 Activity。在 Compose 中建议使用 rememberLauncherForActivityResult 或 PermissionState。

4.4 调试与验证

使用 ADB 命令模拟权限状态,快速验证逻辑:

# 授予通知权限
adb shell pm grant com.example.app android.permission.POST_NOTIFICATIONS

# 撤销通知权限
adb shell pm revoke com.example.app android.permission.POST_NOTIFICATIONS

# 查看当前应用权限列表
adb shell dumpsys package com.example.app | grep -A10 "requested permissions"

在 Logcat 中过滤权限相关日志:

adb logcat -s "PackageManager:V NotificationService:V"

4.5 完整实现流程(Activity 示例)

步骤操作关键方法/代码
1权限检查checkSelfPermission(POST_NOTIFICATIONS)
2条件判断已授权 → 发送通知;未授权 → 下一步
3请求权限requestPermissions(…)
4处理回调onRequestPermissionsResult 判断 grantResults
5结果分支同意 → 执行通知;拒绝 → 引导或解释
public class MainActivity extends AppCompatActivity {
    private static final int REQ_NOTIFICATION = 1001;

    public void onClickSendNotification(View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
                    == PackageManager.PERMISSION_GRANTED) {
                sendNotification();
            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{ Manifest.permission.POST_NOTIFICATIONS },
                        REQ_NOTIFICATION);
            }
        } else {
            sendNotification();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQ_NOTIFICATION) {
            if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
                sendNotification();
            } else {
                // 拒绝处理
                if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
                        Manifest.permission.POST_NOTIFICATIONS)) {
                    // 不再询问 → 引导用户去设置
                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                            Uri.parse("package:" + getPackageName()));
                    startActivity(intent);
                } else {
                    // 普通拒绝 → 显示解释后再次请求
                    Toast.makeText(this, "需要通知权限才能发送提醒", Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    private void sendNotification() {
        // 发送通知逻辑(第5章详解)
    }
}

4.6 常见错误与优化

  • 错误: 在未声明权限的 AndroidManifest.xml 中请求 → 会立即收到 PERMISSION_DENIED,且无弹窗。
  • 错误: 在子线程调用 requestPermissions() → 抛出 IllegalStateException。
  • 优化: 检测用户曾拒绝并勾选“不再询问”后,直接跳转应用设置页,避免无谓弹窗。
  • 优化: 在 Activity 的 onResume() 中再次检查权限状态,处理用户从设置页返回后的状态变化。
性能提醒: 权限请求是同步操作(检查阶段),不会阻塞主线程;但弹窗由系统服务异步处理,回调保证在主线程执行。无需额外线程同步。

至此,你已经掌握 Android 13 通知权限的完整运行时请求流程。下一步(第5章)将实现通知渠道创建和通知发送,将本章获取的权限转化为真实通知。