第4章:权限申请请求方法:requestPermissions与ActivityResultLauncher对比

Android 在运行时权限申请上提供了两套主流 API:传统 requestPermissions + onRequestPermissionsResult 和 基于 Activity Result API 的 ActivityResultLauncher。前者自 API 23 起存在,后者在 AndroidX Activity 1.2.0 之后全面推广。本章从原理、实现流程、代码示例、调试到稳定性优化进行完整对比,帮助你根据项目场景选用合适方案。

4.1 原理差异

对比维度 requestPermissions (传统) ActivityResultLauncher (新 API)
引入版本 Android 6.0 (API 23) AndroidX Activity 1.2.0 / Fragment 1.3.0
回调方式 重写 onRequestPermissionsResult() 方法 通过注册的 ActivityResultCallback 回调
生命周期绑定 Activity/Fragment 必须重写回调方法 自动绑定到注册时所在的 LifecycleOwner
结果接收时机 系统弹窗关闭后立即回调 同传统方式,但回调在 onStart() 之后执行
代码耦合 高:回调与 Activity/Fragment 强耦合 低:回调可内联或作为单独对象传入
内存泄漏风险 较低(回调是生命周期方法) 需注意匿名内部类持有外部引用,使用 registerForActivityResult 正确注册
推荐程度 (2025+) 不建议新项目使用 官方推荐

4.2 实现流程对比

传统 requestPermissions 流程

  1. 检查权限是否已授予 (ContextCompat.checkSelfPermission)
  2. 若未授予,调用 ActivityCompat.requestPermissions(activity, permissions, requestCode)
  3. 系统弹出权限对话框
  4. 用户选择“允许”或“拒绝”
  5. Activity/Fragment 的 onRequestPermissionsResult() 被系统自动调用
  6. 在回调内根据 requestCode 和 grantResults 处理后续逻辑

ActivityResultLauncher 流程

  1. 在 Activity/Fragment 初始化阶段(如 onCreate / onCreateView)注册一个 ActivityResultLauncher:
    registerForActivityResult(new RequestPermissionRequestContract(), callback)
  2. 检查权限是否已授予
  3. 若未授予,调用 launcher.launch(permission)
  4. 系统弹出权限对话框
  5. 用户操作后,RequestPermissionRequestContract 自动处理回调,将结果传给注册时设置的 ActivityResultCallback
  6. 在回调中根据 Boolean 结果处理后续逻辑
要点:registerForActivityResult 必须在 onStart() 之前注册,否则会抛出 IllegalStateException。最佳实践是在 onCreate() 或 onCreateView() 中注册。

4.3 代码实现对比

传统方式(Activity 示例)

public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_CAMERA = 100;

    public void requestCameraPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CAMERA) {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 权限已授予,执行相机操作
            } else {
                // 权限被拒绝,可提示用户
            }
        }
    }
}

ActivityResultLauncher 方式(Activity 示例)

public class MainActivity extends AppCompatActivity {
    private ActivityResultLauncher<String> requestCameraLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 注册 launcher,必须在 onStart 之前
        requestCameraLauncher = registerForActivityResult(
                new RequestPermissionRequestContract(),
                new ActivityResultCallback<Boolean>() {
                    @Override
                    public void onActivityResult(Boolean isGranted) {
                        if (isGranted) {
                            // 权限已授予,执行相机操作
                        } else {
                            // 权限被拒绝,可提示用户
                        }
                    }
                }
        );
    }

    public void requestCameraPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestCameraLauncher.launch(Manifest.permission.CAMERA);
        }
    }
}
注意:传统方式中如果忘记调用 super.onRequestPermissionsResult(),某些 Fragment 或第三方库可能无法正常收到回调。ActivityResultLauncher 则不存在此问题,回调完全由系统管理。

4.4 调试要点

  • 检查 launcher 注册时机:若在 onResume() 之后注册,会直接崩溃,日志输出 IllegalStateException: LifecycleOwner is in RESUMED state。
  • 传统方式 requestCode 冲突:多个权限请求使用相同 requestCode 会导致逻辑混乱,建议为每个权限分配唯一请求码。
  • 权限对话框被用户永久拒绝:两种方式都需要再次调用 shouldShowRequestPermissionRationale() 或通过 ActivityResultCallback 返回的 false 判断后引导用户去设置页。
  • 调试日志:可在 onRequestPermissionsResult 或 launcher 回调中打印权限结果,确认系统行为。

4.5 常见错误

错误现象 原因 解决方案
点击授权后回调未触发 未重写 onRequestPermissionsResult 或未注册 launcher 检查方法签名和注册时机
Launcher 重复注册导致内存泄漏 在匿名内部类中引用了 Activity/Fragment 使用 lambda 或弱引用,或在 onDestroy 中取消注册(非必须)
requestCode 越界 传统方式 requestCode 超过 16 位 使用 < 0xFFFF 的值

4.6 性能与稳定性优化

  • 请求频率控制:不要在短时间内连续调用 requestPermissions 或 launch,避免系统对话框叠加。
  • 生命周期感知:ActivityResultLauncher 已自动感知生命周期,但回调中避免进行耗时操作或更新已销毁的 UI。推荐在回调内使用 if (isDestroyed()) return; 检查。
  • 结果缓存:权限授予状态由系统持久化保存,无需手动缓存。但 onRequestPermissionsResult 可能因为 Activity 重建而丢失,传统方式需配合 onSaveInstanceState 保存请求状态。ActivityResultLauncher 则自动重建 launcher 实例,回调仍然有效。
  • 避免嵌套请求:若用户在权限回调中再次请求另一个权限,容易导致用户困惑。建议一次只请求一个权限或使用 requestMultiplePermissions / RequestMultiplePermissions() Contract。

4.7 两种方式调用链路对比图

下面 SVG 图直观展示了传统 requestPermissions 与 ActivityResultLauncher 从申请到回调的完整路径。左侧为传统方式,右侧为基于 Activity Result API 的新方式。

requestPermissions vs ActivityResultLauncher 调用链路 ActivityCompat.requestPermissions() 系统权限对话框 用户点击允许/拒绝 onRequestPermissionsResult() 根据请求码处理结果 registerForActivityResult() launcher.launch(permission) 系统权限对话框 用户点击允许/拒绝 ActivityResultCallback 回调 左侧:传统方式需重写方法 右侧:注册式回调、生命自动管理

4.8 迁移建议

  • 新项目:一律使用 ActivityResultLauncher + RequestPermissionRequestContract,代码更简洁,且支持 Compose。
  • 旧项目维护:如果已有大量 onRequestPermissionsResult 逻辑,可逐步替换;推荐在新增权限请求时直接采用新方案。
  • 混合使用:两种方案可以共存于同一 Activity,但注意不要为同一权限同时注册两种回调,避免逻辑冲突。
最佳实践:在 onCreate 中一次性注册 launcher,并将回调与 ViewModel 或业务逻辑层关联,避免 UI 逻辑混乱。