Activity生命周期方法中使用将Kotlin无状态Lambda在传给LiveData时的坑(Kotlin、LiveData与Lambda一起使用时)的坑

传入函数体为空的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。
在这里插入图片描述

饿汉单例模式如下

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值