7、权限与Fragment:Fragment中请求权限、onRequestPermissionsResult分发、兼容性处理

Fragment 里的权限请求,说实话,是很多 Android 开发者的“噩梦”。

为什么?因为 Fragment 的生命周期比 Activity 更复杂,而且权限回调的分发机制,稍不注意就会踩坑。我自己刚转 Android 那会儿,就因为在 Fragment 里直接调了 requestPermissions(),结果回调死活收不到……排查了半天,最后发现是 Activity 没做分发。

今天我们就来彻底搞定这件事。

7.1 Fragment 中请求权限的基本姿势

在 Fragment 里请求权限,其实和 Activity 很像。核心方法就是 requestPermissions(),但注意——这个方法 Fragment 自己就有,不需要去拿 Activity 的。

// 在 Fragment 中直接调用
requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_CAMERA);

嗯,这里有个细节:Fragment 的 requestPermissions() 内部其实调用了 Activity 的版本,但它会额外做一件事——把当前 Fragment 的引用传进去。这样回调才能正确分发到 Fragment。

我个人习惯在 Fragment 里定义一个常量来标记请求码,比如:

private static final int REQUEST_CODE_CAMERA = 100;

这样后面处理回调时,一眼就能看出是哪个权限。

7.2 onRequestPermissionsResult 的分发机制

这是最容易出问题的地方。你想想看,当用户点击“允许”或“拒绝”后,系统会回调 Activity 的 onRequestPermissionsResult()。但如果你的 Fragment 也等着这个回调,那 Activity 就必须手动把结果分发给 Fragment。

说白了,就是 Activity 要“转发”一下。

核心原则:Activity 必须调用 super.onRequestPermissionsResult(),否则 Fragment 收不到回调。

来看一个标准的 Activity 实现:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    // 其他逻辑...
}

注意那个 super 调用。如果你忘了写,Fragment 里的 onRequestPermissionsResult() 永远不会被触发。我曾经在一个遗留项目里排查了整整一下午,最后发现就是 Activity 的 super 被注释掉了……

那 Fragment 这边怎么收呢?很简单:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE_CAMERA) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限已授予,打开相机
        } else {
            // 权限被拒绝
        }
    }
}

这里有个坑:如果 Fragment 嵌套了 Fragment(比如 ViewPager 里套 Fragment),那么子 Fragment 的权限回调需要父 Fragment 手动分发。系统不会自动帮你做这件事。

避坑指南:我曾经在多层 Fragment 嵌套时,子 Fragment 请求权限后回调丢失。后来发现,每一层 Fragment 的 onRequestPermissionsResult() 都必须调用 super,并且父 Fragment 要主动调用子 Fragment 的对应方法。否则回调就断了。

7.3 兼容性处理:Fragment 与 Support Library

如果你还在用旧的 android.app.Fragment(API 28 之前),那兼容性是个大问题。因为老版本的 Fragment 对权限回调的支持并不完善。

我个人强烈建议:统一使用 AndroidX 的 Fragment。也就是 androidx.fragment.app.Fragment。它内部已经处理好了权限分发的逻辑,你只需要保证 Activity 继承自 AppCompatActivityFragmentActivity

Fragment 类型 是否需要手动分发 推荐度
android.app.Fragment 是(且容易出问题) ❌ 不推荐
androidx.fragment.app.Fragment 否(自动分发) ✅ 强烈推荐

如果你因为某些原因必须用老版本 Fragment,那记得在 Activity 里手动遍历所有 Fragment 并分发回调。代码大概长这样:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    // 手动分发给所有 Fragment
    List<Fragment> fragments = getFragmentManager().getFragments();
    for (Fragment fragment : fragments) {
        if (fragment != null) {
            fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

嗯,这代码看着就烦。所以还是用 AndroidX 吧。

7.4 知识体系图:Fragment 权限请求全流程

下面这张图,我画了 Fragment 请求权限的完整流程。你可以把它当作一个“检查清单”,每次写权限代码时对照一下。

Fragment 权限请求全流程 Fragment 调用 requestPermissions() Activity 收到系统回调 Activity 调用 super.onRequestPermissionsResult() Fragment 收到 onRequestPermissionsResult() 回调丢失!Fragment 无响应

小提示:如果你用 AndroidX,Activity 的 super 调用会自动把结果分发给所有关联的 Fragment。但如果你用了 FragmentPagerAdapterFragmentStatePagerAdapter,记得检查 Fragment 是否还活着——有时候 Fragment 已经被销毁了,回调就没人收了。

7.5 实战中的几个常见问题

最后,我总结几个我在项目中真实遇到过的坑:

  • 问题一:Fragment 里调了 requestPermissions,但回调没触发。
    原因:Activity 没调 super,或者 Fragment 被 detach 了。解决方案:检查 Activity 代码,确保 super 存在;同时确认 Fragment 在回调时仍处于 added 状态。
  • 问题二:多个 Fragment 同时请求权限,回调混乱。
    原因:请求码冲突。解决方案:每个 Fragment 使用独立的请求码,最好用 System.identityHashCode() 生成唯一码。
  • 问题三:用户勾选“不再询问”后,权限弹窗不再弹出。
    原因:系统行为如此。解决方案:调用 shouldShowRequestPermissionRationale() 判断,如果返回 false,引导用户去设置页手动开启。

嗯,关于 Fragment 的权限控制,核心就是三点:用 AndroidX、Activity 记得调 super、注意 Fragment 生命周期。把这三点刻在脑子里,基本不会出大问题。

好了,这一节就到这里。下一节我们会聊权限与 Service 的结合,那又是另一番风景了。


公众号:蓝海资料掘金营,微信deep3321