關于LiveData可能引發(fā)的內存泄漏及優(yōu)化
隨著MVVM的流行,LiveData便成了Android數據重要的存儲和觀察組件.
一般我們會將LiveData和ViewModel結合使用,LiveData作為ViewModel的成員.
LiveData相比較于一般的觀察者組件,其好用的地方是它在observe后不需要手動的解除訂閱,它會根據訂閱者的生命周期自動進行解除.
可能產生的泄漏分析
這看起來很完美,不過我們深入源碼看看訂閱過程.
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
//......
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
//......
owner.getLifecycle().addObserver(wrapper);
}
這里將LiveData的訂閱者observer
裝封到了LifecycleBoundObserver中,然后又將LifecycleBoundObserver實例作為LifeCycleOwner的生命周期觀察者.
查看LifecycleBoundObserver的實現
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
//......
}
可以發(fā)現這是一個Java的內部類,也就是說這個內部類的實例會持有LiveData的引用,
而LifecycleBoundObserver作為觀察者被添加到了LifecycleOwner的生命周期觀察者里.
這就是說LifeCycleOwner實際上會持有LiveData的引用.
查找LiveData中解除引用的代碼如下
@MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) {
//......
removed.detachObserver();
//......
}
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
//......
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
//......
}
//......
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
這個引用會一直持續(xù)到LifeCycleOwner的onDestroy回調觸發(fā)后才會將LiveData的引用從LifeCycleOwner中解除.
綜上,通過observer方式注冊的LiveData,其只能在LifeCycleOwner被銷毀后才可能會被回收.
這在ViewModel上使用倒是沒問題的,畢竟ViewModel的生命周期和Activity/Fragment基本是同步的.
除非在使用Fragment的ViewModel中的LiveData.observe時傳了Activity作為LifeCycleOwner,如果是這樣便會導致Fragment銷毀后,Activity仍然持有此Fragment關聯的ViewModel中的LiveData引用.
但是LiveData作為一個輕量級又特別好用的數據觀察組件,只放在ViewModel中多少有點不甘心不是.
但是當我們脫離ViewModel使用LiveData時,比如我們想通過LiveData來觀察一個對象的狀態(tài)變化,那么如果這個對象是在LifeCycleOwner銷毀前被垃圾回收了,那么這個對象的LiveData卻不會被回收,而依然被LifeCycleOwner所引用著,那么此時便會發(fā)生內存泄漏.
解決方法
LiveData作為一個如此優(yōu)秀的輕量級數據觀察組件,只能放在ViewModel中使用未免太可惜.
那么有什么辦法能讓LiveData能在其它對象中使用呢?
辦法是有的,比如我們在名為TestTask的對象中添加了一個LiveData,如果我們希望在對象被回收的時候LiveData也會被跟著被回收,那么我們可以在TestTask的finalize方法中添加代碼讓LifeCycleOwner移除對LiveData的引用.
但是這種方法多少有點麻煩,好吧不是有點麻煩而是特別麻煩.
不過這方法能給我們提供一個新的思路,就是我們用一個容器來存放LiveData,然后再通過容器的finalize來解除LifeCycleOwner對LiveData的引用,在TestTask中使用這個容器來訪問LiveData.
通過這種方式,當TestTask被回收時,其中的容器也將會被回收,當容器回收時解除LifeCycleOwner對LiveData的引用,這樣LiveData本身便能正常被銷毀.
嗯,這種方式可行.不過還是略微有點麻煩,畢竟還有一個容器不是.
要解決這個容器也簡單,實際上我們只需要使用裝飾模式,讓這個容器繼承于LiveData,并且在其中添加一個實際持有數據的LiveData,然后所有事情都甩給真實持有數據的LiveData的去干,外層LiveData只需要處理finalize時解除LifeCycleOwner對LiveData的引用即可.
將這個裝飾者命名為SafeLiveData
大致代碼如下
public class SafeLiveData<T> extends MutableLiveData<T> {
private final MutableLiveData<T> realLiveData;
private final List<WeakReference<Observer<? super T>>> observerReferenceList = new ArrayList();
public SafeLiveData(MutableLiveData<T> liveData) {
super();
realLiveData = liveData;
}
public SafeLiveData() {
super();
realLiveData = new MutableLiveData<T>();
}
//......
@MainThread
private void recordObserver(@NonNull Observer<? super T> observer) {
//add observer weakreference to observerReferenceList
//......
}
@Override
protected void finalize() throws Throwable {
super.finalize();
runInMainThread(() -> {
int size = observerReferenceList.size();
for (int i = 0; i < size; i++) {
Observer<? super T> observer = observerReferenceList.get(i).get();
if (observer != null) {
realLiveData.removeObserver(observer);
}
}
});
}
@Override
public void observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) {
runInMainThread(() -> {
recordObserver(observer);
realLiveData.observe(owner, observer);
});
}
@Override
public void observeForever(@NonNull @NotNull Observer<? super T> observer) {
runInMainThread(() -> {
recordObserver(observer);
realLiveData.observeForever(observer);
});
}
//......
}
如此便可完美解決LiveData的內存泄漏的問題.
LiveData線程優(yōu)化
以上解決了內存泄漏的問題,但是實際使用上依然還有個問題,便是線程安全問題.LiveData本身并沒有做線程安全相關的操作,其默認是只能在主線程中使用的,畢竟在大部分方法里都加了assertMainThread
方法
private static void assertMainThread(String methodName) {
if (!ArchTaskExecutor.getInstance().isMainThread()) {
throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ " thread");
}
}
只要你沒在主線程中調用就會拋異常.
在后臺線程里我們也能通過postValue來發(fā)送消息,但是LiveData只有這一個方式是切了線程的,其它方法比如observe,在后臺線程調用直接crash.
還有我們可能會有這樣的操作
class TestTask {
val infoLiveData = MutableLiveData<Int>().apply { value = 0 }
}
類初始化便會調用setValue方法,如果類初始化并不在主線程就會崩潰;
異或這樣的操作
object Manager {
init {
Setting.settingLiveData.observeForever{
}
}
}
object Setting {
val settingLiveData = MutableLiveData<Boolean>()
}
如果Manager初次調用不在主線程也會導致崩潰.
那么如何解決這個問題呢?
實際上也很簡單對于大部分方法我們只需要在裝飾的LiveData的方法中加入Handler.Post對操作進行線程切換,為了避免不必要的Post可能會導致的時序問題,可以先判斷是否在主線程,如果在則直接執(zhí)行,如果不在則Post.
private static void runInMainThread(Runnable runnable) {
boolean inMainThread = isInMainThread();
if (inMainThread) {
runnable.run();
} else {
getHandler().post(runnable);
}
}
對于大部分方法都能通過這種方式來實現,但是有一個方法例外就是setValue
setValue是不能Post的,假設我們使用Post來setValue,那么在setValue后我們將不能馬上使用這個值,當獲取這個值時,這個值可能是空值,這種問題是致命的.
那么如何解決這個問題呢?
我們可以通過wait和notify的方式.如果是一個后臺線程調用setValue,則我們可以先Post一個任務去執(zhí)行setValue的操作,并將此后臺線程掛起,當任務切到主線程執(zhí)行完setValue后,我們再將這個線程喚醒.
實現方法如下
public static <T> void setValueSync(MutableLiveData<T> liveData, T value) {
if (liveData == null) {
return;
}
if (isInMainThread()) {
liveData.setValue(value);
return;
}
Runnable runnable = () -> {
synchronized (liveData) {
try {
liveData.setValue(value);
} finally {
liveData.notify();
}
}
};
synchronized (liveData) {
getHandler().post(runnable);
try {
liveData.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
這樣也算是完美解決這個問題了,如果后臺線程覺得setValue需要等待太慢,也可以使用postValue來傳值.
吐槽
為啥上面代碼都是Java呢?你好low了,現在不會Kotlin都沒人要了兄弟!
??? 是我不想用Kotlin嗎,f**k!
原因是這樣的,用Kotlin重寫LiveData的setValue和getValue會導致無法訪問屬性value來交互,這樣的體驗將是非常差的,而且Google查了半天也沒查到解決辦法.如果有大佬有解決辦法!跪求告知!!!
然后Kotlin對象沒有wait和notify,只能用ReentrantLock和Condition實現.
如果用ReentrantLock和Condition來實現同步的setValue則需要很多額外的對象和維護開銷,較為繁瑣.
這語法糖里有毒......
使用SafeLiveData
如果你覺得上面所說也能解決你的痛點,不如試試這個SafeLiveData
依賴
allprojects {
repositories {
//...
maven { url 'https://www.jitpack.io' }
}
}
dependencies {
def lastSafeLiveDataVersion = "1.1.1" //replace lastSafeLiveDataVersion
implementation "com.github.dqh147258:SafeLiveData:$lastSafeLiveDataVersion"
}
使用的話很簡單將MutableLiveData替換成SafeLiveData即可
例如
val infoLiveData = MutableLiveData<Int>()
替換成
val infoLiveData = SafeLiveData<Int>()
如果你不想破壞自己的LiveData邏輯實現,這是個裝飾器,想保留自己LiveData的特性也簡單,作為構造參數傳入即可
val yourLiveData = MediatorLiveData<Int>()
val infoLiveData = SafeLiveData<Int>(yourLiveData)