BUG:自定义 View 中实现了 LifecycleObserver 接口,编写了一个 onResume(…) 函数,并给它加上了注解“@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)”,但是所在的 Fragment 还没有处于 onResume() 状态,View 的 onResume(…) 方法就触发了
产生的背景:
- 实现了 LifecycleObserver 接口的自定义 View
- 自定义 View 中有一个加了 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 注解的方法 onResume(…)
- 在 MainActivity 中有一个 ViewPager,里面承载了三个 Fragment
- 在第三个 Fragment 的布局中使用该自定义 View
- 启动应用后发生崩溃,查看错误日志,说 onResume(…) 中使用了未初始化的 ImageView
public class UserHeader extends ConstraintLayout implements LifecycleObserver {
// ...
private RoundedImageView roundedImageView;
// ...
public UserHeader(Context context) {
super(context);
init(context, null);
}
// ...
private void init(Context context, AttributeSet attrs) {
// ...
// 加载自定义布局
LayoutInflater.from(context).inflate(R.layout.layout_xxx, this);
// ...
if (getContext() instanceof XXXActivity) {
((XXXActivity) getContext()).getLifecycle().addObserver(this);
}
//实例化组件
roundedImageView = (RoundedImageView) findViewById(R.id.xxx);
// ...
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
// ...
if (!TextUtils.isEmpty(userString)) {
// ...
} else {
// 发生崩溃的位置
roundedImageView.setImageResource(mNotLoggedInHeader);
// ...
}
}
}
排查过程:
- LifeCycle 的 ON_RESUME 触发了,说明其观察的 Context 的 onResume 触发了,控件是写在 Fragment 中,在没有仔细阅读自定义 View 的前提下,天真的认为是因为 Fragment 走了 onResume,导致触发,所以想起 Fragment 懒加载问题,打算去排查(其实 Fragment 要是真的触发了 onResume,那么 roundedImageView 也不会没有初始化了)
- 检查 Fragment 的生命周期打印,发现并没有执行到 onResume
- 检查 VP 的 adapter,确实使用了 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 那么说明只有当 VP 切换到第三个 Fragment 时,对应的它的 onResume 才会触发
- 既然不是 Fragment 的生命周期导致的 LifeCycle 的 ON_RESUME 触发,那说明调用 addObserver(…) 的对象另有其人,经过查看代码与打印日志,发现是承载 Fragments 的活动 MainActivity,这也不难理解崩溃造成的原因了:
- 一启动应用,进入 MainActivity 后,它的 onResume 函数就触发了,进而出发了 View 的 onResume(…) 方法。
- 那为什么在执行 View 的 onResume(…) 方法时 roundedImageView 没有初始化呢,原因在于 addObserver(this) 操作早于 findViewById(…) ,一旦 view 被 addObserver(…) 后,之前产生的事件都会一一触发,直接走了 View 的 onResume(…) 方法,而此时 roundedImageView 的初始化操作还在后面没有执行,先调用了 setImageResource(…) 方法,产生崩溃。
解决方法:
偷了个懒,将 addObserver(…) 操作放在了 findViewById(…) 之后,虽然暂时解决了,但还是存在隐患,万一哪天另外一个同事修改代码的时候,在后面写了初始化方法呢!但是由于是基础库改动需要十分小心而且没有太多时间深入研究,只好写上注释,⚠️警告后人!!日后有时间,要好好考虑一下这个部分的实现了。