Android Architecture Components Part2:LiveData

怪談時間發表於2018-06-13

Android Architecture Components Part2:LiveData

感謝你的再次光臨,歡迎來到Android Architecture Components(ACC)系列文章。上篇文章我們一起討論了Room,通過Room我們能夠方便的操作App的資料庫。如果你的App對本地資料庫有所依賴的話,Room你值得擁有。

今天這篇文章繼續上篇文章的步伐,讓我們一起來全面瞭解ACC另一強大的元件LiveData。相信你馬上會喜歡上她!???

簡述

LiveData是一種可觀測資料容器,它會在資料變化時通知觀測器,以便更新頁面;同時它具備生命感知能力,可以實時觀察Activity/Fragment的生命週期狀態。

既然它是可觀察資料容器與具備生命感知能力,那麼它的優點也很明顯,可以歸納與以下幾點

  1. 確保ui跟隨資料更新
  2. 具備生命感知能力從而減少記憶體洩露
  3. 防止異常crashs
  4. 無需管理繫結者的生命週期
  5. ui獲取的資料都是最近最終的更新資料

使用場景

當我們要監聽某一個資料的變化時,LiveData將大顯身手。例如介面資料的更新,當資料發生變化時,我們要通知介面進行更新ui,這時我們可以使用LiveData在當前Activity/Fragment中對該資料註冊一個觀察者,實時監聽資料的任何改動。每一次改動LiveData都會傳送通知給觀察者。

另一方面,LiveData感知介面的生命週期,所以只有在介面生命週期的STARTED或者RESUMED狀態才會通知觀察者。如果你一直處於後臺且資料一直在變化,LiveData是不會發生通知,只有在介面再一次回到前臺,這時LiveData才會發生通知且只會傳送一次,資料的更新取的是最後一次的變化資料。這樣可以有效的避免記憶體洩露與ui不存在時導致的NullPointerException

使用

首頁我們需要在我們的app下的build.gradle中新增如下依賴程式碼

dependencies {
    def lifecycle_version = "1.1.1"
 
    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternatively - just LiveData
    implementation "android.arch.lifecycle:livedata:$lifecycle_version"
 
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"
}
複製程式碼

然後我們就能正式使用LiveData,看如下程式碼:

class ContactsViewModel(application: Application, private val defTitle: String = "Contacts") : AndroidViewModel(application) {
    val message: MutableLiveData<String> by lazy { MutableLiveData<String>() }
    val contactsList: MutableLiveData<List<ContactsModel>> = MutableLiveData()
     
   fun getContacts(refresh: Boolean): LiveData<List<ContactsModel>> {
        message.value = ""
        if (refresh) {
            getDataFromRemote()
        } else if (contactsList.value == null || contactsList.value?.size ?: 0 <= 0) {
            message.value = "資料請求中,請稍後!"
            if (mLocalData.isEmpty()) {
                getDataFromLocal()
            }
        }
        return contactsList
    }
     
    private fun getDataFromLocal() {
        val runnable = Runnable {
            val dao = mContactsDao.getAllContacts()
            if (dao.isNotEmpty()) {
                contactsList.postValue(dao)
            } else {
                getDataFromRemote()
            }
        }
        mExecutors.disIoExecutor.execute(runnable)
    }
 
    private fun getDataFromRemote() {
        Handler().postDelayed({
            contactsList.value = mRemoteData
            mLocalData = mRemoteData
            saveContacts(mRemoteData)
            Thread(Runnable {
                title.postValue("Remote Contacts")
            }).start()
            message.value = "資料載入完成~"
        }, MDELAY_MILLIS)
    }
}    
複製程式碼

首先我們使用MutableLiveDat對我們所需要的資料進行了包裹,MutableLiveData它繼承與LiveData,暴露了postValue()setValue()方法。一旦MutableLiveData所包裹的資料發生變化,我們可以通過postValue()(asynchronously)與setValue()(synchronously)來設定值與傳送通知,告訴觀察者資料已經改變。

在**getDataFromLocal()**方法中,我們使用了Room來運算元據庫,同時直接通過返回LiveData資料型別的資料,使得Room與LiveData完美結合。

所以我們再來看看觀察者的程式碼:

class ContactsActivity : AppCompatActivity() {
 
    private lateinit var mViewModel: ContactsViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_contacts_layout)
        setupViewModel()
    }
    
    private fun setupViewModel() {
        mViewModel = ViewModelProviders.of(this, ContactsFactory.getInstance(application))[ContactsViewModel::class.java]
        //active STARTED、RESUMED
        mViewModel.getContacts(true).observe(this,
                Observer {
                    //todo ...
                })
        mViewModel.message.observe(this,
                Observer {
                    //todo ...
                })
    }   
}

複製程式碼

我們為所需要觀察的資料新增了observer方法,該方法第一個引數是LifecyleOwner,以便讓LiveData具有生命感知能力,這裡要感知的是ContactsActivity,所以傳入this即可。第二個引數是一個回撥方法,一旦資料發生變化它的**onChanged()**就會回撥,並將資料帶回,這樣介面就能實時更新資料。

最後由於LiveData是生命感知的所以我們也無需擔心他的register/unregister

Extend

我們已經知道LiveData會對處於STATERD或者RESUMED狀態進行傳送通知,如果該狀態下存在observer,由無到有,我們稱之為active,反正稱之為inactive。如果我們能夠知道何時為active與何時為inactive,那麼我們就可以實現自己的LiveData。為了解決這個問題,LiveData提供了兩個方法,分別為onActive()onInactive()

例如我們想為一個監聽器實現生命感知能力,可以進行如下操作

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);
    }
}
複製程式碼

一旦observer由無到有,那麼我們就在**onActive()方法中進行監聽器的註冊。observer由有到無,我們可以在onInactive()中進行登出。這樣就可以是我們的監聽器具備生命感知能力。避免不必要的記憶體洩露或者一次crash。同時一旦監聽器的回撥方法生效時,我們又可以通過LiveData的setValue()**來對觀察者進行資料的更新。所以觀察者的程式碼如下:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}
複製程式碼

如果細心的話,可以發現上面的StockLiveData已經實現了監聽器共享。我們可以在多個介面中使用StockLiveData進行新增observer。例如在Activity中,只要有一個observer,那麼它將一直監聽資料的變化。

案例:對於App統計需求,一旦涉及到多個頁面間的統計引數傳遞,可以自定義一個擴充套件LiveData來全域性監聽引數的傳遞與變化。

Transform

在通知觀察者資料改變之前,如果你想改變LiveData中的值型別,可以使用Transformations

Transformations.map()

獲取原有型別中的某個特定的型別值,可以比喻為解包,可以使用**map()**方法

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

Transformations.switchMap()

與map對應的是**switchMap()**方法,這裡就是打包。

private LiveData<User> getUser(String id) {
  ...;
}
 
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
複製程式碼

MediatorLiveData

與LiveData相關的還有一個MediatorLiveData,它的作用是:可以同時監聽多個LiveData。例如同時監聽本地資料與遠端資料。

LiveData<List<User>> usersFromDatabase;
LiveData<List<User>> usersFromNetwork;
 
MediatorLiveData<List<User>> usersLiveData = 
    new MediatorLiveData<>();
     
usersLiveData.addSource(usersFromDatabase, newUserList ->
    usersLiveData.setValue(value));
     
usersLiveData.addSource(usersFromNetwork, newUserList ->
    usersLiveData.setValue(value));

複製程式碼

一旦其中一個傳送變化,MediatorLiveData都會傳送通知給observer。

是否感覺LiveData很強大呢?那麼趕緊行動起來吧,讓你的App中資料也具有可觀察與生命感知能力。

最後文章中的程式碼都可以在Github中獲取到。使用時請將分支切換到feat_architecture_components

相關文章

Android Architecture Components Part1:Room

關注

私人部落格

Android Architecture Components Part2:LiveData

相關文章