一、概述
在学习完Lifecycle
之后,我们如何通过Lifecycle
让除了Activity/Fragment
之外的其它对象都可以很方便地收到当前页面生命周期的回调。
今天我们来学习ViewModel+LiveData
,它们共同的特点就是绑定到了UI
组件(Activity/Fragment
)上,可以收到生命周期的回调,简单地来说,这两个组件的作用分别为:
-
LiveData
:在Lifecycle
范围内 监听数据 的变化。 -
ViewModel
:在Lifecycle
范围内 存储和共享数据。
今天,我们就一起来学习一下LiveData
的使用场景。
二、LiveData+ViewModel
这个Demo
的功能很简单,在界面上有一个Btn
,点击Btn
后异步加载数据,数据回来以后通知UI
来更新。
2.1 创建 ViewModel 和 LiveData
/**
* 继承于 ViewModel。
*
* @author lizejun
*/
public class DataViewModel extends ViewModel {
private static final String TAG = DataViewModel.class.getSimpleName();
//这里创建一个 MutableLiveData,> 为要提供的数据类型,这里我们声明为 List。
private MutableLiveData> mWatcher;
private Handler mWorkHandler;
/**
* 加载数据,在实际当中,加载数据的操作要放在 Repository 中进行,而不要放在 Model 中,
* 它只是负责数据和 UI 的交互过程。
*
*/
public void load() {
if (mWorkHandler == null) {
HandlerThread thread = new HandlerThread("DataViewModel");
thread.start();
mWorkHandler = new Handler(thread.getLooper());
}
mWorkHandler.post(new Runnable() {
@Override
public void run() {
//模拟加载数据的过程。
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
List result = makeResult();
setResults(result);
}
});
}
/**
* 获取数据的监控者。
*
* @return 监控者。
*/
public MutableLiveData> getWatcher() {
Log.d(TAG, "Call getWatcher");
if (mWatcher == null) {
mWatcher = new MutableLiveData<>();
}
return mWatcher;
}
/**
* 设置数据。
*
* @param results 设置数据。
*/
private void setResults(List results) {
Log.d(TAG, "Call setResults");
//当数据加载完以后,调用 setValue/postValue 方法设置数据。
if (Looper.getMainLooper() == Looper.myLooper()) {
getWatcher().setValue(results);
} else {
getWatcher().postValue(results);
}
}
private List makeResult() {
List result = new ArrayList<>();
result.add("苹果 - 1");
result.add("苹果 - 2");
result.add("苹果 - 3");
result.add("苹果 - 4");
result.add("苹果 - 5");
result.add("苹果 - 6");
return result;
}
}
我们创建一个继承 于ViewModel
的DataViewModel
,前面我们也提到过,ViewModel
作用是存储和共享数据,这里的作用就是 存储。
我们所需要的真实数据则是被保存在MutableLiveData>
当中,?
就是真实数据的类型,而MutableLiveData
就是我们所说的LiveData
,它除了负责保存数据外,还负责在数据变化的时候,通知它的观察者。
往LiveData
中插入数据的方式有两种:
-
setValue
:只允许在主线程中调用。 -
postValue
:主线程/子线程中均可调用。
在上面的Demo
中,我们在load()
中模拟了异步获取数据的操作的逻辑,并最终向LiveData
中插入数据。
2.2 使用方式
下面,让我们来看一下完整的使用过程,以及一些特殊的场景。
/**
* LiveData 学习 Demo。
*/
public class LiveDataActivity extends AppCompatActivity {
private Button mBtnRefresh;
private TextView mTvResult;
private DataViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_data);
mTvResult = findViewById(R.id.tv_result);
//1.创建 ViewModel。
mViewModel = ViewModelProviders.of(this).get(DataViewModel.class);
//2.添加观察者。
mViewModel.getWatcher().observe(this, new Observer>() {
@Override
public void onChanged(@Nullable List strings) {
Log.d("DataViewModel", "onChanged");
String tvDisplay = "";
for (String result : strings) {
tvDisplay += (result + "\n");
}
//4.数据发生了改变后会回调到这里。
mTvResult.setText(tvDisplay);
}
});
mBtnRefresh = findViewById(R.id.btn_refresh);
mBtnRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//3.触发加载。
Log.d("DataViewModel", "mViewModel.load()");
mViewModel.load();
}
});
}
@Override
protected void onResume() {
super.onResume();
Log.d("DataViewModel", "onResume()");
}
@Override
protected void onPause() {
super.onPause();
Log.d("DataViewModel", "onPause()");
}
}
首先,我们通过ViewModelProviders.of(this).get(DataViewModel.class)
创建了DataViewModel
的实例,而不是通过直接new
的方式,这么做的原因有两个:
- 通过
of
传入的Activity
,其在内部实现了与Activity
的绑定。 - 为了数据的共享,这点我们暂时还没有体会到,后面会讲。
接着,获取到DataViewModel
当中的MutableLiveData
对象,调用其observe
方法,该方法接受两个参数,分别对应于其要绑定到的组件以及一个监听者,当我们改变了MutableLiveData
内部持有的数据后,将会回调该监听者,我们就可以在里面做更新数据的操作。
当我们点击 加载 按钮后,log
输出如下所示:
现在,让我们模拟一种特殊的场景,在点击加载按钮后,迅速按下HOME
键让页面处于inactive
的状态,此时的输出为:
可以发现, 没有回调 onChanged() 方法,之后重新进入界面:
在重新进入界面之后,才回调了
onChanged()
方法,也就是说,在界面
inactive
的状态下发生了数据的改变,不会立即通知观察者,而是要等到界面重新
active
之后,才会调用
observer
的
onChanged()
方法。
除了observe
方法外,还有一个observeForever
,它只接受一个观察者参数,也就是说,它并不关注当前界面是否active
,都会回调数据。
//不与组件的生命周期绑定。
mViewModel.getWatcher().observeForever(new Observer>() {
@Override
public void onChanged(@Nullable List strings) {
Log.d("DataViewModel", "onChanged");
String tvDisplay = "";
for (String result : strings) {
tvDisplay += (result + "\n");
}
//4.数据发生了改变后会回调到这里。
mTvResult.setText(tvDisplay);
}
});
用相同的操作验证的结果如下,即使界面处于inactive
状态,也会回调onChanged()
方法:
因此,为了避免内存泄漏,我们应当在界面销毁的时候,调用
MutableLiveData
的
removeObserver
方法。
2.3 小结
整个数据的流向如下所示:
通过
LiveData
作为中介者,实现了
UI
组件和数据组件的隔离,对于
UI
组件来说,它只负责
发起请求操作 和
在数据变化的时候更新界面,而对于数据组件来说,它负责
接受请求和
改变LiveData
,而通知
UI
组件的操作则是由
LiveData
来完成的,因此它可以根据当前
UI
组件的状态
active/inative
进行控制。
三、继承 LiveData
继承LiveData
的时候,我们一般会重写它的下面两个方法,它们的含义为:
-
onActive()
:当LiveData
有一个活跃的观察者时调用。 -
onInactive()
:当LiveData
没有任何一个活跃的观察者时调用。
而我们继承LiveData
的场景主要有两种:
- 通过
onActive/onInactive
回调,我们可以知道 是否有观察者正在活动,这有利于我们注册和反注册类似于传感器或者广播的监听。 - 通过将
LiveData
设置为单例的,让其在多个Activity
内共享数据,只要数据发生了变化,那么任何一个处于active
状态的观察者就会收到通知。
以下就是一个通过继承LiveData
,并重写onActive/onInactive
方法实现监听网络变化的例子。
public class NetLiveData extends LiveData {
private BroadcastReceiver mBroadcastReceiver;
private static Context sAppContext;
private static volatile NetLiveData sInstance;
private AtomicBoolean mNotice = new AtomicBoolean(false);
public static NetLiveData getInstance(Context context) {
if (sInstance == null) {
synchronized (NetLiveData.class) {
if (sInstance == null) {
sInstance = new NetLiveData(context);
}
}
}
return sInstance;
}
private NetLiveData(Context context) {
sAppContext = context.getApplicationContext();
}
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer observer) {
super.observe(owner, new Observer() {
@Override
public void onChanged(@Nullable Boolean aBoolean) {
if (mNotice.compareAndSet(true, false)) {
observer.onChanged(aBoolean);
}
}
});
}
@Override
protected void onActive() {
super.onActive();
registerBroadcast(sAppContext);
}
@Override
protected void onInactive() {
super.onInactive();
unRegisterReceiver(sAppContext);
}
@Override
protected void setValue(Boolean value) {
super.setValue(value);
mNotice.set(true);
}
/**
* 注册网络连接监听。
*/
private void registerBroadcast(Context context) {
if (mBroadcastReceiver == null) {
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
setValue(isNetworkAvailable(context));
}
};
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mBroadcastReceiver, filter);
}
}
/**
* 取消网络连接监听。
*/
private void unRegisterReceiver(Context context) {
if (mBroadcastReceiver != null) {
context.unregisterReceiver(mBroadcastReceiver);
mBroadcastReceiver = null;
}
}
/**
* 获取当前网络是否连接,连接返回 true,未连接返回 false。
*
* @param context 上下文。
* @return 网络连接连接返回 true,否则返回 false。
*/
private static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (int i = 0; i < networkInfo.length; i++) {
if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
}
四、参考文献
(1) Android架构组件 (二) - LiveData
(2) 关于使用 Android MVVM + LiveData 模式的一些建议