理解Android Architecture Components系列之LiveData(四)

LiveData

LiveData是一種持有可被觀察數據的類。和其他可被觀察的類不同的是,LiveData是有生命周期感知能力的,這意味著它可以在activities, fragments, 或者 services生命周期是活躍狀態時更新這些組件。那么什么是活躍狀態呢?上篇文章中提到的STARTEDRESUMED就是活躍狀態,只有在這兩個狀態下LiveData是會通知數據變化的。

要想使用LiveData(或者這種有可被觀察數據能力的類)就必須配合實現了LifecycleOwner的對象使用。在這種情況下,當對應的生命周期對象DESTORY時,才能移除觀察者。這對Activity或者Fragment來說顯得尤為重要,因為他們可以在生命周期結束的時候立刻解除對數據的訂閱,從而避免內存泄漏等問題。

使用LiveData的優點

  • UI和實時數據保持一致 因為LiveData采用的是觀察者模式,這樣一來就可以在數據發生改變時獲得通知,更新UI。
  • 避免內存泄漏 觀察者被綁定到組件的生命周期上,當被綁定的組件銷毀(destory)時,觀察者會立刻自動清理自身的數據。
  • 不會再產生由于Activity處于stop狀態而引起的崩潰 例如:當Activity處于后臺狀態時,是不會收到LiveData的任何事件的。
  • 不需要再解決生命周期帶來的問題 LiveData可以感知被綁定的組件的生命周期,只有在活躍狀態才會通知數據變化。
  • 實時數據刷新 當組件處于活躍狀態或者從不活躍狀態到活躍狀態時總是能收到最新的數據
  • 解決Configuration Change問題 在屏幕發生旋轉或者被回收再次啟動,立刻就能收到最新的數據。
  • 數據共享 如果對應的LiveData是單例的話,就能在app的組件間分享數據。這部分詳細的信息可以參考繼承LiveData

使用LiveData

  1. 創建一個持有某種數據類型的LiveData (通常是在ViewModel中)
  2. 創建一個定義了onChange()方法的觀察者。這個方法是控制LiveData中數據發生變化時,采取什么措施 (比如更新界面)。通常是在UI Controller (Activity/Fragment) 中創建這個觀察者。
  3. 通過 observe()方法連接觀察者和LiveData。observe()方法需要攜帶一個LifecycleOwner類。這樣就可以讓觀察者訂閱LiveData中的數據,實現實時更新。

創建LiveData對象

LiveData是一個數據的包裝。具體的包裝對象可以是任何數據,包括集合(比如List)。LiveData通常在ViewModel中創建,然后通過gatter方法獲取。具體可以看一下代碼:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String 暫時就把MutableLiveData看成是LiveData吧,下面的文章有詳細的解釋
private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

// Rest of the ViewModel...
}

觀察LiveData中的數據

通常情況下都是在組件的onCreate()方法中開始觀察數據,原因有以下兩點:

  • 系統會多次調用onResume()方法。
  • 確保Activity/Fragment在處于活躍狀態時立刻可以展示數據。

下面的代碼展示了如何觀察LiveData對象:

public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

更新LiveData對象

如果想要在UI Controller中改變LiveData中的值呢?(比如點擊某個Button把性別從男設置成女)。LiveData并沒有提供這樣的功能,但是Architecture Component提供了MutableLiveData這樣一個類,可以通過setValue(T)postValue(T)方法來修改存儲在LiveData中的數據。MutableLiveDataLiveData的一個子類,從名稱上也能看出這個類的作用。舉個直觀點的例子:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});

調用setValue()方法就可以把LiveData中的值改為John Doe。同樣,通過這種方法修改LiveData中的值同樣會觸發所有對這個數據感興趣的類。那么setValue()postValue()有什么不同呢?區別就是setValue()只能在主線程中調用,而postValue()可以在子線程中調用。

Room和LiveData配合使用

Room可以返回LiveData的數據類型。這樣對數據庫中的任何改動都會被傳遞出去。這樣修改完數據庫就能獲取最新的數據,減少了主動獲取數據的代碼。詳細的例子在前面的文章,不記得可以回去翻翻。

繼承LiveData擴展功能

LiveData的活躍狀態包括:STARTED或者RESUMED兩種狀態。那么如何在活躍狀態下把數據傳遞出去呢?下面是示例代碼:

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

可以看到onActive()onInactive()就表示了處于活躍和不活躍狀態的回調。

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

如果把StockLiveData寫成單例模式,那么還可以在不同的組件間共享數據。代碼如下:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

轉換LiveData中的值(Transform LiveData)

這么說很容易和上文改變LiveData中的值搞混。這里的變換是指在LiveData的數據被分發到各個組件之前轉換值的內容,各個組件收到的是轉換后的值,但是LiveData里面數據本身的值并沒有改變。(和RXJava中map的概念很像)Lifecycle包中提供了Transformations來提供轉換的功能。

Transformations.map()

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

把原來是包含User的LiveData轉換成包含String的LiveData傳遞出去。

Transformations.switchMap()

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

和上面的map()方法很像。區別在于傳遞給switchMap()的函數必須返回LiveData對象。
和LiveData一樣,Transformation也可以在觀察者的整個生命周期中存在。只有在觀察者處于觀察LiveData狀態時,Transformation才會運算。Transformation是延遲運算的(calculated lazily),而生命周期感知的能力確保不會因為延遲發生任何問題。

如果在ViewModel對象的內部需要一個Lifecycle對象,那么使用Transformation是一個不錯的方法。舉個例子:假如有個UI組件接受輸入的地址,返回對應的郵政編碼。那么可以 實現一個ViewModel和這個組件綁定:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS (不要這么干)
       return repository.getPostCode(address);
    }
}

看代碼中的注釋,有個// DON'T DO THIS (不要這么干),這是為什么?有一種情況是如果UI組件被回收后又被重新創建,那么又會觸發一次 repository.getPostCode(address)查詢,而不是重用上次已經獲取到的查詢。那么應該怎樣避免這個問題呢?看一下下面的代碼:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

postalCode變量的修飾符是publicfinal,因為這個變量的是不會改變的。哎?不會改變?那我輸入不同的地址還總返回相同郵編?先打住,postalCode這個變量存在的作用是把輸入的addressInput轉換成郵編,那么只有在輸入變化時才會調用repository.getPostCode()方法。這就好比你用final來修飾一個數組,雖然這個變量不能再指向其他數組,但是數組里面的內容是可以被修改的。繞來繞去就一點:當輸入是相同的情況下,用了 switchMap() 可以減少沒有必要的請求。并且同樣,只有在觀察者處于活躍狀態時才會運算并將結果通知觀察者。(最近一直有大佬反應這段話有問題,感謝各位大佬指正,我又看了遍英文文檔,確實有邏輯不通的地方。原文中的話就是:In this case, the postalCode field is public and final, because the field never changes. The postalCode field is defined as a transformation of the addressInput, which means that the repository.getPostCode() method is called when addressInput changes. This is true if there is an active observer, if there are no active observers at the time repository.getPostCode() is called, no calculations are made until an observer is added.翻譯過來就是通過addressInput做一層緩沖,只有addressInput發生改變時才會真的發生一次查詢,UI組件回收再被重建后重新設置了addressInput中內容,但是addressInput中內容和上次(重建前)一樣,因此不會觸發真正的查詢,在這個意義上來講減少了查詢次數。所以使用switchMap()的意義就在于對底層返回的LiveData根據需要變更其中的內容,同時不會影響生命周期控制邏輯。再次感謝各位大佬的較真,你們的指正是我不斷前進的動力!!!如果覺得還有有邏輯問題,歡迎溝通!!!

合并多個LiveData中的數據

MediatorLiveData是LiveData的子類,可以通過MediatorLiveData合并多個LiveData來源的數據。同樣任意一個來源的LiveData數據發生變化,MediatorLiveData都會通知觀察他的對象。說的有點抽象,舉個例子。比如UI接收來自本地數據庫和網絡數據,并更新相應的UI。可以把下面兩個LiveData加入到MeidatorLiveData中:

  • 關聯數據庫的LiveData
  • 關聯聯網請求的LiveData

相應的UI只需要關注MediatorLiveData就可以在任意數據來源更新時收到通知。

相關文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)
理解Android Architecture Components系列之WorkManager(八)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容