9、权限与RxJava:RxPermission库原理、链式调用、错误处理

说实话,权限请求这块,我早期做项目时都是自己手写回调。那时候代码又臭又长,Activity里塞满了onRequestPermissionsResult。后来接触了RxPermission,才觉得这事儿可以做得优雅一点。

今天我们就聊聊RxPermission。它不是什么黑科技,但设计思路挺巧妙的。说白了,就是把Android那个啰嗦的权限回调,包装成了RxJava的Observable流。你想想看,链式调用、线程切换、错误处理,这些RxJava的看家本领全都能用上。

9.1 RxPermission的核心原理

RxPermission是怎么工作的?我拆开源码看过,其实就三步:

  1. 创建一个透明的Fragment,挂到当前Activity上
  2. 在这个Fragment里发起权限请求
  3. 把结果封装成Observable发射出来

为什么要用Fragment?因为Fragment的生命周期和Activity绑定,而且它可以自己处理onRequestPermissionsResult回调。这样你的Activity就不用重写那个方法了。

核心思想: 用Fragment做代理,把权限结果转成RxJava流。

我举个例子,你一看就明白:

// 传统方式
ActivityCompat.requestPermissions(this, 
    new String[]{Manifest.permission.CAMERA}, 
    REQUEST_CODE);
    
@Override
public void onRequestPermissionsResult(...) {
    // 处理结果
}

// RxPermission方式
RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.request(Manifest.permission.CAMERA)
    .subscribe(granted -> {
        if (granted) {
            // 打开相机
        }
    });

看到区别了吗?传统方式你得自己维护请求码、处理回调。RxPermission把这些都封装掉了。你只需要关心结果就行。

9.2 链式调用:优雅地组合权限

实际项目中,很少只请求一个权限。比如你要拍照并保存到相册,就需要相机和存储两个权限。这时候链式调用的优势就出来了。

RxPermissions rxPermissions = new RxPermissions(this);

rxPermissions.request(
    Manifest.permission.CAMERA,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
)
.subscribe(granted -> {
    if (granted) {
        // 两个权限都同意了
        startCamera();
    } else {
        // 至少有一个被拒绝
        showPermissionDeniedDialog();
    }
});

这里有个细节要注意:request()方法可以传多个权限,但返回的是boolean。只有所有权限都同意,才返回true。这其实挺合理的——你总不能相机开了,存储没开,然后去拍照吧?

小技巧: 如果你需要分别判断每个权限的结果,可以用requestEach()方法。它会返回一个Permission对象,里面有权限名和结果。

rxPermissions.requestEach(
    Manifest.permission.CAMERA,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
)
.subscribe(permission -> {
    if (permission.granted) {
        Log.d("TAG", permission.name + " 同意了");
    } else if (permission.shouldShowRequestPermissionRationale) {
        // 用户拒绝了,但没有勾选"不再询问"
        Log.d("TAG", permission.name + " 被拒绝,可再次请求");
    } else {
        // 用户勾选了"不再询问"
        Log.d("TAG", permission.name + " 被永久拒绝");
    }
});

嗯,这里要特别说一下shouldShowRequestPermissionRationale。我在项目中遇到过,用户第一次拒绝后,第二次请求时如果这个字段是false,说明用户勾选了"不再询问"。这时候你再弹权限对话框也没用了,得引导用户去设置里手动打开。

9.3 错误处理:别让崩溃毁了体验

RxJava的错误处理机制,在权限请求中同样适用。最常见的错误就是Activity销毁了,但权限请求还没完成。

我记得有一次,用户正在请求权限,突然接了个电话。Activity被回收了,但RxPermission的Fragment还在。结果权限回调时,Activity已经没了,直接抛了个空指针。

怎么解决?用onError回调:

rxPermissions.request(Manifest.permission.CAMERA)
    .subscribe(
        granted -> {
            // 正常处理
        },
        throwable -> {
            // 错误处理
            Log.e("TAG", "权限请求出错", throwable);
            // 可以在这里做降级处理
            showToast("权限请求失败,请重试");
        }
    );

还有更优雅的方式——用compose()操作符绑定生命周期:

rxPermissions.request(Manifest.permission.CAMERA)
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
    .subscribe(granted -> {
        // 安全处理
    });

这样当Activity销毁时,订阅会自动取消,不会再有回调。

注意: 不要在主线程做耗时操作。虽然权限请求本身很快,但如果你在subscribe里做了网络请求或数据库操作,记得用subscribeOn()observeOn()切换线程。

9.4 实战:完整的权限请求流程

说了这么多,我们写一个完整的例子。假设你要做一个扫码功能,需要相机权限:

public class ScanActivity extends AppCompatActivity {
    
    private RxPermissions rxPermissions;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan);
        
        rxPermissions = new RxPermissions(this);
        
        findViewById(R.id.btn_scan).setOnClickListener(v -> {
            requestCameraPermission();
        });
    }
    
    private void requestCameraPermission() {
        rxPermissions
            .request(Manifest.permission.CAMERA)
            .subscribeOn(AndroidSchedulers.mainThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                granted -> {
                    if (granted) {
                        startScan();
                    } else {
                        showPermissionDenied();
                    }
                },
                throwable -> {
                    Log.e("Scan", "权限请求异常", throwable);
                    showToast("权限请求失败");
                }
            );
    }
    
    private void startScan() {
        // 打开相机扫码
    }
    
    private void showPermissionDenied() {
        // 显示权限被拒绝的提示
    }
}

这个例子看起来简单,但包含了几个关键点:

  • 权限请求在点击事件中触发,不是一启动就弹
  • 用了subscribeOnobserveOn确保在主线程处理
  • 有错误处理,不会因为异常导致崩溃

9.5 RxPermission的局限性

说实话,RxPermission也不是万能的。我遇到过几个坑:

  • 不能用于Service:因为需要Activity的FragmentManager
  • 不能用于非Activity上下文:比如ApplicationContext
  • Android 11的包可见性权限:这个需要特殊处理

如果你遇到这些场景,还是得用传统的requestPermissions()方法。不过大多数情况下,RxPermission已经够用了。

我的建议: 如果你的项目已经用了RxJava,那RxPermission是首选。如果没用RxJava,为了一个权限请求引入整个RxJava,有点得不偿失。这时候可以考虑用协程或者LiveData的方案。

9.6 知识体系总览

下面这张图,是我整理的RxPermission核心知识结构。你可以对照着回顾一下:

RxPermission 核心原理:透明Fragment代理权限回调 创建透明Fragment 挂载到Activity 在Fragment中发起 权限请求 结果封装为 Observable发射 链式调用:request() / requestEach() 错误处理:onError + 生命周期绑定

这张图把RxPermission的三个核心部分串起来了。原理是基础,链式调用是用法,错误处理是保障。三者缺一不可。


好了,关于RxPermission的内容就这些。说白了,它就是帮我们把权限请求这件事,从"回调地狱"变成了"响应式流"。我个人觉得,这种设计思路值得学习——把复杂的东西包装成简单的接口,让调用方只关心业务逻辑。

如果你在实际项目中遇到权限相关的问题,欢迎交流。我踩过的坑,说不定能帮你省点时间。

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