ViewLifeCycleOwner 在从 Main 转到后台时返回 null,再次转到 MainThread

问题描述

我需要在适配器解析视图后几毫秒开始观察,以便它们正确接收信号。

这个方法是在 onViewCreated() 之后执行的;

                pageControllerAdapter.submitList(stringVisitors,() -> {
                    if (!currentPage.hasActiveObservers()) {
                        Post.delayed(
                                () -> {
                                    Post.onMain(
                                            () -> {
                                                currentPage.observe(getViewLifecycleOwner(),page -> {
                                                            selector.setSelected(page);
                                                            binding.searchPages.setCurrentItem(page,true);
                                                        }
                                                );
                                            }
                                    );

                                },150L,TimeUnit.MILLISECONDS
                        );
                    }
                });

其中 Post.delayed()Executors.newSingleThreadScheduledExecutor().schedule() 方法,而 Post.onMain() 是用于 Runnable 发布的 new Handler(Looper.getMainLooper());

奇怪的事情正在发生……:

第一次执行代码时一切正常,但在配置更改后出现IllegalStateException

@MainThread
    @NonNull
    public LifecycleOwner getViewLifecycleOwner() {
        if (mViewLifecycleOwner == null) {
            throw new IllegalStateException("Can't access the Fragment View's LifecycleOwner when "
                    + "getView() is null i.e.,before onCreateView() or after onDestroyView()");
        }
        return mViewLifecycleOwner;
    }

但是...如果我调用 getViewLifeCycleOwner();在进入 .getMainLooper() 线程之前,在 Post.delayed()Post.onMain() 之间像这样:

                        Post.delayed(
                                () -> {
                                    getViewLifecycleOwner();
                                    Post.onMain(
                                            () -> {
                                                currentPage.observe(getViewLifecycleOwner(),TimeUnit.MILLISECONDS
                        );

代码有效....

我怀疑正在发生的事情是……:

/**
     * The provider that owns this Lifecycle.
     * Only WeakReference on LifecycleOwner is kept,so if somebody leaks Lifecycle,they won't leak
     * the whole Fragment / Activity. However,to leak Lifecycle object isn't great idea neither,* because it keeps strong references on all other listeners,so you'll leak all of them as
     * well.
     */
    private final WeakReference<LifecycleOwner> mLifecycleOwner;

WeakReference 正在 Post.delayed()(预定的执行器)中被 GC,所以当需要再次在 mainThread 中执行该方法时,引用已经被收集,但是当 a该方法的第二次执行是在后台调度的执行器中执行的,即使根本没有使用该引用,该方法getViewLifeCycleOwner()中涉及的所有引用的副本也会被线程保留并在返回时逃脱垃圾收集到mainThread

我的预感正确吗?

解决方法

所以这不是正在发生的事情......,是的,但部分原因。 新线程并没有“收集” WeakReference,而是“延长”了它的生命周期。 如何?在延迟方法规定的 150 毫秒之前执行 getViewLifeCycle() 方法时,它帮助绕过了 IllegalStateException() 并带来了 LifecycleRegistry 的副本,该副本在该方法执行时已被销毁......谢天谢地,LiveData 组件是当生命周期已经被销毁并且一切都结束时执行 return;

真正的问题与空状态、初始状态和之前保存的状态有关...

在配置更改发生之前已经存在于堆栈中的 Fragment 再次被相同的 Fragment 替换,这一切都是因为上层组件忽略了相等性,并且这种销毁速度比规定的 150 毫秒更快延迟方法。

这就是在 Executors.newSingleThreadScheduledExecutor().schedule() 可以访问视图之前破坏视图的原因。

起初我认为问题在于对通用枚举的相等性检查(因为它忽略了新对象,但这是相反的问题,所以..),但是允许重复操作通过的事情......实际上,所述组件上的“启动状态”在配置更改时触发(更准确地说:onViewCreated),因此相等性检查无法推断出真正的最后一个动作/片段已经存在于堆栈中......

解决方案是检查新旧 Fragment class() 是否相等,以防止重复事务。

另一种选择是为配置更改设置特殊情况,并使组件了解此状态,但通常重复的事务/操作在任何代码 IMO 中都是一个不好的迹象,因此最好的做法是完全防止重复操作。