前言
Jetpack AAC 系列文章:
Jetpack Lifecycle 該怎么看梅惯?還肝否叔磷?
Jetpack LiveData 是時候了解一下了
Jetpack ViewModel 抽絲剝繭
上篇分析了Lifecycle卦羡,知道了如何優(yōu)雅地監(jiān)聽生命周期植捎,本篇將著重分析Lifecycle 的具體應(yīng)用場景之一:LiveData的原理及使用芽狗。
通過本篇文章鲤妥,你將了解到:
1、為什么需要LiveData?
2夷恍、LiveData 的使用方式
3魔眨、LiveData 的原理
4、LiveData 優(yōu)劣勢及其解決方案
1、為什么需要LiveData?
一個異步回調(diào)的例子
某個功能需要從網(wǎng)絡(luò)獲取數(shù)據(jù)并展示在頁面上遏暴,想想這個時候該怎么做呢侄刽?
很容易想到分三步:
1、請求網(wǎng)絡(luò)接口獲取數(shù)據(jù)朋凉。
2州丹、頁面調(diào)用接口并傳入回調(diào)對象。
3杂彭、數(shù)據(jù)通過回調(diào)接口通知UI 更新墓毒。
典型代碼如下:
object NetUtil {
//接口
lateinit var listener : InfoNotify
fun getUserInfo(notify: InfoNotify) {
listener = notify
Thread {
//模擬獲取網(wǎng)絡(luò)數(shù)據(jù)
Thread.sleep(2000)
//回調(diào)通知更新
listener?.notify(100)
}.start()
}
interface InfoNotify {
fun notify(a : Int)
}
}
編寫了一個網(wǎng)絡(luò)工具類,getUserInfo(xx) 傳入回調(diào)對象亲怠,而后在線程里拿到數(shù)據(jù)后通過回調(diào)通知界面更新:
findViewById(R.id.original_callback).setOnClickListener((v)->{
NetUtil.INSTANCE.getUserInfo(new NetUtil.InfoNotify() {
@Override
public void notify(int a) {
runOnUiThread(()->{
Toast.makeText(LiveDataActivity.this, "a=" + a, Toast.LENGTH_SHORT).show();
});
}
});
});
這是獲取異步信息并展示的常規(guī)做法蚁鳖,但卻不夠完善,存在三個問題:
第一個問題:
當(dāng)退回到桌面后赁炎,此時網(wǎng)絡(luò)接口返回數(shù)據(jù),那么就會彈出Toast钾腺,如果我們想要在App退到后臺后不再彈出Toast徙垫,那么需要在彈Toast前判斷當(dāng)前App是否在前臺可見。
第二個問題:
假若在調(diào)用網(wǎng)絡(luò)的過程中退出LiveDataActivity放棒,當(dāng)網(wǎng)絡(luò)數(shù)據(jù)返回后再Toast姻报,因?yàn)锳ctivity 已經(jīng)不存在了,就會發(fā)生Crash间螟。規(guī)避的方式如下:
runOnUiThread(()->{
//如果Activity 正在銷毀或者已經(jīng)銷毀吴旋,那就沒必要Toast了
if (!LiveDataActivity.this.isFinishing() && !LiveDataActivity.this.isDestroyed()) {
Toast.makeText(LiveDataActivity.this, "a=" + a, Toast.LENGTH_SHORT).show();
}
});
第三個問題:
我們知道內(nèi)部類持有外部類引用,而new NetUtil.InfoNotify() 表示構(gòu)建了一個匿名內(nèi)部類厢破,這個內(nèi)部類對象會被NetUtil 持有荣瑟。Activity 退出時因?yàn)楸荒涿麅?nèi)部類持有,導(dǎo)致其無法釋放摩泪,造成內(nèi)存泄漏笆焰。規(guī)避方式如下:
1)在Activity onDestroy()里移除NetUtil 的InfoNotify監(jiān)聽。
2)在NetUtil 里使用弱引用包裹InfoNotify 對象见坑。
可以看出嚷掠,為了解決以上三個問題,需要額外多出不少代碼荞驴,而這些代碼又是重復(fù)性/代表性比較高不皆,因此我們期望有一種方式來幫我們實(shí)現(xiàn)簡單的異步/同步 通信問題,我們只需要著眼于數(shù)據(jù)熊楼,而不用管生命周期霹娄、內(nèi)存泄漏等問題。
剛好LiveData 能夠滿足需求。
2项棠、LiveData 的使用方式
簡單同步使用方式
分為三步:
第一步:構(gòu)造LiveData
public class SimpleLiveData {
//LiveData 接收泛型參數(shù)
private MutableLiveData<String> name;
public MutableLiveData<String> getName() {
if (name == null) {
name = new MutableLiveData<>();
}
return name;
}
}
LiveData 是抽象類悲雳,MutableLiveData 是其中的一個實(shí)現(xiàn)子類,上面的代碼其實(shí)就是將我們感興趣的數(shù)據(jù)包裹在MutableLiveData里香追,類型為String合瓢。
為了方便獲取MutableLiveData 實(shí)例,再將它封裝在SimpleLiveData里透典。
第二步:監(jiān)聽LiveData數(shù)據(jù)變化
有了SimpleLiveData晴楔,接下來看如何對它進(jìn)行操作:
private void handleSingleLiveData() {
//構(gòu)造LiveData
simpleLiveData = new SimpleLiveData();
//獲取LiveData實(shí)例
simpleLiveData.getName().observe(this, (data)-> {
//監(jiān)聽LiveData,此處的data參數(shù)類型即是為setValue(name)時name 的類型-->String
Toast.makeText(LiveDataActivity.this, "singleLiveData name:" + data, Toast.LENGTH_SHORT).show();
});
}
第三步:主動變更LiveData數(shù)據(jù)
既然有觀察者監(jiān)聽峭咒,那么勢必需要有主動發(fā)起通知的地方税弃。
findViewById(R.id.btn_change_name).setOnClickListener((v)->{
int a = (int)(Math.random() * 10);
//獲取LiveData實(shí)例,更新LiveData
simpleLiveData.getName().setValue("singleName:" + a);
});
很簡單凑队,調(diào)用LiveData.setValue(xx)即可则果,LiveData數(shù)據(jù)發(fā)生變更后,就會通知第二步的觀察者漩氨,觀察者刷新UI(Toast)西壮。
簡單異步使用方式
你可能已經(jīng)發(fā)現(xiàn)了,上面的數(shù)據(jù)變更是在主線程發(fā)起的叫惊,我們實(shí)際場景更多的是在子線程發(fā)起的款青,模擬子線程發(fā)起數(shù)據(jù)變更:
findViewById(R.id.btn_change_name).setOnClickListener((v)->{
new Thread(()->{
int a = (int)(Math.random() * 10);
//獲取LiveData實(shí)例,更新LiveData
simpleLiveData.getName().postValue("singleName:" + a);
}).start();
});
開啟線程霍狰,在線程里更新LiveData抡草,此時調(diào)用的方法是postValue(xx)。
需要注意的是:許多文章在分析LiveData時蔗坯,習(xí)慣性和ViewModel混在一起講解康震,造成初學(xué)者理解上的困難,實(shí)際上兩者是不同的東西宾濒,都可以單獨(dú)使用签杈。分別將兩者分析后,再結(jié)合一起使用就會比較清楚來龍去脈鼎兽。
3答姥、LiveData 的原理
通過比對傳統(tǒng)的回調(diào)和LiveData,發(fā)現(xiàn)LiveData 使用簡潔谚咬,沒有傳統(tǒng)回調(diào)的那幾個缺點(diǎn)鹦付,接下來我們帶著問題分析它是如何做到規(guī)避那幾個缺點(diǎn)的。
添加觀察者
#LiveData.java
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
//該方法調(diào)用者必須在主線程
assertMainThread("observe");
//如果處在DESTROYED 狀態(tài)择卦,則沒必要添加觀察者
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
//包裝觀察者
LiveData.LifecycleBoundObserver wrapper = new LiveData.LifecycleBoundObserver(owner, observer);
//將包裝結(jié)果添加到Map里
LiveData.ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
//監(jiān)聽生命周期
owner.getLifecycle().addObserver(wrapper);
}
重點(diǎn)看看LifecycleBoundObserver:
#LiveData.java
class LifecycleBoundObserver extends LiveData.ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
...
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
//=====重要1
if (currentState == DESTROYED) {
//移除觀察者
removeObserver(mObserver);
//不再分發(fā)
return;
}
Lifecycle.State prevState = null;
while (prevState != currentState) {
prevState = currentState;
//通知觀察者
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
...
}
onStateChanged() 是LifecycleEventObserver 接口里定義的方法敲长,而LifecycleEventObserver 繼承自LifecycleObserver郎嫁。
當(dāng)宿主(Activity/Fragment) 生命周期發(fā)生改變時會調(diào)用onStateChanged()。
我們注意到注釋里的:"重要1"
removeObserver(mObserver)
目的是將之前添加的觀察者從Map 里移除祈噪。
當(dāng)宿主(Activity/Fragment) 處在DESTROYED 狀態(tài)時泽铛,移除LiveData的監(jiān)聽,避免內(nèi)存泄漏辑鲤。
這就解決了第三個問題:內(nèi)存泄漏問題盔腔。
shouldBeActive()用來判斷當(dāng)前宿主是否是活躍狀態(tài),此處定義的活躍狀態(tài)為:宿主的狀態(tài)要>="STARTED"狀態(tài)月褥,而該狀態(tài)區(qū)間為:Activity.onStart() 之后且Activity.onPause()之前弛随。
當(dāng)宿主處于活躍狀態(tài)時,才會繼續(xù)通知UI 數(shù)據(jù)變更了宁赤,進(jìn)而刷新UI舀透,若是處于非活躍狀態(tài),比如App 失去焦點(diǎn)(onPause()被調(diào)用)决左,那么將不會刷新UI 愕够。
通知觀察者
觀察者接收數(shù)據(jù)的通知有兩個來源:
1、宿主的生命周期發(fā)生變化佛猛。
2链烈、通過調(diào)用setValue()/postValue() 觸發(fā)。
上面分析的是第1種情況挚躯,接下來分析第2種場景。
LiveData.setValue() 調(diào)用棧
先看方法實(shí)現(xiàn):
#LiveData.java
protected void setValue(T value) {
//必須在主線程調(diào)用
assertMainThread("setValue");
//版本增加
mVersion++;
//暫存值
mData = value;
//分發(fā)到觀察者
dispatchingValue(null);
}
再看dispatchingValue(xx)
#LiveData.java
void dispatchingValue(@Nullable LiveData.ObserverWrapper initiator) {
...
do {
mDispatchInvalidated = false;
if (initiator != null) {
//精準(zhǔn)通知
considerNotify(initiator);
initiator = null;
} else {
//遍歷調(diào)用所有觀察者
for (Iterator<Map.Entry<Observer<? super T>, LiveData.ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
通過搜索發(fā)現(xiàn)擦秽,dispatchingValue(xx) 被兩個地方調(diào)用码荔,其實(shí)就是上面所說的:觀察者接收數(shù)據(jù)的通知有兩個來源。
當(dāng)主動調(diào)用setValue(xx)/postValue(xx)時感挥,因?yàn)闆]有指定分發(fā)給哪個觀察者缩搅,因此會遍歷通知所有觀察者。
而當(dāng)生命周期發(fā)生變化時触幼,因?yàn)槊總€觀察者都綁定了Lifecycle硼瓣,因此都獨(dú)立處理了數(shù)據(jù)分發(fā)。
如圖所示置谦,最后都會調(diào)用到considerNotify(xx):
#LiveData.java
private void considerNotify(LiveData.ObserverWrapper observer) {
//非活躍狀態(tài)堂鲤,直接返回
if (!observer.mActive) {
return;
}
//此處再額外判斷是為了防止observer.mActive 沒有及時被賦值(也就是Lifecycle 沒有及時通知到)
//因此逮光,這里會主動去拿一次狀態(tài)坟奥,若是非活躍狀態(tài)屎暇,就返回履植。
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//如果LiveData數(shù)據(jù)版本<= 觀察者的數(shù)據(jù)版本盼樟,則直接返回
if (observer.mLastVersion >= mVersion) {
return;
}
//更新觀察者版本
observer.mLastVersion = mVersion;
//最終通知觀察者
observer.mObserver.onChanged((T) mData);
}
可以看出申屹,不論數(shù)據(jù)通知來源于哪丛晌,最后都只會在活躍狀態(tài)時才會通知觀察者镜廉。
這就解決了最開始的第一個、第二個問題寓涨。
不區(qū)分活躍/非活躍
當(dāng)然啦盯串,是否活躍都是通過調(diào)用ObserverWrapper 里的方法來進(jìn)行判斷的,因此若是想要不區(qū)分是否活躍都能收到數(shù)據(jù)變更戒良,則可在添加觀察者時体捏,調(diào)用如下方法:
simpleLiveData.getName().observeForever(s -> {
Toast.makeText(LiveDataActivity.this, "singleLiveData name:" + s, Toast.LENGTH_SHORT).show();
});
該方法調(diào)用時沒有傳入LifecycleOwner 實(shí)例,因此此時的Observer沒有和Lifecycle進(jìn)行關(guān)聯(lián)蔬墩,當(dāng)然就沒有所謂的活躍與非活躍的劃分了译打。
更直觀的是Observer的命名:AlwaysActiveObserver(永遠(yuǎn)活躍)。
綁定Lifecycle Observer的命名:LifecycleBoundObserver (有限制)拇颅。
LiveData.postValue() 調(diào)用棧
#LiveData.java
protected void postValue(T value) {
boolean postTask;
//子線程奏司、主線程都需要修改mPendingData,因此需要加鎖
synchronized (mDataLock) {
//mPendingData 是否還在排隊(duì)等待發(fā)送出去
//mPendingData == NOT_SET 表示當(dāng)前沒有排隊(duì)
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
//說明上次的Runnable 還沒執(zhí)行
//直接返回樟插,不需要切換到主線程執(zhí)行
return;
}
//切換到主線程執(zhí)行Runnable
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
這里有個明顯的特點(diǎn):
當(dāng)調(diào)用postValue(xx)比較快時韵洋,數(shù)據(jù)都會更新為最新的存儲到mPendingData里,若是上條數(shù)據(jù)變更沒有發(fā)送出去黄锤,那么將不會再執(zhí)行新的Runnable了搪缨。
因此觀察者有可能不會收到全部的數(shù)據(jù)變更,而是只保證收到最新的更新鸵熟。
切換到主線程執(zhí)行Runnable:
#LiveData.java
private final Runnable mPostValueRunnable = new Runnable() {
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
//重置狀態(tài)
mPendingData = NOT_SET;
}
//發(fā)送數(shù)據(jù)變更
setValue((T) newValue);
}
};
postValue(xx)作用:
將數(shù)據(jù)存儲到臨時變量里副编,并切換到主線程執(zhí)行setValue(xx),將數(shù)據(jù)變更分發(fā)出去流强。
4痹届、LiveData 優(yōu)劣勢及其解決方案
優(yōu)勢
通過原理部分的分析,你可能已經(jīng)察覺到了:LiveData 比較簡單打月,上手也比較快队腐。
其優(yōu)勢比較明顯:
a、生命周期感知:
借助Lifecycle 能夠感知生命周期各個階段的狀態(tài)奏篙,進(jìn)而能夠?qū)Σ煌纳芷跔顟B(tài)做相應(yīng)的處理柴淘。
正因?yàn)榭梢愿兄芷冢裕?/p>
- 可以在活躍狀態(tài)時再更新UI 秘通。
- UI 保持最新數(shù)據(jù)(從非活躍到活躍狀態(tài)總能收到最新數(shù)據(jù))为严。
- 觀察者無需手動移除,不會有內(nèi)存泄漏肺稀。
- Activity/Fragment 不存活不會更新UI梗脾,避免了Crash。
- 粘性事件設(shè)計方式盹靴,新的觀察者無需再次主動獲取最新數(shù)據(jù)炸茧。
還有個額外的特點(diǎn):稍微改造一下瑞妇,LiveData 可以當(dāng)做組件之間消息傳遞使用。
b梭冠、數(shù)據(jù)實(shí)時同步
在主線程調(diào)用時:LiveData.setValue(xx)能夠直接將數(shù)據(jù)通知到觀察者辕狰。
在子線程調(diào)用時:LiveData.postValue(xx)將數(shù)據(jù)暫存,并且切換到主線程調(diào)用setValue(xx)控漠,將暫存數(shù)據(jù)發(fā)出去蔓倍。
因此,從數(shù)據(jù)變更--->發(fā)送通知--->觀察者接收數(shù)據(jù) 這幾個步驟沒有明顯地耗時盐捷,UI 能夠?qū)崟r監(jiān)聽到數(shù)據(jù)的變化偶翅。
劣勢
a、postValue(xx) 數(shù)據(jù)丟失
postValue(xx)每次調(diào)用時將數(shù)據(jù)存儲在mPendingData 變量里碉渡,因此后面的數(shù)據(jù)會覆蓋前面的數(shù)據(jù)聚谁。LiveData 確保UI 能夠拿到最新的數(shù)據(jù),而此過程中的數(shù)據(jù)變化過程可能會丟失滞诺。
問題的原因是:不是每一次數(shù)據(jù)變更都會post到主線程執(zhí)行形导。
因此想要每次都通知,則需要重新包裝一下LiveData习霹,如下:
public class LiveDataPostUtil {
private static Handler handler;
public static <T> void postValue(MutableLiveData<T> liveData, T data) {
if (liveData == null || data == null)
return;
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
handler.post(new CustomRunnable<>(liveData, data));
}
static class CustomRunnable<T> implements Runnable{
private MutableLiveData<T> liveData;
private T data;
public CustomRunnable(MutableLiveData<T> liveData, T data) {
this.liveData = liveData;
this.data = data;
}
@Override
public void run() {
liveData.setValue(data);
}
}
}
b朵耕、粘性事件
相信大家看到過一些博客的分析也知道了LiveData 粘性事件問題。
粘性事件:
數(shù)據(jù)變更發(fā)生后淋叶,才注冊的觀察者阎曹,此時觀察者還能收到變更通知。
來看看什么場景下會有這種現(xiàn)象煞檩。
定義全局持有LiveData 的單例:
public class GlobalLiveData {
private static class Inner {
static GlobalLiveData ins = new GlobalLiveData();
}
public static GlobalLiveData getInstance() {
return Inner.ins;
}
private SimpleLiveData simpleLiveData;
private GlobalLiveData() {
simpleLiveData = new SimpleLiveData();
}
public SimpleLiveData getSimpleLiveData() {
return simpleLiveData;
}
}
在Activity.onCreate()里監(jiān)聽數(shù)據(jù)變化:
GlobalLiveData.getInstance().getSimpleLiveData().getName().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
Toast.makeText(LiveDataActivity.this, "global name:" + s, Toast.LENGTH_SHORT).show();
}
});
然后點(diǎn)擊按鈕發(fā)送數(shù)據(jù)變更:
findViewById(R.id.btn_change_name).setOnClickListener((v)->{
GlobalLiveData.getInstance().getSimpleLiveData().getName().setValue("from global");
});
數(shù)據(jù)變更發(fā)出去后处嫌,觀察者收到通知并Toast,此時一切正常形娇。
當(dāng)Activity 關(guān)閉并重新打開時,此時發(fā)現(xiàn)還有Toast 彈出筹误。
粘性事件現(xiàn)象發(fā)生了桐早。
明明是全新注冊的觀察者,而且此時沒有新的數(shù)據(jù)變更厨剪,卻依然收到之前的數(shù)據(jù)哄酝。
這和LiveData 的實(shí)現(xiàn)有關(guān),看看核心源碼實(shí)現(xiàn):
#LiveData.java
private void considerNotify(LiveData.ObserverWrapper observer) {
//mVersion 為LiveData 當(dāng)前數(shù)據(jù)版本祷膳,當(dāng)setValue/postValue 發(fā)生時陶衅,mVersion++
//通過比對LiveData 當(dāng)前數(shù)據(jù)版本與觀察者的數(shù)據(jù)版本,若是發(fā)現(xiàn)LiveData 當(dāng)前數(shù)據(jù)版本 更大
//說明是之前沒有通知過觀察者直晨,因此需要通知搀军,反之則不通知膨俐。
if (observer.mLastVersion >= mVersion) {
return;
}
//將觀察者數(shù)據(jù)版本保持與LiveData 版本一致,表明該觀察者消費(fèi)了最新的數(shù)據(jù)罩句。
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
再回溯一下流程:
1焚刺、初始時LiveData.mVersion= -1,ObserverWrapper.mLastVersion = -1门烂,因此初次進(jìn)入Activity時沒有數(shù)據(jù)通知乳愉。
2、當(dāng)點(diǎn)擊按鈕后(LiveData.setValue())屯远,此時LiveData.mVersion = 0蔓姚;因?yàn)長iveData.mVersion>ObserverWrapper.mLastVersion,因此觀察者能夠收到通知慨丐。
3坡脐、當(dāng)退出Activity 再進(jìn)來后,因?yàn)镺bserverWrapper 是全新new 出來的咖气,ObserverWrapper.mLastVersion = -1挨措,而LiveData.mVersion =0,還是大于ObserverWrapper.mLastVersion崩溪,因此依然能夠收到通知浅役。
要解決這個問題,很直觀的想法是從version字段出發(fā)伶唯,而LiveData觉既、ObserverWrapper 并沒有對外暴露方法來修改version,此時我們想到了反射乳幸。
通過反射修改ObserverWrapper.mLastVersion 的值瞪讼,使得在第一次注冊時候保持與LiveData.mVersion 值一致。
這也是很多博客的主流解決方法粹断,因?yàn)橐瓷銶ap符欠,進(jìn)而反射里面的Observer拿出version,步驟有點(diǎn)多瓶埋,這里提供一種方案希柿,只需要拿到LiveData.mVersion即可,剛好LiveData提供了方法:
int getVersion() {
return mVersion;
}
因此我們只需要調(diào)用這個反射方法即可:
public class EasyLiveData<T> extends LiveData<T> {
@Override
public void observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) {
super.observe(owner, new EasyObserver<>(observer));
}
@Override
public void observeForever(@NonNull @NotNull Observer<? super T> observer) {
super.observeForever(new EasyObserver<>(observer));
}
@Override
protected void setValue(T value) {
super.setValue(value);
}
@Override
protected void postValue(T value) {
super.postValue(value);
}
class EasyObserver<T> implements Observer<T>{
private Observer observer;
private boolean shouldConsumeFirstNotify;
public EasyObserver(Observer observer) {
this.observer = observer;
shouldConsumeFirstNotify = isNewLiveData(EasyLiveData.this);
}
@Override
public void onChanged(T t) {
//第一次進(jìn)來养筒,沒有發(fā)生過數(shù)據(jù)變更曾撤,則后續(xù)的變更直接通知。
if (shouldConsumeFirstNotify) {
observer.onChanged(t);
} else {
//若是LiveData 之前就有數(shù)據(jù)變更晕粪,那么這一次的變更不處理
shouldConsumeFirstNotify = true;
}
}
private boolean isNewLiveData(LiveData liveData) {
Class ldClass = LiveData.class;
try {
Method method = ldClass.getDeclaredMethod("getVersion");
method.setAccessible(true);
//獲取版本
int version = (int)method.invoke(liveData);
//版本為-1挤悉,說明是初始狀態(tài),LiveData 還未發(fā)生過數(shù)據(jù)變更巫湘。
return version == -1;
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
}
如若不想要粘性事件装悲,則使用上述的EasyLiveData 即可昏鹃。
粘性事件/非粘性事件 對比如下:
可以看出,再次進(jìn)入Activity時衅斩,并沒有彈出Toast盆顾。
優(yōu)劣勢辯證看
LiveData 優(yōu)勢很明顯,當(dāng)然劣勢也比較突出畏梆,雖然說是劣勢您宪,換個角度看就是仁者見仁智者見智:
個人猜測LiveData 設(shè)計的側(cè)重點(diǎn)就不是在消息通知上,而是為了讓UI 能夠感知到最新數(shù)據(jù)奠涌,并且無需再次請求數(shù)據(jù)宪巨。
當(dāng)然,為了使得LiveData 更加契合我們的應(yīng)用場景溜畅,可以按上述方法進(jìn)行適當(dāng)改造捏卓。
如果你是用Java 開發(fā),那么LiveData 是把利刃慈格,如果你用Kotlin怠晴,可以考慮用Flow。
下篇將分析ViewModel浴捆,徹底厘清為啥ViewModel能夠存儲數(shù)據(jù)以及運(yùn)用場合蒜田。
本文基于:implementation 'androidx.appcompat:appcompat:1.4.1'
您若喜歡,請點(diǎn)贊选泻、關(guān)注冲粤,您的鼓勵是我前進(jìn)的動力
持續(xù)更新中,和我一起步步為營系統(tǒng)页眯、深入學(xué)習(xí)Android
1梯捕、Android各種Context的前世今生
2、Android DecorView 必知必會
3窝撵、Window/WindowManager 不可不知之事
4傀顾、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6碌奉、Android invalidate/postInvalidate/requestLayout 徹底厘清
7短曾、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9道批、Android 鍵盤一招搞定
10错英、Android 各種坐標(biāo)徹底明了
11入撒、Android Activity/Window/View 的background
12隆豹、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14茅逮、Android 存儲系列
15璃赡、Java 并發(fā)系列不再疑惑
16判哥、Java 線程池系列
17、Android Jetpack 前置基礎(chǔ)系列
18碉考、Android Jetpack 易懂易學(xué)系列