当前位置: 首页 > article >正文

Android LiveData 处理数据倒灌的几种措施

LiveData

  • MutableLiveData
  • SingleLiveEvent
  • UnFlowLiveData
  • UnPeekLiveData
  • 扩展
      • 1. 为什么Fragment中要使用`viewLifecycleOwner`代替`this`
      • 2. 如果使用了`Fragment`的`this`,有可能产生的问题
  • 参考地址

MutableLiveData

  1. 粘性特性

    • 定义
      • MutableLiveData的粘性特性是指当一个观察者开始观察MutableLiveData时,如果数据已经有了一个值,那么这个观察者会立即收到这个已有值的通知。例如,假设MutableLiveData存储了一个用户的偏好设置(如主题颜色),在观察者(如一个Activity用于显示界面)开始观察之前,这个主题颜色的值可能已经被设置好了。当Activity开始观察该MutableLiveData时,它会立刻获取到这个已有的主题颜色值,就好像数据“粘”在了观察者上。
    • 实现原理
      • MutableLiveDataobserve()方法被调用时,它会检查当前是否已经有了一个非空的值。如果有,它会立即将这个值传递给新注册的观察者。在内部机制上,LiveDataMutableLiveData的父类)有一个版本号机制。每次数据更新时,版本号会增加。当观察者注册时,会比较观察者的初始版本号和LiveData的当前版本号,如果LiveData的版本号大于观察者的初始版本号,并且有数据值,就会将数据发送给观察者。
    • 应用场景
      • 这种特性在很多场景下都很有用。比如在应用启动时加载配置数据。如果MutableLiveData存储了应用的语言配置,当一个新的Activity启动并开始观察这个语言配置数据时,它可以立即获取到已有的语言配置,从而正确地设置界面语言,无需额外的操作来获取初始配置。
  2. 数据倒灌

    • 定义
      • 数据倒灌是指在某些情况下,当配置发生变化(如屏幕旋转)导致Activity或Fragment重建时,观察者可能会收到旧的数据。例如,一个Activity中有一个MutableLiveData存储用户输入的表单数据。当屏幕旋转时,Activity会被重建,新的观察者(重建后的Activity)可能会收到之前旧的观察者已经接收过的数据,就好像数据“倒灌”回来了。
    • 产生原因
      • 这主要是因为MutableLiveData的粘性特性和Android系统的组件重建机制共同作用的结果。在组件重建时,新的观察者会重新注册到MutableLiveData上,由于粘性特性,它会检查是否有已有的数据并可能接收这些数据,而这些数据可能是之前旧的观察者已经处理过的数据。
    • 解决方法
      • 为了避免数据倒灌,可以采用一些策略。一种常见的方法是使用SingleLiveEvent(它是对MutableLiveData的一种特殊应用),SingleLiveEvent在发送一次事件后会自动重置状态,这样可以避免在组件重建时旧事件被重新发送。另一种方法是在观察者中记录数据是否已经被处理过,通过比较数据的版本号或者其他唯一标识来判断是否应该接收数据,避免重复处理之前已经处理过的数据。

SingleLiveEvent

解决了数据倒灌的问题
是对 Event 事件包装器 一致性问题的改进,但未解决多观察者消费的问题;

而且额外引入了消息未能从内存中释放的问题。

public class SingleLiveEvent<T> extends MutableLiveData<T> {

    private static final String TAG = "SingleLiveEvent";

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @MainThread
    public void observe(LifecycleOwner owner, final Observer<T> observer) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
        }

        // Observe the internal MutableLiveData
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        mPending.set(true);
        super.setValue(t);
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    public void call() {
        setValue(null);
    }
}

关于LiveData粘性事件所带来问题的解决方案:https://www.jianshu.com/p/d0244c4c7cc9
简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379

UnFlowLiveData

解决了数据倒灌,并且支持多个观察者

方案思路:

  • 在observe/observeForever时创建新的LiveData,并且根据observer保存该LiveData到mObserverMap中,而且该LiveData订阅相关的observer;

  • 当postValue/setValue时,遍历mObserverMap的所有LiveData,并把值设置给LiveData;

public class UnFlowLiveData<T> {
    private final Handler mMainHandler;
    private T mValue;
    private final ConcurrentHashMap<Observer<? super T>, MutableLiveData<T>> mObserverMap;

    public UnFlowLiveData() {
        mMainHandler = new Handler(Looper.getMainLooper());
        mObserverMap = new ConcurrentHashMap<>();
    }

    @MainThread
    public void observeForever(@NonNull Observer<? super T> observer) {
        checkMainThread("observeForever");
        MutableLiveData<T> liveData = new MutableLiveData<>();
        // 该LiveData也observeForever该observer,这样setValue时,能把value回调到onChanged中
        liveData.observeForever(observer);
        mObserverMap.put(observer, liveData);
    }

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        checkMainThread("observe");
        Lifecycle lifecycle = owner.getLifecycle();
        if (lifecycle.getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        lifecycle.addObserver(new LifecycleObserver() {

            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            public void onDestroy() {
                mObserverMap.remove(observer);
                lifecycle.removeObserver(this);
            }
        });
        MutableLiveData<T> liveData = new MutableLiveData<>();
        // 该LiveData也observe该observer,这样setValue时,能把value回调到onChanged中
        liveData.observe(owner, observer);
        mObserverMap.put(observer, liveData);
    }

    @MainThread
    public void removeObserver(@NonNull final Observer<? super T> observer) {
        checkMainThread("removeObserver");
        mObserverMap.remove(observer);
    }

    public T getValue() {
        return mValue;
    }

    public void clearValue() {
        mValue = null;
    }

    @MainThread
    public void setValue(T value) {
        checkMainThread("setValue");
        mValue = value;
        // 遍历所有LiveData,并把value设置给LiveData
        for (MutableLiveData<T> liveData : mObserverMap.values()) {
            liveData.setValue(value);
        }
    }

    public void postValue(T value) {
        mMainHandler.post(() -> setValue(value));
    }

    private void checkMainThread(String methodName) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new IllegalStateException("UnFlowLiveData, Cannot invoke " + methodName
                    + " on a background thread");
        }
    }
}

简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379

UnPeekLiveData

public class ProtectedUnPeekLiveData<T> extends LiveData<T> {

    protected boolean isAllowNullValue;

    private final HashMap<Integer, Boolean> observers = new HashMap<>();

    public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer<? super T> observer) {
        LifecycleOwner owner = activity;
        Integer storeId = System.identityHashCode(observer);//源码这里是activity.getViewModelStore(),是为了保证同一个ViewModel环境下"唯一可信源"
        observe(storeId, owner, observer);
    }

    private void observe(@NonNull Integer storeId,
                         @NonNull LifecycleOwner owner,
                         @NonNull Observer<? super T> observer) {

        if (observers.get(storeId) == null) {
            observers.put(storeId, true);
        }

        super.observe(owner, t -> {
            if (!observers.get(storeId)) {
                observers.put(storeId, true);
                if (t != null || isAllowNullValue) {
                    observer.onChanged(t);
                }
            }
        });
    }
    
    @Override
    protected void setValue(T value) {
        if (value != null || isAllowNullValue) {
            for (Map.Entry<Integer, Boolean> entry : observers.entrySet()) {
                entry.setValue(false);
            }
            super.setValue(value);
        }
    }

    protected void clear() {
        super.setValue(null);
    }
}

其思路也很清晰,为每个传入的observer对象携带一个布尔类型的值,作为其是否能进入observe方法的开关。每当有一个新的observer存进来的时候,开关默认关闭。

每次setValue后,打开所有Observer的开关,允许所有observe执行。

同时方法进去后,关闭当前执行的observer开关,即不能对其第二次执行了,除非你重新setValue。

作者:慕尼黑凌晨四点
链接:https://www.jianshu.com/p/d0244c4c7cc9

扩展

以下是为你格式化后并且添加代码标注说明的内容:

1. 为什么Fragment中要使用viewLifecycleOwner代替this

在Android开发中,FragmentFragment中的View的生命周期并不一致。我们在使用一些可观察的数据(比如LiveData)时,需要让观察者(observer)准确感知Fragment中的View的生命周期,而不是Fragment本身的生命周期。基于这样的需求,Android专门构造了与Fragment中的View相对应的LifecycleOwner,也就是viewLifecycleOwner。以下是相关代码示例说明其重要性:

// 假设这是一个Fragment类
public class MyFragment extends Fragment {
    private MutableLiveData<String> liveData = new MutableLiveData<>();

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        // 错误示范:使用this(也就是Fragment自身)来作为LifecycleOwner观察LiveData
        liveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                // 处理数据变化逻辑
            }
        });
        
        // 正确示范:使用viewLifecycleOwner来作为LifecycleOwner观察LiveData
        liveData.observe(viewLifecycleOwner, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                // 处理数据变化逻辑,这样能确保和View的生命周期更好地绑定
            }
        });
    }
}

来源信息

  • 作者:caz
  • 链接:https://juejin.cn/post/6915222252506054663
  • 来源:稀土掘金
  • 著作权说明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2. 如果使用了Fragmentthis,有可能产生的问题

当在Fragment中使用“this”(指代Fragment自身)来处理相关逻辑时,存在一定风险。在Fragment未被复用的情况下,可能不会出现明显问题。但是一旦Fragment被复用,LiveData内的数据就会交由多个页面共同处理,这极有可能对其他页面的内部逻辑产生不良影响。以下是一个简单示例来体现这种情况:

// 假设有两个不同的页面(这里简化为两个Fragment)复用了同一个Fragment类
public class ReusedFragment extends Fragment {
    private MutableLiveData<Integer> sharedLiveData = new MutableLiveData<>();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 模拟设置LiveData的初始值
        sharedLiveData.setValue(10);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        // 错误示范:使用this观察LiveData,当Fragment被复用就可能出问题
        sharedLiveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                // 这里不同页面都会执行这个逻辑,可能互相干扰
                Log.d("ReusedFragment", "Received data: " + integer);
            }
        });
    }
}

// 第一个复用ReusedFragment的页面(Fragment)
public class FirstPageFragment extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ReusedFragment reusedFragment = new ReusedFragment();
        getChildFragmentManager().beginTransaction().add(R.id.container, reusedFragment).commit();
    }
}

// 第二个复用ReusedFragment的页面(Fragment)
public class SecondPageFragment extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ReusedFragment reusedFragment = new ReusedFragment();
        getChildFragmentManager().beginTransaction().add(R.id.container, reusedFragment).commit();
    }
}

在上述代码中,如果ReusedFragment中的LiveData使用this来添加观察者,那么当它在FirstPageFragmentSecondPageFragment中被复用时,LiveData数据变化的通知会同时传递给两个页面中的观察者,导致它们的内部逻辑可能因为共享的数据处理而出现混乱,互相影响。

参考地址

关于LiveData粘性事件所带来问题的解决方案:https://www.jianshu.com/p/d0244c4c7cc9
简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379
豆包AI


http://www.kler.cn/a/397580.html

相关文章:

  • SAFETY LAYERS IN ALIGNED LARGE LANGUAGEMODELS: THE KEY TO LLM SECURITY
  • C++创建型设计模式体现出的面向对象设计原则
  • AI大模型(一):Prompt AI编程
  • 安装paddle
  • 【从零开始的LeetCode-算法】3270. 求出数字答案
  • MongoDB分布式集群搭建----副本集----PSS/PSA
  • 计算机视觉 ---图像读取与显示(OpenCV与Matplotlib)
  • ‌EAC(Estimate at Completion)和ETC(Estimate to Complete)
  • c# Encoding.GetEncoding
  • 后端返回大数问题
  • rk3399开发环境使用Android 10初体验蓝牙功能
  • 计算光纤色散带来的相位移动 matlab
  • vue.js设计与实现(霍春阳著) 章节总结
  • golang对日期格式化
  • Tailwind CSS 和 UnoCSS简单比较
  • 数据库管理-第262期 崖山:知其不可而为之(20241116)
  • 【笔记】Vue3回忆录
  • 【C语言指南】C语言内存管理 深度解析
  • aitrader双界面引擎(dash和streamlit),引入zvt作为数据获取及存储支持
  • 以太坊基础知识结构详解
  • 将大型语言模型(如GPT-4)微调用于文本续写任务
  • STM32设计井下瓦斯检测联网WIFI加Zigbee多路节点协调器传输
  • 【jvm】如何破坏双亲委派机制
  • LeetCode - #134 加油站
  • vocode Vue3项目 红色波浪线解决方案集锦
  • 丹摩征文活动|丹摩智算平台使用指南