9、权限与RxJava:RxPermission库原理、链式调用、错误处理
说实话,权限请求这块,我早期做项目时都是自己手写回调。那时候代码又臭又长,Activity里塞满了onRequestPermissionsResult。后来接触了RxPermission,才觉得这事儿可以做得优雅一点。
今天我们就聊聊RxPermission。它不是什么黑科技,但设计思路挺巧妙的。说白了,就是把Android那个啰嗦的权限回调,包装成了RxJava的Observable流。你想想看,链式调用、线程切换、错误处理,这些RxJava的看家本领全都能用上。
9.1 RxPermission的核心原理
RxPermission是怎么工作的?我拆开源码看过,其实就三步:
- 创建一个透明的Fragment,挂到当前Activity上
- 在这个Fragment里发起权限请求
- 把结果封装成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() {
// 显示权限被拒绝的提示
}
}
这个例子看起来简单,但包含了几个关键点:
- 权限请求在点击事件中触发,不是一启动就弹
- 用了
subscribeOn和observeOn确保在主线程处理 - 有错误处理,不会因为异常导致崩溃
9.5 RxPermission的局限性
说实话,RxPermission也不是万能的。我遇到过几个坑:
- 不能用于Service:因为需要Activity的FragmentManager
- 不能用于非Activity上下文:比如ApplicationContext
- Android 11的包可见性权限:这个需要特殊处理
如果你遇到这些场景,还是得用传统的requestPermissions()方法。不过大多数情况下,RxPermission已经够用了。
我的建议: 如果你的项目已经用了RxJava,那RxPermission是首选。如果没用RxJava,为了一个权限请求引入整个RxJava,有点得不偿失。这时候可以考虑用协程或者LiveData的方案。
9.6 知识体系总览
下面这张图,是我整理的RxPermission核心知识结构。你可以对照着回顾一下:
这张图把RxPermission的三个核心部分串起来了。原理是基础,链式调用是用法,错误处理是保障。三者缺一不可。
好了,关于RxPermission的内容就这些。说白了,它就是帮我们把权限请求这件事,从"回调地狱"变成了"响应式流"。我个人觉得,这种设计思路值得学习——把复杂的东西包装成简单的接口,让调用方只关心业务逻辑。
如果你在实际项目中遇到权限相关的问题,欢迎交流。我踩过的坑,说不定能帮你省点时间。