Jetpack入门

Android官方最为推荐的项目架构就是MVVM,因而Jetpack中的许多架构组件是专门为MVVM架构量身打造的,我们关注的其实还是架构组件。
一:ViewModel
传统的开发模式下,Activity的任务实在是太重了,既要负责逻辑处理,又要控制UI展示,甚至还得处理网络回调,那么这个项目将会变得非常臃肿并且难以维护,因为没有任何架构上的划分.
而ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑。
另外,ViewModel还有一个非常重要的特性,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。
1. ViewModel的基本用法
比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel。
这里最需要注意的是,我们绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例。之所以要这么写,是因为ViewModel有其独立的生命周期,并且其生命周期要长于Activity。如果我们在onCreate()方法中创建ViewModel的实例,那么每次onCreate()方法执行的时候,ViewModel都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了。
在这里插入图片描述
2.向ViewModel传递参数
由于所有ViewModel的实例都是通过ViewModelProvider来获取的,因此我们没有任何地方可以向ViewModel的构造函数中传递参数。当然,这个问题也不难解决,只需要借助ViewModelProvider.Factory就可以实现了。
在这里插入图片描述
现在我们给MainViewModel的构造函数添加了一个countReserved参数,接下来的问题就是如何向MainViewModel的构造函数传递数据了,前面已经说了需要借助ViewModelProvider.Factory。
在这里插入图片描述
因为create()方法的执行时机和Activity的生命周期无关,所以不会产生之前提到的问题(onCreat时重新创建实列的问题)。
二:Lifecycles
在一个Activity中去感知它的生命周期非常简单,而如果要在一个非Activity的类中去感知Activity的生命周期,应该怎么办呢?这种需求是广泛存在的,同时也衍生出了一系列的解决方案,比如通过在Activity中嵌入一个隐藏的Fragment来进行感知,或者通过手写监听器的方式来进行感知,等等。
下面的代码演示了如何通过手写监听器的方式来对Activity的生命周期进行感知
在这里插入图片描述
这里我们为了让MyObserver能够感知到Activity的生命周期,需要专门在MainActivity中重写相应的生命周期方法,然后再通知给MyObserver。这种实现方式虽然是可以正常工作的,但是不够优雅,需要在Activity中编写太多额外的逻辑
这里我们为了让MyObserver能够感知到Activity的生命周期,需要专门在MainActivity中重写相应的生命周期方法,然后再通知给MyObserver。这种实现方式虽然是可以正常工作的,但是不够优雅,需要在Activity中编写太多额外的逻辑。
在这里插入图片描述
LifecycleObserver是一个空方法接口,只需要进行一下接口实现声明就可以了,而不去重写任何方法。
接下来我们可以在MyObserver中定义任何方法,但是如果想要感知到Activity的生命周期,还得借助额外的注解功能才行。
生命周期事件的类型一共有7种:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP和ON_DESTROY分别匹配Activity中相应的生命周期回调;另外还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期回调。
当Activity的生命周期发生变化的时候并没有人去通知MyObserver,这个时候就得借助LifecycleOwner这个好帮手了。
在这里插入图片描述
首先调用LifecycleOwner的getLifecycle()方法,得到一个Lifecycle对象,然后调用它的addObserver()方法来观察LifecycleOwner的生命周期,再把MyObserver的实例传进去就可以了。
不过目前MyObserver虽然能够感知到Activity的生命周期发生了变化,却没有办法主动获知当前的生命周期状态。要解决这个问题也不难,只需要在MyObserver的构造函数中将Lifecycle对象传进来即可,如下所示:
在这里插入图片描述
有了Lifecycle对象之后,我们就可以在任何地方调用lifecycle.currentState来主动获知当前的生命周期状态。lifecycle.currentState返回的生命周期状态是一个枚举类型,一共有INITIALIZED、DESTROYED、CREATED、STARTED、RESUMED这5种状态类型,它们与Activity的生命周期回调所对应的关系如图
在这里插入图片描述
三:LiveData
Lifecycles组件的重要内容就基本讲完了,目前我们已经掌握了Jetpack中的两个重要组件。但是这两个组件是相对比较独立的,并没有太多直接的关系。为了让各个组件可以更好地结合使用,接下来我们就开始学习Jetpack中另外一个非常重要的组件——LiveData。
1.LiveData的基本用法
原来我们一直使用的都是在Activity中手动获取ViewModel中的数据这种交互方式,但是ViewModel却无法将数据的变化主动通知给Activity。
或许你会说,我把Activity的实例传给ViewModel,这样ViewModel不就能主动对Activity进行通知了吗?注意,千万不可以这么做。不要忘了,ViewModel的生命周期是长于Activity的,如果把Activity的实例传给ViewModel,就很有可能会因为Activity无法释放而造成内存泄漏,这是一种非常错误的做法。
而这个问题的解决方案也是显而易见的,就是使用我们本节即将学习的LiveData。正如前面所描述的一样,LiveData可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。也就是说,如果我们将计数器的计数使用LiveData来包装,然后在Activity中去观察它,就可以主动将数据变化通知给Activity了。
在这里插入图片描述
这里我们将counter变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表示它包含的是整型数据。MutableLiveData是一种可变的LiveData,它的用法很简单,主要有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法。getValue()方法用于获取LiveData中包含的数据;setValue()方法用于给LiveData设置数据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据。而上述代码其实就是调用getValue()和setValue()方法对应的语法糖写法。

在这里插入图片描述
接下来到最关键的地方了,这里调用了viewModel.counter的observe()方法来观察数据的变化。经过对MainViewModel的改造,现在counter变量已经变成了一个LiveData对象,任何LiveData对象都可以调用它的observe()方法来观察数据的变化。observe()方法接收两个参数:第一个参数是一个LifecycleOwner对象,有没有觉得很熟悉?没错,Activity本身就是一个LifecycleOwner对象,因此直接传this就好;第二个参数是一个Observer接口,当counter中包含的数据发生变化时,就会回调到这里,因此我们在这里将最新的计数更新到界面上即可。
重新运行一下程序,你会发现,计数器功能同样是可以正常工作的。不同的是,现在我们的代码更科学,也更合理,而且不用担心ViewModel的内部会不会开启线程执行耗时逻辑。不过需要注意的是,如果你需要在子线程中给LiveData设置数据,一定要调用postValue()方法,而不能再使用setValue()方法,否则会发生崩溃。
比较推荐的做法是,永远只暴露不可变的LiveData给外部。这样在非ViewModel中就只能观察LiveData的数据变化,而不能给LiveData设置数据。
在这里插入图片描述
可以看到,这里先将原来的counter变量改名为_counter变量,并给它加上private修饰符,这样_counter变量对于外部就是不可见的了。然后我们又新定义了一个counter变量,将它的类型声明为不可变的LiveData,并在它的get()属性方法中返回_counter变量。这样,当外部调用counter变量时,实际上获得的就是_counter的实例,但是无法给counter设置数据,从而保证了ViewModel的数据封装性。
2.map和switchMap
LiveData的基本用法虽说可以满足大部分的开发需求,但是当项目变得复杂之后,可能会出现一些更加特殊的需求。LiveData为了能够应对各种不同的需求场景,提供了两种转换方法:map()和switchMap()方法。下面我们就学习这两种转换方法的具体用法和使用场景。
先来看map()方法,这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换。那么什么情况下会用到这个方法呢?下面我来举一个例子。比如说有一个User类,User中包含用户的姓名和年龄,我们可以在ViewModel中创建一个相应的LiveData来包含User类型的数据,可是如果MainActivity中明确只会显示用户的姓名,而完全不关心用户的年龄,那么这个时候还将整个User类型的LiveData暴露给外部,就显得不那么合适了。而map()方法就是专门用于解决这种问题的,它可以将User类型的LiveData自由地转型成任意其他类型的LiveData。
在这里插入图片描述
可以看到,这里我们调用了Transformations的map()方法来对LiveData的数据类型进行转换。map()方法接收两个参数:第一个参数是原始的LiveData对象;第二个参数是一个转换函数,我们在转换函数里编写具体的转换逻辑即可。这里的逻辑也很简单,就是将User对象转换成一个只包含用户姓名的字符串。另外,我们还将userLiveData声明成了private,以保证数据的封装性。外部使用的时候只要观察userName这个LiveData就可以了。当userLiveData的数据发生变化时,map()方法会监听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userName的观察者。
接下来,我们开始学习switchMap()方法,虽然它的使用场景非常固定,但是可能比map()方法要更加常用。
前面我们所学的所有内容都有一个前提:LiveData对象的实例都是在ViewModel中创建的。然而在实际的项目中,不可能一直是这种理想情况,很有可能ViewModel中的某个LiveData对象是调用另外的方法获取的。
下面就来模拟一下这种情况,新建一个Repository单例类,代码如下所示
在这里插入图片描述
需要注意的是,getUser()方法返回的是一个包含User数据的LiveData对象,而且每次调用getUser()方法都会返回一个新的LiveData实例。
然后我们在MainViewModel中也定义一个getUser()方法,并且让它调用Repository的getUser()方法来获取LiveData对象:
在这里插入图片描述
接下来的问题就是,在Activity中如何观察LiveData的数据变化呢?既然getUser()方法返回的就是一个LiveData对象,那么我们可不可以直接在Activity中使用如下写法呢?
在这里插入图片描述
请注意,这么做是完全错误的。因为每次调用getUser()方法返回的都是一个新的LiveData实例,而上述写法会一直观察老的LiveData实例,从而根本无法观察到数据的变化。你会发现,这种情况下的LiveData是不可观察的。
这个时候,switchMap()方法就可以派上用场了。正如前面所说,它的使用场景非常固定:如果ViewModel中的某个LiveData对象是调用另外的方法获取的,那么我们就可以借助switchMap()方法,将这个LiveData对象转换成另外一个可观察的LiveData对象。
在这里插入图片描述
这里我们定义了一个新的userIdLiveData对象,用来观察userId的数据变化,然后调用了Transformations的switchMap()方法,用来对另一个可观察的LiveData对象进行转换。
switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,switchMap()方法会对它进行观察;第二个参数是一个转换函数,注意,我们必须在这个转换函数中返回一个LiveData对象,因为switchMap()方法的工作原理就是要将转换函数中返回的LiveData对象转换成另一个可观察的LiveData对象。那么很显然,我们只需要在转换函数中调用Repository的getUser()方法来得到LiveData对象,并将它返回就可以了。
为了让你能更清晰地理解switchMap()的用法,我们再来梳理一遍它的整体工作流程。首先,当外部调用MainViewModel的getUser()方法来获取用户数据时,并不会发起任何请求或者函数调用,只会将传入的userId值设置到userIdLiveData当中。一旦userIdLiveData的数据发生变化,那么观察userIdLiveData的switchMap()方法就会执行,并且调用我们编写的转换函数。然后在转换函数中调用Repository.getUser()方法获取真正的用户数据。同时,switchMap()方法会将Repository.getUser()方法返回的LiveData对象转换成一个可观察的LiveData对象,对于Activity而言,只要去观察这个LiveData对象就可以了。
最后再介绍一个我当初学习switchMap()方法时产生疑惑的地方。在刚才的例子当中,我们调用MainViewModel的getUser()方法时传入了一个userId参数,为了能够观察这个参数的数据变化,又构建了一个userIdLiveData,然后在switchMap()方法中再去观察这个LiveData对象就可以了。但是ViewModel中某个获取数据的方法有可能是没有参数的,这个时候代码应该怎么写呢?
其实这个问题并没有想象中复杂,写法基本上和原来是相同的,只是在没有可观察数据的情况下,我们需要创建一个空的LiveData对象,示例写法如下:在这里插入图片描述
可以看到,这里我们定义了一个不带参数的refresh()方法,又对应地定义了一个refreshLiveData,但是它不需要指定具体包含的数据类型,因此这里我们将LiveData的泛型指定成Any?即可。接下来就是点睛之笔的地方了,在refresh()方法中,我们只是将refreshLiveData原有的数据取出来(默认是空),再重新设置到refreshLiveData当中,这样就能触发一次数据变化。是的,LiveData内部不会判断即将设置的数据和原有数据是否相同,只要调用了setValue()或postValue()方法,就一定会触发数据变化事件。然后我们在Activity中观察refreshResult这个LiveData对象即可,这样只要调用了refresh()方法,观察者的回调函数中就能够得到最新的数据。
可能你会说,学到现在,只看到了LiveData与ViewModel结合在一起使用,好像和我们上一节学的Lifecycles组件没什么关系嘛。其实并不是这样的,LiveData之所以能够成为Activity与ViewModel之间通信的桥梁,并且还不会有内存泄漏的风险,靠的就是Lifecycles组件。LiveData在内部使用了Lifecycles组件来自我感知生命周期的变化,从而可以在Activity销毁的时候及时释放引用,避免产生内存泄漏的问题。
另外,由于要减少性能消耗,当Activity处于不可见状态的时候(比如手机息屏,或者被其他的Activity遮挡),如果LiveData中的数据发生了变化,是不会通知给观察者的。只有当Activity重新恢复可见状态时,才会将数据通知给观察者,而LiveData之所以能够实现这种细节的优化,依靠的还是Lifecycles组件。
还有一个小细节,如果在Activity处于不可见状态的时候,LiveData发生了多次数据变化,当Activity恢复可见状态时,只有最新的那份数据才会通知给观察者,前面的数据在这种情况下相当于已经过期了,会被直接丢弃。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值