感谢你的再次光临, 欢迎来到 Android Architecture Components(ACC) 系列文章. 上篇文章我们一起讨论了 Room, 通过 Room 我们能够方便的操作 App 的数据库. 如果你的 App 对本地数据库有所依赖的话, Room 你值得拥有.
今天这篇文章继续上篇文章的步伐, 让我们一起来全面了解 ACC 另一强大的组件 LiveData. 相信你马上会喜欢上她!
简述
LiveData 是一种可观测数据容器, 它会在数据变化时通知观测器, 以便更新页面; 同时它具备生命感知能力, 可以实时观察 Activity/Fragment 的生命周期状态.
既然它是可观察数据容器与具备生命感知能力, 那么它的优点也很明显, 可以归纳与以下几点
确保 ui 跟随数据更新
具备生命感知能力从而减少内存泄露
防止异常 crashs
无需管理绑定者的生命周期
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 https://github.com/idisfkj/android-api-analysis 中获取到. 使用时请将分支切换到 feat_architecture_components
来源: https://segmentfault.com/a/1190000015283274