感受LiveData 与 ViewModel结合之美
Posted 唯鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了感受LiveData 与 ViewModel结合之美相关的知识,希望对你有一定的参考价值。
LiveData与ViewModel都是android官方架构组件(Android Architecture Components)之一。
1.前言
虽说这篇是说LiveData
与ViewModel
,但是或多或少都有涉及另外一个组件:Lifecycles
。它们连同Room
都是在17年谷歌IO大会推出的,当时还是预览版,大致17年底时推出了正式版。到今年的IO大会过后,又增加了许多新成员。
可以看到27.0.0的v7库有依赖Lifecycles
。
当时Lifecycles
有集成进SupportActivity
。
其实一开始我没有太当回事。。。直到27.1.0以后:
好吧,今天的主角出现了,LiveData
与ViewModel
。看到这里我觉得是该了解一波了。
顺便看一下截止目前最新的v7:
发现好多常用的组件分离出了v4包,比如ViewPager
、SwipeRefreshLayout
,这里就不多说了。
2.优势
LiveData 是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。同时,LiveData 持有界面代码 Lifecycle 的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。
上面的描述介绍了LiveData
的优点:不用手动控制生命周期,不用担心内存泄露,数据变化时会收到通知。
ViewModel 将视图的数据和逻辑从具有生命周期特性的实体(如 Activity 和 Fragment)中剥离开来。直到关联的 Activity 或 Fragment 完全销毁时,ViewModel 才会随之消失,也就是说,即使在旋转屏幕导致 Fragment 被重新创建等事件中,视图数据依旧会被保留。ViewModels 不仅消除了常见的生命周期问题,而且可以帮助构建更为模块化、更方便测试的用户界面。
ViewModel
的优点也很明显,为Activity 、Fragment存储数据,直到完全销毁。尤其是屏幕旋转的场景,常用的方法都是通过onSaveInstanceState()
保存数据,再在onCreate()
中恢复,真的是很麻烦。
其次因为ViewModel
存储了数据,所以ViewModel
可以在当前Activity
的Fragment
中实现数据共享。
那么LiveData
与ViewModel
的组合使用可以说是双剑合璧,而Lifecycles
贯穿其中。
3.基本使用
这里我们按照官方Demo来简单说明,
1. 数据储存
/**
* A ViewModel used for the @link ChronoActivity3.
*/
public class LiveDataTimerViewModel extends ViewModel
private static final int ONE_SECOND = 1000;
private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();
private long mInitialTime;
public LiveDataTimerViewModel()
mInitialTime = SystemClock.elapsedRealtime();
Timer timer = new Timer();
// Update the elapsed time every second.
timer.scheduleAtFixedRate(new TimerTask()
@Override
public void run()
final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
// setValue() cannot be called from a background thread so post to main thread.
mElapsedTime.postValue(newValue);
, ONE_SECOND, ONE_SECOND);
public LiveData<Long> getElapsedTime()
return mElapsedTime;
LiveDataTimerViewModel
很简单,在初始化时启动一个定时任务,每隔一秒通过postValue
方法刷新一下数据。
public class ChronoActivity3 extends AppCompatActivity
private LiveDataTimerViewModel mLiveDataTimerViewModel;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.chrono_activity_3);
mLiveDataTimerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);
subscribe();
private void subscribe()
final Observer<Long> elapsedTimeObserver = new Observer<Long>()
@Override
public void onChanged(@Nullable final Long aLong)
String newText = ChronoActivity3.this.getResources().getString(
R.string.seconds, aLong);
((TextView) findViewById(R.id.timer_textview)).setText(newText);
Log.d("ChronoActivity3", "Updating timer");
;
//
mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
Activity也很简单,创建一个观察者elapsedTimeObserver
。当LiveDataTimerViewModel
中数据有变化时,它就会接收到最新的数据。当然你的页面要处于STARTED
或者 RESUMED
。除非你使用observeForever
来观察数据,有兴趣的可以去查看源码来了解实现原理。
mLiveDataTimerViewModel.getElapsedTime().observeForever(elapsedTimeObserver);
2. Fragmnet 之间数据共享
public class SeekBarViewModel extends ViewModel
public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
SeekBarViewModel
中存储一个Integer
类型的数据。
public class Fragment_step5 extends Fragment
private SeekBar mSeekBar;
private SeekBarViewModel mSeekBarViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_step5, container, false);
mSeekBar = root.findViewById(R.id.seekBar);
//注意这里是getActivity()
mSeekBarViewModel = ViewModelProviders.of(getActivity()).get(SeekBarViewModel.class);
subscribeSeekBar();
return root;
private void subscribeSeekBar()
// 当SeekBar变化时,更新ViewModel中的数据.
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
if (fromUser)
Log.d("Step5", "Progress changed!");
mSeekBarViewModel.seekbarValue.setValue(progress);
......
);
// 当ViewModel数据变化时,更新SeekBar。
mSeekBarViewModel.seekbarValue.observe(this, new Observer<Integer>()
@Override
public void onChanged(@Nullable Integer value)
if (value != null)
mSeekBar.setProgress(value);
);
实现效果:
这个页面是上下各有一个Fragment_step5
的Fragment,Fragment中各有一个SeekBar。效果是拖动其中的SeekBar,另一边的SeekBar也会随之一样变化。
4.简化使用
这里我写了一个小小的工具库Saber
来处理(好吧,猝不及防的广告。。。),使用注解处理器(Annotation Processor)将繁琐的代码自动生成。
首先创建一个类,使用@LiveData
注解标记你要保存的数据。注意这里的参数名称,下面会用到。
public class SeekBar
@LiveData
Integer value;
Build – > Make Project 生成代码如下:
public class SeekBarViewModel extends ViewModel
private MutableLiveData<Integer> mValue;
public MutableLiveData<Integer> getValue()
if (mValue == null)
mValue = new MutableLiveData<>();
return mValue;
public Integer getValueValue()
return getValue().getValue();
public void setValue(Integer mValue)
if (this.mValue == null)
return;
this.mValue.setValue(mValue);
public void postValue(Integer mValue)
if (this.mValue == null)
return;
this.mValue.postValue(mValue);
提供了ViewModel的常用操作。setXXX()
要在主线程中调用,而postXXX()
既可在主线程也可在子线程中调用。一般情况下可以直接使用。比如上面的Fragment例子。简化为:
public class TestFragment extends Fragment
private SeekBar mSeekBar;
@BindViewModel(isShare = true) //<--标记需要绑定的ViewModel
SeekBarViewModel mSeekBarViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_test, container, false);
mSeekBar = root.findViewById(R.id.seekBar);
Saber.bind(this); // <--这里绑定ViewModel
subscribeSeekBar();
return root;
private void subscribeSeekBar()
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
if (fromUser)
mSeekBarViewModel.setValue(progress);
......
);
@OnChange(model = "mSeekBarViewModel") //<--接收变化
void setData(Integer value) //注意这里使用 @LiveData 标记的参数名
if (value != null)
mSeekBar.setProgress(value);
默认使用@BindViewModel
用于数据储存,如果需要Fragment
之间数据共享,需要@BindViewModel(isShare = true)
,当然也要保证传入相同的key值。默认key值是类的规范名称,也就是包名加类名。
所以一旦需要互通的Fragment类名或包名不一致,就无法数据共享。这时可以指定key值:@BindViewModel(key = "value")
对于第一个例子我们可以这样使用:
public class LiveDataTimerViewModel extends TimerViewModel // <-- 继承生成的ViewModel
private static final int ONE_SECOND = 1000;
private long mInitialTime;
public LiveDataTimerViewModel()
mInitialTime = SystemClock.elapsedRealtime();
Timer timer = new Timer();
// Update the elapsed time every second.
timer.scheduleAtFixedRate(new TimerTask()
@Override
public void run()
final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
// setValue() cannot be called from a background thread so post to main thread.
postTime(newValue); //<--直接使用post方法。
, ONE_SECOND, ONE_SECOND);
Activity如下:
public class ChronoActivity3 extends AppCompatActivity
private TextView textView;
@BindViewModel
LiveDataTimerViewModel mTimerViewModel;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = this.findViewById(R.id.tv);
Saber.bind(this); // <-- 绑定
@OnChange(model = "mTimerViewModel")
void setData(Long time)
String newText = MainActivity.this.getResources().getString(R.string.seconds, time);
textView.setText(newText);
Log.d("ChronoActivity3 ", "Updating timer");
是不是使用起来更加的简洁了,如果一个页面有多个ViewModel
可能效果更加的明显。
5.原理
因为实现大量借鉴了butterknife
,所以使用方法与butterknife
几乎一模一样。是不是想起了 butterknife
的@BindView
与 @OnClick
。
其实原理也不复杂,就是生成一个类来帮我们来获取ViewModel
并实现数据的变化监听。如下:
public class MainActivity_Providers implements UnBinder
private MainActivity target;
@UiThread
public MainActivity_Providers(MainActivity target)
this.target = target;
init();
private void init()
target.mTimerViewModel = ViewModelProviders.of(target).get(LiveDataTimerViewModel.class);
target.mTimerViewModel.getTime().observe(target, new Observer<Long>()
@Override
public void onChanged(Long value)
target.setData(value);
);
@CallSuper
@UiThread
public void unbind()
MainActivity target = this.target;
if (target == null)
throw new IllegalStateException("Bindings already cleared.");
this.target = null;
所有代码已上传至Github。希望大家多多点赞支持!有什么问题及建议也可以提Issues,让我们将他慢慢的完善起来。
6.参考
以上是关于感受LiveData 与 ViewModel结合之美的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio 之 ROM, LiveData+ViewModel+AsyncTask+Repository
Android ViewModel与LiveData组件组合使用详解