ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

【Android】LiveData

【Android】LiveData 一、为什么需要LiveData只用ViewModel的局限在上一篇的计数器写法中数据保存在了ViewModel里但更新UI仍然是手动调用setTextbtnAdd.setOnClickListener(v - { viewModel.add(); // 手动刷新UI tvCount.setText(String.valueOf(viewModel.count)); });这种写法存在很多潜在的问题无法自动感知页面生命周期。如果网络请求/异步任务在ViewModel里执行回调时Activity已经退到后台或已经销毁此时强行更新UI会导致空指针 、内存泄漏。数据驱动能力弱。数据和UI强耦合不符合MVVM的“数据驱动视图”思想数据变化无法自动分发到多个观察者。LiveData的核心定位它是一个可观察的数据持有类并且具备生命周期感知能力。数据变化时自动通知处于活跃状态的页面更新UI页面销毁时自动解绑彻底解决上述问题。二、LiveData核心特性可观察数据发生变化时自动通知所有观察者Activity/Fragment生命周期感知只给处于活跃状态STARTED/RESUMED的页面分发数据页面销毁自动解除订阅不会内存泄漏数据自动恢复配置变更旋转屏幕导致页面重建会自动收到最新的一次数据不用手动保存恢复UI与数据解耦ViewModel只负责生产数据Activity只负责观察并渲染互不持有线程安全友好区分主线程/子线程更新数据的API三、基础使用改造计数器案例我们把上一篇文章的计数器改成ViewModel LiveData的标准写法。1. 在ViewModel中定义LiveData核心规则对外暴露只读的LiveData防止外部随意修改数据内部使用可写的MutableLiveData只允许ViewModel内部修改数据数据的所有变更逻辑都封装在ViewModel内部public class CounterViewModel extends ViewModel { //内部可写的MutableLiveDataprivate修饰只在ViewModel内部修改 private final MutableLiveDataInteger _count new MutableLiveData(0); //对外暴露只读的LiveData供Activity观察 public LiveDataInteger getCount() { return _count; } //加法业务方法 public void add() { //获取当前值 Integer current _count.getValue(); if(current null) { current 0; } _count.setValue(current 1); } //重置方法 public void reset() { _count.setValue(0); } }2. 在Activity中观察LiveData使用observe()方法订阅数据变化传入LifecycleOwner(也就是Activity自身)这是LiveData能感知生命周期的关键。public class MainActivity extends AppCompatActivity { private TextView tvCount; private CounterViewModel viewModel; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) - { Insets systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); tvCount findViewById(R.id.tv_count); Button btnAdd findViewById(R.id.btn_add); Button btnReset findViewById(R.id.btn_reset); //获取ViewModel实例 //通过ViewModelProvider获取实例绑定当前Activity viewModel new ViewModelProvider(this).get(CounterViewModel.class); //观察count数据的变化 viewModel.getCount().observe(this, new ObserverInteger() { Override public void onChanged(Integer integer) { //数据变化时自动回调这里直接更新UI tvCount.setText(String.valueOf(integer)); } }); //点击事件只触发业务逻辑不手动刷新UI btnAdd.setOnClickListener(v - viewModel.add()); btnReset.setOnClickListener(v - viewModel.reset()); } }3、使用效果点击按钮→ViewModel里的_count数据变化→LiveData自动触发onChanged→UI自动更新旋转屏幕→Activity重建→重新observe→立刻收到最新的count值→UI自动恢复无需手动保存按Home键回到后台→LiveData停止分发数据避免后台更新UI导致异常退出界面→LiveData自动解绑不会内存泄漏四、核心API详解1. LiveData与MutableLiveData的区别类可读性使用场景LiveDataT只读只能观察不能外部修改对外暴露供View层订阅保证数据流向唯一MutableLiveDataT可读可写继承自LiveDataViewModel内部使用负责生产和修改数据最佳实践ViewModel永远对外暴露LiveData内部用MutableLiveData遵循数据单向流动原则View→ViewModel触发事件→ViewModel修改LiveData→LiveData通知View更新。2. setValue()与postValue()的区别setValue(T value)必须在主线程调用立刻更新值立刻通知活跃的观察者多次调用每次都会触发回调postValue(T value)可以在子线程调用内部会自动切到主线程不是立即执行而是在post到主线程消息队列短时间内多次调用只有最后一次的值会被分发中间值会被丢弃使用规则主线程修改数据→用setValue子线程网络请求回调、IO线程修改数据→用postValue高频更新场景慎用postValue可能丢中间值3. observe()与observeForever()observe(LifecycleOwner owner, Observer observer)绑定生命周期页面活跃才回调销毁自动解绑observeForever(Observer observer)不绑定生命周期只要数据变就一直回调必须手动调用removeObserver()解绑否则会内存泄漏非特殊场景严禁使用五、生命周期感知的底层原理LiveData通过Lifecycle框架监听Activity/Fragment的生命周期状态只在活跃态分发数据。销毁态自动移除观察者。简单流程调用observe(this, observer)时LiveData会把观察者和LifecycleOwner绑定当页面生命周期变化onStart、onResume、onPause、onDestroyLiveData都能收到通知状态变为START或RESUMED活跃态视为可接收数据有新数据就回调状态变为DESTROYED页面销毁自动移除观察者不会泄露不会空指针页面从后台回到前台如果期间有数据自动更新回到前台时会自动收到最新值六、粘性事件数据倒灌与解决方案1. 什么是粘性事件LiveData有一个默认特性新观察者订阅时会立刻收到LiveData里当前存储的最后一次数据。优点旋转屏幕重建页面能立刻恢复最新数据缺点对于“一次性事件”比如Toast、弹窗、页面跳转时会出现“倒灌”问题举一个聊天室的简单例子聊天页ViewModel里有一个toastMsg的LiveData用来作为消息是否发送成功的标志位不成功则弹提示发送消息失败setValue(发送失败)页面谈了一次Toast旋转屏幕页面重建重新observe又收到“发送失败”再弹一次Toast甚至切后台回来也可能重复弹这就是粘性事件/数据倒灌是LiveData最著名的坑。2. 解决方案日常开发最常用的方法是Event包装类 单次消费标记public class EventT { private final T content; private boolean hasBeenHandled false; public Event(T content) { this.content content; } //获取内容如果未被消费则返回否则返回null public T getContentIfNotHandled() { if(hasBeenHandled) { return null; } hasBeenHandled true; return content; } //强制获取当前内容无论是否消费过 public T peekCotent() { return content; } }ViewModel中使用private final MutableLiveDataEventString _toast new MutableLiveData(); public LiveDataEventString getToast() { return _toast; } public void sendMessage() { // 发送失败触发一次性提示 _toast.setValue(new Event(发送失败)); }Activity中观察viewModel.getToast().observe(this, event - { String msg event.getContentIfNotHandled(); if (msg ! null) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } });这样同一条消息只会被消费一次旋转屏幕也不会重复弹出。七、数据转换Transformations当需要对LiveData的数据做转换后再交给UI可以用Transformations.map和Transformations.switchMap无需自己写中间层。1. map一对一数据转换比如计数器数字要转成“当前计数X”的字符串再显示public class CounterViewModel extends ViewModel { private final MutableLiveDataInteger _count new MutableLiveData(0); // 将Int类型的count转换为String类型的展示文本 public final LiveDataString countText Transformations.map(_count, count - { return 当前计数 count; }); public void add() { Integer current _count.getValue(); if (current null) current 0; _count.setValue(current 1); } }UI层直接观察countText即可不用自己做字符串拼接。2. switchMap触发式切换数据源常用于“根据一个参数切换不同的数据源”比如根据用户ID查询用户信息private final MutableLiveDataString userId new MutableLiveData(); // userId变化时自动切换到对应数据源 public LiveDataUser user Transformations.switchMap(userId, id - { return repository.getUserById(id); // 返回一个LiveData }); public void setUserId(String id) { userId.setValue(id); }他跟map的区别在哪呢map“加工”。把A变成B比如把Int变成String。同步的瞬间完成。switchMap“换源”。因为A变了所以把整个数据源换成另一个LiveData。异步的要去拿新数据。
返回列表