第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 流程
- 检查权限是否已授予 (
ContextCompat.checkSelfPermission) - 若未授予,调用
ActivityCompat.requestPermissions(activity, permissions, requestCode) - 系统弹出权限对话框
- 用户选择“允许”或“拒绝”
- Activity/Fragment 的
onRequestPermissionsResult()被系统自动调用 - 在回调内根据
requestCode和grantResults处理后续逻辑
ActivityResultLauncher 流程
- 在 Activity/Fragment 初始化阶段(如
onCreate/onCreateView)注册一个ActivityResultLauncher:
registerForActivityResult(new RequestPermissionRequestContract(), callback) - 检查权限是否已授予
- 若未授予,调用
launcher.launch(permission) - 系统弹出权限对话框
- 用户操作后,
RequestPermissionRequestContract自动处理回调,将结果传给注册时设置的ActivityResultCallback - 在回调中根据
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 的新方式。
4.8 迁移建议
- 新项目:一律使用
ActivityResultLauncher + RequestPermissionRequestContract,代码更简洁,且支持 Compose。 - 旧项目维护:如果已有大量
onRequestPermissionsResult逻辑,可逐步替换;推荐在新增权限请求时直接采用新方案。 - 混合使用:两种方案可以共存于同一 Activity,但注意不要为同一权限同时注册两种回调,避免逻辑冲突。
最佳实践:在
onCreate 中一次性注册 launcher,并将回调与 ViewModel 或业务逻辑层关联,避免 UI 逻辑混乱。