一、概述
在學(xué)習(xí)完Lifecycle
之后,我們?nèi)绾瓮ㄟ^Lifecycle
讓除了Activity/Fragment
之外的其它對象都可以很方便地收到當(dāng)前頁面生命周期的回調(diào)。
今天我們來學(xué)習(xí)ViewModel+LiveData
,它們共同的特點就是綁定到了UI
組件(Activity/Fragment
)上,可以收到生命周期的回調(diào),簡單地來說,這兩個組件的作用分別為:
-
LiveData
:在Lifecycle
范圍內(nèi) 監(jiān)聽數(shù)據(jù) 的變化。 -
ViewModel
:在Lifecycle
范圍內(nèi) 存儲和共享數(shù)據(jù)。
今天,我們就一起來學(xué)習(xí)一下LiveData
的使用場景。
二、LiveData+ViewModel
這個Demo
的功能很簡單,在界面上有一個Btn
,點擊Btn
后異步加載數(shù)據(jù),數(shù)據(jù)回來以后通知UI
來更新。
2.1 創(chuàng)建 ViewModel 和 LiveData
/**
* 繼承于 ViewModel。
*
* @author lizejun
*/
public class DataViewModel extends ViewModel {
private static final String TAG = DataViewModel.class.getSimpleName();
//這里創(chuàng)建一個 MutableLiveData,<?> 為要提供的數(shù)據(jù)類型,這里我們聲明為 List。
private MutableLiveData<List<String>> mWatcher;
private Handler mWorkHandler;
/**
* 加載數(shù)據(jù),在實際當(dāng)中,加載數(shù)據(jù)的操作要放在 Repository 中進行,而不要放在 Model 中,
* 它只是負責(zé)數(shù)據(jù)和 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() {
//模擬加載數(shù)據(jù)的過程。
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
List<String> result = makeResult();
setResults(result);
}
});
}
/**
* 獲取數(shù)據(jù)的監(jiān)控者。
*
* @return 監(jiān)控者。
*/
public MutableLiveData<List<String>> getWatcher() {
Log.d(TAG, "Call getWatcher");
if (mWatcher == null) {
mWatcher = new MutableLiveData<>();
}
return mWatcher;
}
/**
* 設(shè)置數(shù)據(jù)。
*
* @param results 設(shè)置數(shù)據(jù)。
*/
private void setResults(List<String> results) {
Log.d(TAG, "Call setResults");
//當(dāng)數(shù)據(jù)加載完以后,調(diào)用 setValue/postValue 方法設(shè)置數(shù)據(jù)。
if (Looper.getMainLooper() == Looper.myLooper()) {
getWatcher().setValue(results);
} else {
getWatcher().postValue(results);
}
}
private List<String> makeResult() {
List<String> result = new ArrayList<>();
result.add("蘋果 - 1");
result.add("蘋果 - 2");
result.add("蘋果 - 3");
result.add("蘋果 - 4");
result.add("蘋果 - 5");
result.add("蘋果 - 6");
return result;
}
}
我們創(chuàng)建一個繼承 于ViewModel
的DataViewModel
,前面我們也提到過,ViewModel
作用是存儲和共享數(shù)據(jù),這里的作用就是 存儲。
我們所需要的真實數(shù)據(jù)則是被保存在MutableLiveData<?>
當(dāng)中,?
就是真實數(shù)據(jù)的類型,而MutableLiveData
就是我們所說的LiveData
,它除了負責(zé)保存數(shù)據(jù)外,還負責(zé)在數(shù)據(jù)變化的時候,通知它的觀察者。
往LiveData
中插入數(shù)據(jù)的方式有兩種:
-
setValue
:只允許在主線程中調(diào)用。 -
postValue
:主線程/子線程中均可調(diào)用。
在上面的Demo
中,我們在load()
中模擬了異步獲取數(shù)據(jù)的操作的邏輯,并最終向LiveData
中插入數(shù)據(jù)。
2.2 使用方式
下面,讓我們來看一下完整的使用過程,以及一些特殊的場景。
/**
* LiveData 學(xué)習(xí) 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.創(chuàng)建 ViewModel。
mViewModel = ViewModelProviders.of(this).get(DataViewModel.class);
//2.添加觀察者。
mViewModel.getWatcher().observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable List<String> strings) {
Log.d("DataViewModel", "onChanged");
String tvDisplay = "";
for (String result : strings) {
tvDisplay += (result + "\n");
}
//4.數(shù)據(jù)發(fā)生了改變后會回調(diào)到這里。
mTvResult.setText(tvDisplay);
}
});
mBtnRefresh = findViewById(R.id.btn_refresh);
mBtnRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//3.觸發(fā)加載。
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)
創(chuàng)建了DataViewModel
的實例,而不是通過直接new
的方式,這么做的原因有兩個:
- 通過
of
傳入的Activity
,其在內(nèi)部實現(xiàn)了與Activity
的綁定。 - 為了數(shù)據(jù)的共享,這點我們暫時還沒有體會到,后面會講。
接著,獲取到DataViewModel
當(dāng)中的MutableLiveData
對象,調(diào)用其observe
方法,該方法接受兩個參數(shù),分別對應(yīng)于其要綁定到的組件以及一個監(jiān)聽者,當(dāng)我們改變了MutableLiveData
內(nèi)部持有的數(shù)據(jù)后,將會回調(diào)該監(jiān)聽者,我們就可以在里面做更新數(shù)據(jù)的操作。
當(dāng)我們點擊 加載 按鈕后,log
輸出如下所示:
現(xiàn)在,讓我們模擬一種特殊的場景,在點擊加載按鈕后,迅速按下HOME
鍵讓頁面處于inactive
的狀態(tài),此時的輸出為:
可以發(fā)現(xiàn),沒有回調(diào) onChanged() 方法,之后重新進入界面:
在重新進入界面之后,才回調(diào)了
onChanged()
方法,也就是說,在界面inactive
的狀態(tài)下發(fā)生了數(shù)據(jù)的改變,不會立即通知觀察者,而是要等到界面重新active
之后,才會調(diào)用observer
的onChanged()
方法。
除了observe
方法外,還有一個observeForever
,它只接受一個觀察者參數(shù),也就是說,它并不關(guān)注當(dāng)前界面是否active
,都會回調(diào)數(shù)據(jù)。
//不與組件的生命周期綁定。
mViewModel.getWatcher().observeForever(new Observer<List<String>>() {
@Override
public void onChanged(@Nullable List<String> strings) {
Log.d("DataViewModel", "onChanged");
String tvDisplay = "";
for (String result : strings) {
tvDisplay += (result + "\n");
}
//4.數(shù)據(jù)發(fā)生了改變后會回調(diào)到這里。
mTvResult.setText(tvDisplay);
}
});
用相同的操作驗證的結(jié)果如下,即使界面處于inactive
狀態(tài),也會回調(diào)onChanged()
方法:
因此,為了避免內(nèi)存泄漏,我們應(yīng)當(dāng)在界面銷毀的時候,調(diào)用
MutableLiveData
的removeObserver
方法。
2.3 小結(jié)
整個數(shù)據(jù)的流向如下所示:
通過
LiveData
作為中介者,實現(xiàn)了UI
組件和數(shù)據(jù)組件的隔離,對于UI
組件來說,它只負責(zé) 發(fā)起請求操作 和 在數(shù)據(jù)變化的時候更新界面,而對于數(shù)據(jù)組件來說,它負責(zé) 接受請求和 改變LiveData
,而通知UI
組件的操作則是由LiveData
來完成的,因此它可以根據(jù)當(dāng)前UI
組件的狀態(tài)active/inative
進行控制。
三、繼承 LiveData
繼承LiveData
的時候,我們一般會重寫它的下面兩個方法,它們的含義為:
-
onActive()
:當(dāng)LiveData
有一個活躍的觀察者時調(diào)用。 -
onInactive()
:當(dāng)LiveData
沒有任何一個活躍的觀察者時調(diào)用。
而我們繼承LiveData
的場景主要有兩種:
- 通過
onActive/onInactive
回調(diào),我們可以知道 是否有觀察者正在活動,這有利于我們注冊和反注冊類似于傳感器或者廣播的監(jiān)聽。 - 通過將
LiveData
設(shè)置為單例的,讓其在多個Activity
內(nèi)共享數(shù)據(jù),只要數(shù)據(jù)發(fā)生了變化,那么任何一個處于active
狀態(tài)的觀察者就會收到通知。
以下就是一個通過繼承LiveData
,并重寫onActive/onInactive
方法實現(xiàn)監(jiān)聽網(wǎng)絡(luò)變化的例子。
public class NetLiveData extends LiveData<Boolean> {
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<Boolean> observer) {
super.observe(owner, new Observer<Boolean>() {
@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);
}
/**
* 注冊網(wǎng)絡(luò)連接監(jiān)聽。
*/
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);
}
}
/**
* 取消網(wǎng)絡(luò)連接監(jiān)聽。
*/
private void unRegisterReceiver(Context context) {
if (mBroadcastReceiver != null) {
context.unregisterReceiver(mBroadcastReceiver);
mBroadcastReceiver = null;
}
}
/**
* 獲取當(dāng)前網(wǎng)絡(luò)是否連接,連接返回 true,未連接返回 false。
*
* @param context 上下文。
* @return 網(wǎng)絡(luò)連接連接返回 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架構(gòu)組件 (二) - LiveData
(2) 關(guān)于使用 Android MVVM + LiveData 模式的一些建議