使用RxJava时屏幕旋转后Fragment视图为空,Kotlin拓展访问正常
解决Fragment旋转后RxJava Interval访问视图为空崩溃的问题
这问题我之前帮人排查过好多次,本质是屏幕旋转重建Fragment后,旧的RxJava订阅还绑定着已销毁的旧Fragment实例——旧Fragment的视图已经被销毁,Kotlin Android Extensions生成的视图引用自然就成了null,后续Interval回调里调用updateUi()访问这些空视图,直接就触发空指针异常崩溃了。给你几个靠谱的解决思路:
1. 用ViewModel持有Observable,分离业务与视图生命周期
ViewModel在屏幕旋转时不会被销毁,刚好能用来托管RxJava的数据流,避免和Fragment的生命周期绑定冲突:
第一步:创建ViewModel管理Interval逻辑
class MyViewModel : ViewModel() { private val intervalObservable = Observable.interval(1, TimeUnit.SECONDS) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) // 对外暴露可订阅的数据流 fun getIntervalStream(): Observable<Long> = intervalObservable }
第二步:Fragment中订阅ViewModel的数据流,在视图销毁时取消订阅
class MyFragment : Fragment() { private val viewModel by viewModels<MyViewModel>() private var intervalDisposable: Disposable? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 订阅数据流,只在视图存在时更新UI intervalDisposable = viewModel.getIntervalStream() .subscribe { // 这里访问视图前可以先判断view是否存在,更稳妥 if (view != null) { updateUi() } } } override fun onDestroyView() { super.onDestroyView() // 视图销毁时取消订阅,切断旧订阅与Fragment的关联 intervalDisposable?.dispose() } private fun updateUi() { // 用Kotlin Android Extensions访问视图的逻辑 tv_count.text = "Updated" } }
2. 用RxLifecycle自动绑定订阅与Fragment生命周期
如果项目已经引入了RxLifecycle库,可以让订阅自动跟随Fragment的生命周期,在视图销毁时自动取消订阅,省得手动管理Disposable:
class MyFragment : RxFragment() { // 继承RxFragment,或者用RxJava2的绑定方式 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Observable.interval(1, TimeUnit.SECONDS) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) // 绑定到Fragment的DESTROY_VIEW事件,视图销毁时自动取消订阅 .compose(bindUntilEvent(FragmentEvent.DESTROY_VIEW)) .subscribe { updateUi() } } private fun updateUi() { tv_count.text = "Updated" } }
3. 手动管理Disposable(无额外依赖方案)
如果不想引入新库,最简单的方式就是自己维护Disposable对象,在Fragment的onDestroyView方法里主动取消订阅:
class MyFragment : Fragment() { private var intervalDisposable: Disposable? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) intervalDisposable = Observable.interval(1, TimeUnit.SECONDS) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { // 额外加一层判断,确保视图未被销毁 if (isViewCreated) { updateUi() } } } override fun onDestroyView() { super.onDestroyView() // 取消订阅,释放旧Fragment的引用 intervalDisposable?.dispose() intervalDisposable = null } private fun updateUi() { tv_count.text = "Updated" } }
额外提醒
不管用哪种方案,都要记住:Kotlin Android Extensions的视图引用是绑定在Fragment的view实例上的,当onDestroyView调用后,这些引用都会变成null。所以在updateUi()里最好先判断视图是否存在,或者直接用view?.tv_count?.text = "Updated"这种安全调用的方式,双重保险避免空指针。
内容的提问来源于stack exchange,提问作者Roberto Betancourt
相关产品推荐
相关产品推荐

