前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Architecture Components Part2:LiveData

Android Architecture Components Part2:LiveData

作者头像
Rouse
发布2019-07-16 18:06:52
5190
发布2019-07-16 18:06:52
举报
文章被收录于专栏:Android补给站Android补给站

感谢你的再次光临,欢迎来到Android Architecture Components(AAC)系列文章。上篇文章我们一起讨论了Room,通过Room我们能够方便的操作App的数据库。如果你的App对本地数据库有所依赖的话,Room你值得拥有。

今天这篇文章继续上篇文章的步伐,让我们一起来全面了解AAC另一强大的组件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中添加如下依赖代码

代码语言:javascript
复制
1dependencies {
2    def lifecycle_version = "1.1.1"
3    // ViewModel and LiveData
4    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
5    // alternatively - just LiveData
6    implementation "android.arch.lifecycle:livedata:$lifecycle_version"
7    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"
8}

然后我们就能正式使用LiveData,看如下代码:

代码语言:javascript
复制
 1class ContactsViewModel(application: Application, private val defTitle: String = "Contacts") : AndroidViewModel(application) {
 2    val message: MutableLiveData<String> by lazy { MutableLiveData<String>() }
 3    val contactsList: MutableLiveData<List<ContactsModel>> = MutableLiveData()
 4   fun getContacts(refresh: Boolean): LiveData<List<ContactsModel>> {
 5        message.value = ""
 6        if (refresh) {
 7            getDataFromRemote()
 8        } else if (contactsList.value == null || contactsList.value?.size ?: 0 <= 0) {
 9            message.value = "数据请求中,请稍后!"
10            if (mLocalData.isEmpty()) {
11                getDataFromLocal()
12            }
13        }
14        return contactsList
15    }
16    private fun getDataFromLocal() {
17        val runnable = Runnable {
18            val dao = mContactsDao.getAllContacts()
19            if (dao.isNotEmpty()) {
20                contactsList.postValue(dao)
21            } else {
22                getDataFromRemote()
23            }
24        }
25        mExecutors.disIoExecutor.execute(runnable)
26    }
27    private fun getDataFromRemote() {
28        Handler().postDelayed({
29            contactsList.value = mRemoteData
30            mLocalData = mRemoteData
31            saveContacts(mRemoteData)
32            Thread(Runnable {
33                title.postValue("Remote Contacts")
34            }).start()
35            message.value = "数据加载完成~"
36        }, MDELAY_MILLIS)
37    }
38}    

首先我们使用MutableLiveDat对我们所需要的数据进行了包裹,MutableLiveData它继承与LiveData,暴露了postValue()setValue()方法。一旦MutableLiveData所包裹的数据发生变化,我们可以通过postValue()(asynchronously)与setValue()(synchronously)来设置值与发送通知,告诉观察者数据已经改变。

getDataFromLocal()方法中,我们使用了Room来操作数据库,同时直接通过返回LiveData数据类型的数据,使得Room与LiveData完美结合。

所以我们再来看看观察者的代码:

代码语言:javascript
复制
 1class ContactsActivity : AppCompatActivity() {
 2    private lateinit var mViewModel: ContactsViewModel
 3    override fun onCreate(savedInstanceState: Bundle?) {
 4        super.onCreate(savedInstanceState)
 5        setContentView(R.layout.activity_contacts_layout)
 6        setupViewModel()
 7    }
 8    private fun setupViewModel() {
 9        mViewModel = ViewModelProviders.of(this, ContactsFactory.getInstance(application))[ContactsViewModel::class.java]
10        //active STARTED、RESUMED
11        mViewModel.getContacts(true).observe(this,
12                Observer {
13                    //todo ...
14                })
15        mViewModel.message.observe(this,
16                Observer {
17                    //todo ...
18                })
19    }   
20}

我们为所需要观察的数据添加了observer方法,该方法第一个参数是LifecyleOwner,以便让LiveData具有生命感知能力,这里要感知的是ContactsActivity,所以传入this即可。第二个参数是一个回调方法,一旦数据发生变化它的onChanged()就会回调,并将数据带回,这样界面就能实时更新数据。

最后由于LiveData是生命感知的所以我们也无需担心他的register/unregister

Extend

我们已经知道LiveData会对处于STATERD或者RESUMED状态进行发送通知,如果该状态下存在observer,由无到有,我们称之为active,反正称之为inactive。如果我们能够知道何时为active与何时为inactive,那么我们就可以实现自己的LiveData。为了解决这个问题,LiveData提供了两个方法,分别为onActive()onInactive()

例如我们想为一个监听器实现生命感知能力,可以进行如下操作

代码语言:javascript
复制
 1public class StockLiveData extends LiveData<BigDecimal> {
 2    private static StockLiveData sInstance;
 3    private StockManager mStockManager;
 4    private SimplePriceListener mListener = new SimplePriceListener() {
 5        @Override
 6        public void onPriceChanged(BigDecimal price) {
 7            setValue(price);
 8        }
 9    };
10    @MainThread
11    public static StockLiveData get(String symbol) {
12        if (sInstance == null) {
13            sInstance = new StockLiveData(symbol);
14        }
15        return sInstance;
16    }
17    private StockLiveData(String symbol) {
18        mStockManager = new StockManager(symbol);
19    }
20    @Override
21    protected void onActive() {
22        mStockManager.requestPriceUpdates(mListener);
23    }
24    @Override
25    protected void onInactive() {
26        mStockManager.removeUpdates(mListener);
27    }
28}

一旦observer由无到有,那么我们就在onActive()方法中进行监听器的注册。observer由有到无,我们可以在onInactive()中进行注销。这样就可以是我们的监听器具备生命感知能力。避免不必要的内存泄露或者一次crash。同时一旦监听器的回调方法生效时,我们又可以通过LiveData的setValue()来对观察者进行数据的更新。所以观察者的代码如下:

代码语言:javascript
复制
1public class MyFragment extends Fragment {
2    @Override
3    public void onActivityCreated(Bundle savedInstanceState) {
4        StockLiveData.get(getActivity()).observe(this, price -> {
5            // Update the UI.
6        });
7    }
8}

如果细心的话,可以发现上面的StockLiveData已经实现了监听器共享。我们可以在多个界面中使用StockLiveData进行添加observer。例如在Activity中,只要有一个observer,那么它将一直监听数据的变化。

案例:对于App统计需求,一旦涉及到多个页面间的统计参数传递,可以自定义一个扩展LiveData来全局监听参数的传递与变化。

Transform

在通知观察者数据改变之前,如果你想改变LiveData中的值类型,可以使用Transformations

Transformations.map()

获取原有类型中的某个特定的类型值,可以比喻为解包,可以使用map()方法

代码语言:javascript
复制
1LiveData<User> userLiveData = ...;
2LiveData<String> userName = Transformations.map(userLiveData, user -> {
3    user.name + " " + user.lastName
4});

Transformations.switchMap()

与map对应的是switchMap()方法,这里就是打包。

代码语言:javascript
复制
1private LiveData<User> getUser(String id) {
2  ...;
3}
4LiveData<String> userId = ...;
5LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

MediatorLiveData

与LiveData相关的还有一个MediatorLiveData,它的作用是:可以同时监听多个LiveData。例如同时监听本地数据与远程数据。

代码语言:javascript
复制
1LiveData<List<User>> usersFromDatabase;
2LiveData<List<User>> usersFromNetwork;
3MediatorLiveData<List<User>> usersLiveData = 
4    new MediatorLiveData<>();
5usersLiveData.addSource(usersFromDatabase, newUserList ->
6    usersLiveData.setValue(value));
7usersLiveData.addSource(usersFromNetwork, newUserList ->
8    usersLiveData.setValue(value));

一旦其中一个发送变化,MediatorLiveData都会发送通知给observer。

是否感觉LiveData很强大呢?那么赶紧行动起来吧,让你的App中数据也具有可观察与生命感知能力。

最后文章中的代码都可以在Github中获取到。使用时请将分支切换到

feat_architecture_components

项目地址:

https://github.com/idisfkj/android-api-analysis

相关文章

Android Architecture Components Part1:Room

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android补给站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简述
  • 使用场景
  • 使用
  • Extend
  • Transform
  • MediatorLiveData
  • 相关文章
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档