
上周刚接手一个老Android项目,打开代码的瞬间就有点头大——没有Bean类、共用逻辑写得一团乱,更糟的是,一运行Fragment里的LRecyclerView就崩溃,日志里满屏飘着java.lang.IllegalStateException: Observer ... was not registered。
查了半小时没头绪,最后跟着源码才发现坑在哪。相信不少Android开发接手旧项目时,都遇到过这种“前人埋坑,后人填坑”的糟心场景,今天把解决过程捋清楚,帮大家少走弯路。
当时点击切换Fragment,App直接闪退,Logcat里刷出的错误栈里,最扎眼的就是这句:
java.lang.IllegalStateException: Observer com.github.jdsjlzx.recyclerview.LRecyclerView$DataObserver@97905af was not registered.
at android.database.Observable.unregisterObserver(Observable.java:69)
at android.support.v7.widget.RecyclerView$Adapter.unregisterAdapterDataObserver(RecyclerView.java:6537)
at com.github.jdsjlzx.recyclerview.LRecyclerView.setAdapter(LRecyclerView.java:139)翻译过来就是“这个Observer没注册过,却要执行取消注册操作”。顺着错误栈定位到LRecyclerView.setAdapter()方法,看了源码才明白问题所在:
LRecyclerView的setAdapter()里有段逻辑:先调用unregisterAdapterDataObserver(mDataObserver)取消旧观察者,再注册新的。但如果第一次设置Adapter时,旧Adapter根本没注册过观察者,这时候执行“取消注册”就会直接崩掉——简单说,就是代码没判断“有没有旧Adapter”,上来就瞎执行取消操作。
再看同事写的代码,发现他在Fragment的onResume()和Handler的handleMessage()里都调用了setAdapter(),相当于每次切换Fragment或收到消息,都重复给LRecyclerView设Adapter,这才触发了重复取消注册的问题。
既然问题出在“重复设置Adapter,且没判断旧Adapter是否存在”,那解决办法就很直接:设置Adapter前,先检查LRecyclerView当前有没有Adapter,只有为空时才新创建并设置。
原本同事的代码是这样写的(错误示范):
// 不管有没有旧Adapter,直接new并设置,导致重复调用setAdapter()
LRecyclerViewAdapter mLRecyclerViewAdapter = new LRecyclerViewAdapter(adapter);
lrecyclerView.setAdapter(mLRecyclerViewAdapter);这种写法每次执行都会触发LRecyclerView内部的“取消注册-注册”逻辑,第一次执行时旧Observer不存在,直接崩溃。
我把代码改成了这样(正确写法):
// 关键:先判断当前Adapter是否为空,为空才设置
if (lrecyclerView.getAdapter() == null) {
// 只在第一次创建时new Adapter并设置
LRecyclerViewAdapter mLRecyclerViewAdapter = new LRecyclerViewAdapter(adapter);
lrecyclerView.setAdapter(mLRecyclerViewAdapter);
// 顺便加了下拉刷新和加载更多的配置(可选,根据需求加)
lrecyclerView.setRefreshHeader(new MaterialRefreshHeader(getContext()));
lrecyclerView.setLoadMoreEnabled(true);
}改完后重新运行,Fragment切换、消息接收时都不再重复设置Adapter,Logcat里的“Observer未注册”错误直接消失,LRecyclerView也能正常加载数据了——前后花了不到5分钟。
后来查了LRecyclerView的官方Issue(源码里也给了链接:https://github.com/jdsjlzx/LRecyclerView/issues/115),发现这是个老坑:
LRecyclerView内部会对传入的Adapter做一层包装(变成LRecyclerViewAdapter),并给包装后的“内部Adapter”注册一个mDataObserver,用来监听数据变化。但它的setAdapter()方法里,会先执行unregisterAdapterDataObserver(mDataObserver)——如果这是第一次设置Adapter,内部Adapter还没注册过这个Observer,就会抛出“未注册”的异常。
而同事的代码里,因为在多个生命周期方法里重复调用setAdapter(),相当于多次触发这个有问题的逻辑,直接把隐藏的bug给暴露了。
onResume()、onCreateView()还是Handler里,设置Adapter前必须用getAdapter() == null判断,避免重复调用setAdapter()。如果需要更新数据,直接调用adapter.notifyDataSetChanged(),而不是重新new Adapter。LRecyclerViewAdapter(它会包装你的原始Adapter),不能直接传普通的RecyclerView.Adapter,否则会报类型转换错误。正确写法是把原始Adapter传给LRecyclerViewAdapter的构造器:// 原始Adapter
MyOriginalAdapter originalAdapter = new MyOriginalAdapter(dataList);
// 用LRecyclerViewAdapter包装
LRecyclerViewAdapter wrapperAdapter = new LRecyclerViewAdapter(originalAdapter);
// 再设置给LRecyclerView
if (lrecyclerView.getAdapter() == null) {
lrecyclerView.setAdapter(wrapperAdapter);
}最后再吐槽一句:接手旧项目时,遇到崩溃别慌,先抓准日志里的关键错误(比如这次的“Observer未注册”),顺着错误栈找源码逻辑,很多时候问题都出在“重复调用”“未判断空值”这种小细节上。希望我的踩坑经历,能帮你少花点时间在填坑上~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。