MutableLiveData作爲全局變量時,觀察者方法被重複調用問題

Android Jetpack 提供了一系列的庫和工具,其中就包括了LiveData。今天我要講的是當MutableLiveData作爲全局變量,觀察者方法被重複調用的問題。

DataRepository 作爲單例類,聲明類型MutableLiveData的變量data。

object DataRepository {

    var data = MutableLiveData<String>()
}

​ 在DataActivityonCreate方法中,添加變量data方法的觀察者,緊接着使用postValue方法對data進行賦值。

class DataActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_data)

        DataRepository.data.observe(this, Observer {
            Log.i("TAG", "data:$it")
        })

        Handler().postDelayed({
            DataRepository.data.postValue("${Random.nextInt(100)}")
        }, 1 * 1000)
    }
}

​ 主要代碼就是以上內容,代碼十分簡單,看起來也沒有什麼問題。假設打開APP,首先進入MainActivity頁面,再由MainActivity跳轉到DataActivity。在進入DataActivity後,將會打印data:$it,一切都很正常。當從DataActivity返回到MainActivity,再次進入到DataActivity,會發現data:$it會打印兩次。

2020-05-27 10:21:15.626 I/DataActivity: data:15
2020-05-27 10:21:16.622 I/DataActivity: data:17

​ 這樣的結果就十分的奇怪了,爲什麼第一次進入DataActivity,輸出正常,第二次進入DataActivity,卻打印了兩次?我們可以看到這兩次的結果是不同的,並且存在1秒鐘的間隔,那就可以猜測觀察者方法並不是被一次data的賦值調用了兩次,而是進入到DataActivity,註冊觀察者後,其方法就被調用了。下面我們來LiveDataobserve

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    owner.getLifecycle().addObserver(wrapper);
}

​ 如上面的代碼所示,生成了LifecycleBoundObserver對象,並將此對象添加爲owner生命週期的觀察者。

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {

    @Override
    boolean shouldBeActive() {
    	return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }
        
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        activeStateChanged(shouldBeActive());
    }

}
void activeStateChanged(boolean newActive) {
	......
    if (mActive) {
        dispatchingValue(this);
    }
}

當生命週期發生改變時,執行完就會先調用onStateChanged,之後調用activeStateChanged方法。當生命週期大於等於STARTED時,就會調用dispatchingValue方法

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else {
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    // notify for a more predictable notification order.
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

​ 接着就會調用considerNotify,將數據通知到觀察者。其實第二次進入DataActivity,是由於第一次postValue添加的值已經賦值給mData,當第二次進入DataActivity後,執行到生命週期STARTED後,就會把第一次緩存的mData再次通知出來。出現這個問題的原因已經找到了,那麼怎麼解決這個問題呢。我們可以看下當observer.mLastVersion >= mVersion就不會通知觀察者。每次改變LiveData時,mVersion的值就會加1,當通知給觀察者後,就會把值賦值給觀察者的mLastVersion,避免的重複通知。由於LiveDatamVersion在第一次設置了值,所以兩者的版本不一致。解決問題思路是在調用observe後,使用反射,將LiveDatamVersion賦值給observer.mLastVersion,這樣兩者版本一樣,onChanged方法就不會執行。

public class PublicMutableLiveData<T> extends MutableLiveData<T> {

    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, observer);
        hook(observer);
    }

    private void hook(Observer<? super T> observer) {
        Class<LiveData> liveDataClass = LiveData.class;
        try {
            Field mObservers = liveDataClass.getDeclaredField("mObservers");
            mObservers.setAccessible(true);
            Object mObserversObject = mObservers.get(this);
            if(mObserversObject == null){
                return;
            }
            Class<?> mObserversClass = mObserversObject.getClass();
            Method methodGet = mObserversClass.getDeclaredMethod("get", Object.class);
            methodGet.setAccessible(true);
            Object entry = methodGet.invoke(mObserversObject, observer);
            if(!(entry instanceof Map.Entry)){
                return;
            }
            Object lifecycleBoundObserver = ((Map.Entry) entry).getValue();

            Class<?> observerWrapper = lifecycleBoundObserver.getClass().getSuperclass();
            if(observerWrapper == null){
                return;
            }
            Field mLastVersionField = observerWrapper.getDeclaredField("mLastVersion");
            mLastVersionField.setAccessible(true);

            Method versionMethod = liveDataClass.getDeclaredMethod("getVersion");
            versionMethod.setAccessible(true);
            Object version = versionMethod.invoke(this);

            mLastVersionField.set(lifecycleBoundObserver, version);

            mObservers.setAccessible(false);
            methodGet.setAccessible(false);
            mLastVersionField.setAccessible(false);
            versionMethod.setAccessible(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

文章相關鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章