坑
传入函数体为空的Lambda表达式导致LiveData抛出异常App崩溃的问题。
2022.11.2 更新:
了解到,旧Android Studio 编译项目时,会将无状态的Lambda表达式编译成一个单例类,目前在“Android Studio Dolphin | 2021.3.1”版本更新记录中,找到如下记录,最新Android Studio不再将无状态Lambda编译成单例类
现象
Kotlin写的页面Activity A(简称面A)中,用LiveData创建了一个观察者:
private fun onRouterOnlineChanged() {
mViewModel
.getRouterOnlineLiveData()
.observe(this, Observer<Boolean> {
})
}
这个观察者用到了Lambda且Lambda函数体为空。问题来了,测试频繁的打开页面A、关闭页面A、再打开页面A的操作中,App崩溃了,错误日志为:
Caused by: java.lang.IllegalArgumentException: Cannot add the same observer with different lifecycles
at androidx.lifecycle.LiveData.observe(LiveData.java:179)
at realtek.smart.fiberhome.com.device.ui.router.RouterHomeActivity.onRouterOnlineChanged(RouterHomeActivity.kt:252)
at realtek.smart.fiberhome.com.device.ui.router.RouterHomeActivity.initData(RouterHomeActivity.kt:77)
at realtek.smart.fiberhome.com.core.base.BaseActivity.onCreate(BaseActivity.java:42)
根据错误日志,发现是在LiveData源码中observe处抛出的,existing不为null且关联到LifecycleOwner就会抛出。
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
再往下看,看existing的获取mObservers.putIfAbsent(observer, wrapper),发现是通key从Map中获取的。正常情况下,这里应该返回null,否则observe(LiveData源码)中就会抛出异常从而导致崩溃。
/**
* If the specified key is not already associated
* with a value, associates it with the given value.
*
* @param key key with which the specified value is to be associated
* @param v value to be associated with the specified key
* @return the previous value associated with the specified key,
* or {@code null} if there was no mapping for the key
*/
public V putIfAbsent(@NonNull K key, @NonNull V v) {
Entry<K, V> entry = get(key);
if (entry != null) {
return entry.mValue;
}
put(key, v);
return null;
}
现在可以确定导致页面A崩溃的原因是putIfAbsent方法中,Entry<K, V> entry = get(key)的entry不为null,可是这里的key是一个lambda表达式呀,因为页面A中添加观察者是observe(this, lambda表达式),LiveData中mObservers.putIfAbsent(lambda表达式, wrapper)。
分析
为了方便分析和描述问题,这里的三个方法是从页面A中截取的,顺序一致。
private fun onWanInfoChanged() {
mViewModel
.getWanInfoLiveData()
.observe(this, Observer { wanInfo ->
mViewModel.getMainRouterInfo()?.let {
mAccessTypeView.text = DeviceHelper.getMainRouterAccessType(it.wanLinkMode, wanInfo.addressType)
}
})
}
private fun onRouterRestoreChanged() {
mViewModel
.getRouterRestoreLiveData()
.observe(this, object :Observer<Boolean>{
override fun onChanged(t: Boolean?) {
}
})
}
private fun onRouterOnlineChanged() {
mViewModel
.getRouterOnlineLiveData()
.observe(this, Observer<Boolean> {
})
}
开始分析,错误日志中显示是onRouterOnlineChanged抛出异常,而不是onWanInfoChanged和onRouterRestoreChanged抛出异常。为什么是onRouterOnlineChanged方法抛出异常,而不是和它类似的onRouterRestoreChanged方法抛出异常呢,它们区别是onRouterOnlineChanged中LiveData注册观察者时传入函数体空的Lambda,而onRouterRestoreChanged传的是空的匿名类。它们的执行体都是空的,到这里我们看不出什么原因。那看看生成的字节码是什么,看能不能找到原因,我们通过Android Studio自带的工具Show Kotlin Bytecode(在Tools->Kotlin->Show Kotlin Bytecode)查看这两个方法的字节码,
onWanInfoChanged的字节码反编译后:
private final void onWanInfoChanged() {
RouterHomeViewModel var10000 = this.mViewModel;
if (var10000 == null) {
Intrinsics.throwUninitializedPropertyAccessException("mViewModel");
}
var10000.getWanInfoLiveData().observe((LifecycleOwner)this, (Observer)(new Observer() {
// $FF: synthetic method
// $FF: bridge method
public void onChanged(Object var1) {
this.onChanged((WanInfo)var1);
}
public final void onChanged(WanInfo wanInfo) {
MainRouterInfoPo var10000 = RouterHomeActivity.access$getMViewModel$p(RouterHomeActivity.this).getMainRouterInfo();
if (var10000 != null) {
MainRouterInfoPo var2 = var10000;
boolean var3 = false;
boolean var4 = false;
int var6 = false;
RouterHomeActivity.access$getMAccessTypeView$p(RouterHomeActivity.this).setText((CharSequence)DeviceHelper.INSTANCE.getMainRouterAccessType(var2.getWanLinkMode(), wanInfo.getAddressType()));
}
}
}));
}
onRouterRestoreChanged的字节码反编译后:
private final void onRouterRestoreChanged() {
RouterHomeViewModel var10000 = this.mViewModel;
if (var10000 == null) {
Intrinsics.throwUninitializedPropertyAccessException("mViewModel");
}
var10000.getRouterRestoreLiveData().observe((LifecycleOwner)this, (Observer)(new Observer() {
public void onChanged(@Nullable Boolean t) {
}
// $FF: synthetic method
// $FF: bridge method
public void onChanged(Object var1) {
this.onChanged((Boolean)var1);
}
}));
}
onRouterOnlineChanged的字节码反编译后:
private final void onRouterOnlineChanged() {
RouterHomeViewModel var10000 = this.mViewModel;
if (var10000 == null) {
Intrinsics.throwUninitializedPropertyAccessException("mViewModel");
}
var10000.getRouterOnlineLiveData().observe((LifecycleOwner)this, (Observer)null.INSTANCE);
}
我们重点看onRouterRestoreChanged和onRouterOnlineChanged反编译后的代码,发现将匿名内部类和空函数体的Lambda传给LiveData后生成的的代码确实不一样。
结论
到最后本人能力有限,找不出最后的根本原因,只能找出表面原因,频繁做关闭页面A、打开页面A、再关闭页面A的操作会使App崩溃,确实是传入函数体为空的Lambda表达式导致LiveData抛出异常。
解决方法是不要传入空的Lambda表达式给LiveData,用匿名类的方式或者Lambda表达式里加个打印语句都行,最后问题解决了。
有哪位朋友看过这篇文章,知道真的原因,麻烦评论告知下,谢谢!!!
更新
2021年03月08日
我们仔细观察通过字节码反编译后的代码,(Observer)null.INSTANCE,如果你了解单例模式的话,写单例的时候我们习惯给单例命名为instance,如果单例是final修饰的话变量名就要全部大写INSTANCE。因此这里的(Observer)null.INSTANCE是一个静态的单例,再结合静态类的特性,该静态单例的生命周期长于Activity。就会发生Activity页面关闭再打开时,传给Observer的还是上次的(Observer)null.INSTANCE。