Android Jetpack介绍
Android Jetpack 是一套组件、工具和指导,可以帮助您快速构建出色的 Android 应用。
Google在17年的I/O大会上推出了架构组件(Architecture Component)。
随后在18年I/O大会上发布了 Android Jetpack,Jetpack 是Android开发组件工具集,旨在帮助我们轻松构建更稳定、更健壮、以及更可维护的应用程序。
-
紧接着Google推出AndroidX,将许多Google认为是正确的方案和实践集中起来了。
AndroidX 是对support library的重大改进。
AndroidX中的所有软件包名都以字符串androidx.开头,位于一致的命名空间中。
与support支持库不同,AndroidX中各个组件可单独维护和更新。
所有新的支持库开发都将在AndroidX库中进行。
目前很多组件库新版本都迁移到了androidx。比如Lifecycle2.0.0+、Paging2.0.0+、ViewPager2等,非官方库也积极响应,比如lottie2.8.0+版本之后就使用了androidx实现。由此可以得出,官方在已有组件新版本实现和全新组件的开发都将只支持androidx,所以尽快将自己的项目迁移到androidx吧。
- Jetpack结构图(2018年版本),目前图中大部分组件都推出了稳定的release版本。
Jetpack主要分为4个部分,基础、架构、行为、界面。从图中得知,Jetpack并不全是些新东西,只要是能够帮助开发者更好更方便地构建应用程序的组件,Google都将其归纳入了Jetpack,可以看出Google对jetpack很重视,对开发者很上心。
刚刚结束的Google I/O 2019大会上,Jetpack又迎来了新组件CameraX
、SavedStateViewModel
、Jetpack Compose
等等,提出Kotlin first ,并强调大部分新的Jetpack Apis和功能将会优先提供 Kotlin 版本。参考文章
每个 Jetpack 组件均可单独采用,并且它们可以流畅地协作。Android开发请一定关注和使用Jetpack+Kotlin。
本文重点是在架构组件的使用上,我们先来看看官方推荐的架构实现。
官方推荐架构
使用此架构能带来什么好处?
- UI和业务逻辑解耦。
- 有效避免生命周期组件内存泄漏。
- 提高模块可测试性。
- 提高应用稳定性,有效降低以下异常发生概率。
- Can not perform this action after onSaveInstanceState
- WindowManager$BadTokenException, is your activity running?
- OOM 、 NullPointerException
- ...
测试每个组件
界面和交互:使用 Android 界面插桩测试。基于此架构只需mock 一个
ViewModel
即可完成界面测试。ViewModel:使用 JUnit 测试。只需mock一个类,即
Repository
。Repository:使用 JUnit 测试。只需mock两个类,XxxDao,XxxService;由于XxxDao,XxxService都是接口,还可以创建虚拟实现来完成复杂测试用例。
XxxDao:可以使用插桩测试来测试 DAO 类。这里注意对于每个测试,都请创建内存中数据库以确保测试没有任何副作用(例如更改磁盘上的数据库文件)。
XxxService:就Retrofit而言可以使用MockWebServer模拟本地服务器。
评价一个架构好不好主要看三点:稳定性、易维护、可测试程度。
提到架构组件库,不得不说的是Lifecycle
库。文章后面部分就分别从该库中的Lifecycle、ViewModel、LiveData这三个类来简要分析其实现原理以及使用建议。
Lifecycle
Lifecycle是一个类,它包含组件(Activity或Fragment)生命周期状态的信息,并允许其他对象观察此状态。
那lifecycle是如何跟踪组件生命周期的呢?
上图中states表示组件状态,events表示组件生命周期事件。其实Lifecycle代码内部使用了两个主要枚举(Event、State)来跟踪其关联组件的生命周期状态。
- 其中枚举Event的值和Activity或Fragment组件的生命周期回调事件一一对应。
-
而枚举State则表示被跟踪组件的当前状态,其中
STARTED
和RESUMED
为活跃状态,配合LiveData使用时,只有组件处于活跃状态才能接受到数据更新通知。
实践示例:工具类LifecycleHandler,一个具有生命周期感知的Handler。
LifecycleOwner和LifecycleRegistry
- LifecycleOwner: 是一个单一的方法接口,表示该类具有生命周期。support包从26.1.0版本开始,Fragment和Activity默认实现了该接口,这样直接和LiveData使用就能获取组件的生命周期感知能力。
- LifecycleRegistry: Lifecycle接口的实现类,协助组件处理生命周期,可处理多个观察者。如果你想自定义LifecyclerOwner请参考support包中Fragment和Activity实现。
ViewModel
ViewModel 是用来保存应用UI数据的类,它会在配置变更(Configuration Change)后继续存在。
先看看官方给出的ViewModel生命周期图解
关于ViewModel的生命周期就一句话:在Activity、Fragment等组件整个生命周期过程中,ViewModel的实例有且只有一个。
这样设计好处在哪呢?
可用ViewModel存储数据,它能安全度过手机旋转等配置变更场景。
ViewModel能很好的实现多个Fragment之间的数据共享。
如果界面和业务都比较复杂,ViewModel会不会爆掉?对于这种场景,官方也给出了解决思路:单一责任原则。
上图为官方中文视频截图,从单一责任原则考虑提出了实现建议。
Actvity或Fragment只显示UI和接收互动。
为避免ViewModel臃肿,可创建presenter处理UI数据。
Repository 数据源操作入口。(便于单元测试)
还可配合其它架构组件使用。
关于ViewModel的最佳实践
如何时候都不要将Context传入ViewModel。
如果要在ViewModel中使用Application实例,请使用AndroidViewModel类。
ViewModel+LiveData+Databinding 可构建反应式UI。(请查看文末提供的参考资料)
-
ViewModel与onSaveInstanceState要配合使用。
ViewModel onSaveInstanceState 能度过配置变更 能度过配置变更和进程关闭 能存储大量数据 只可存储少量数据 xx 必须序列化 其实ViewModel和onSaveInstanceState是相辅相成的,当进程被关闭时,ViewModel会被销毁,而onSaveInstanceState不会受影响,所以可用onSaveInstanceState存储少量关键数据(如userId),并在该场景恢复时用来加载页面数据。
在使用ViewModel时,如果页面仅仅是简单的展示数据没什么交互,一个LiveData就能轻松搞定,但实际情况是大多数页面复杂且交互多,就想着怎样更好的处理ViewModel和View之间的通信,直到看到了这篇文章,参考之后得出了下图实现。
ViewModel和View之间通信模型
- UserProfileActivity引用UserViewModel,可观察其提供的UserLiveData、StatusLiveData、PageStateLiveData数据源变更分别处理数据显示、页面loading、跳转等UI操作。
- 注意Activity和ViewModel之间是单向引用。为避免内存泄漏,ViewModel不能持有任何Context引用。
该模型如何响应用户事件的?比如点击某个按钮,需要提交信息给server,并在成功响应后刷新UI,这个过程中ViewModel和View是如何通信的?这里简单描述下该过程,首先是Activity将更新事件传递给ViewModel,ViewModel有将其委托给Presenter处理,Presenter将处理状态和结果,通过给图中指定的LiveData设置数据,liveData就能将新数据回调给Activity,这样页面上所有操作就都能通过数据来驱动了。
另外,如果Activity中存在多个View组件(Fragment),这些组件可直接依赖Activity的ViewModel进行交互。
LiveData
LiveData是一个具有生命周期感知特性的可观察的数据保持类。
- LiveData只通知活跃状态(
STARTED
orRESUMED
)的Observer更新,并在DESTROYED
状态时自动移除Observers,来避免内存泄漏。 - 始终保持最新数据。举例:1.退后台的Activity在返回前台后会立即收到最新数据。2. 配置变更导致Activity重建后也会立即收到最新数据。
- 共享资源。单利模式共享同一个LiveData。
-
SingleLiveEvent 只通知一次事件,适用于Navigation event、SnackBar等等场景。
参考文章: SingleLiveEvent or EventWrapper
LiveData、MutableLiveData、MediatorLiveData三者关系?
- 继承关系:MediatorLiveData -> MutableLiveData -> LiveData。 所以MediatorLiveData功能最强大。
- LiveData 是一个具有生命周期感知的可观察的数据保持类。
- MutableLiveData 在LiveData基础上打开了修改Value的方法权限。
- MediatorLiveData 可管理多个LiveData。
Transformations
用过RxJava的朋友都知道,它可以很方便地在Observable之间转换。LiveData也提供了类似的功能。
-
map : 将一种数据类型的
LiveData<A>
转换为另一种类型的LiveData<B>
// 示例代码:观察将被转换LiveData<User>,待其数据源变更后转换为LiveData<String>并通知订阅者。 LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });
-
switchMap : 和map类似。差别在于triggerLiveData变更后,会触发和等待另外一个LiveData获取数据。
// 示例代码:将addressInputLiveData转换为postalCodeLiveData. 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 } // addressInputLiveData变更时触发repository.getPostCode, // 待其回去成功后,再将数据设置给postalCodeLiveData。 private void setInput(String address) { addressInput.setValue(address); } }
以上为使用示例代码,其内部使用的MediatorLiveData
实现,较简单,感兴趣的朋友请自行查阅源码。
几个问题
LifecycleOwner组件是如何与liveData通信的?
- SupportActivity 通过添加一个空的ReportFragment来处理生命周期状态变更回调;Fragment则在自身生命周期函数中处理。
- LifecycleOwner组件,通过LifecycleRegistry类中handleLifecycleEvent -> dispatchEvent方法与liveData通信,从而使liveData具有感知组件生命周期的能力。
- 组件销毁时,LifecycleRegistry会通知liveData移除observer。
ViewModel如何做到一直在内存中,直到Activity销毁或Fragment被移除时才被清除的?
1.x.x版本实现
- Activity或Fragment会添加一个空的HolderFragment,而ViewModelStore实例被HolderFragment持有,所以就保证了整个生命周期中ViewModelStore实例始终唯一,也就保证了其缓存的ViewModel实例会一直存在直到组件销毁(在onDestroy中会调用ViewModelStore.clear()方法清除其缓存的ViewModel实例)。
- 由于这个HolderFragment设置了setRetainInstance(true), 这样在Activity重建时它不会执行onDestroy回调,这就是它能度过配置变更的原因。
2.x.x版本-对应androidx-fragment-v1.0.0
- Activity
- 缓存:在onRetainNonConfigurationInstance()回调方法中将ViewModelStore实例缓存到NonConfigurationInstances中。
- 恢复:在onCreate中通过getLastNonConfigurationInstance()获取重建前的状态并回复ViewModelStore。
- Fragment
- 缓存:在FragmentActivity.onSaveInstanceState -> FragmentManager.saveAllState -> FragmentManager.saveNonConfig方法中,将ViewModelStore实例缓存到了FragmentManagerNonConfig中,最终通过FragmentActivity将其缓存到NonConfigurationInstances中。
- 恢复:方法调用栈FragmentActivity.onCreate -> FragmentManager.restoreAllState(arg1, nonConfig) -> FragmentState.instantiate(x,x,x,nonConfig, viewModelStore);其中在FragmentState.instantiate(x,x,x,nonConfig, viewModelStore)方法会创建一个新的Fragment并将ViewModelStore变量赋值。
以上结论是我分别从lifecycle库1.x和2.x版本源码分析后得出。关于ViewModel的生命周期实现原理,各个版本实现略有不同,其中lifecycle2.x版本已改用Androidx实现,所以ViewModel的缓存实现还和androidx组件版本有关系。感兴趣的朋友,请自行查阅源码。
当Fragment被detach后再attach回来,会导致添加多个Observer?
-
分析出现的原因
由于Fragment默认实现是在onDestroy才通知liveData 移除observers,而我们每次在onCreateView都会add新的observer实例,这样就会导致数据更新时,LiveData会同时通知多个Observer,界面就会快速刷新多次。
-
解决方案
- 当你在
onActivityCreated
方法中添加LiveData.observer(LifecycleOwner owner, Observer<T> observer)
时,第一个参数使用Fragment.getViewLifecycleOwner()
方法返回值。(如果你没有找到该方法,请更新你依赖的support包版本,Google已在新版本中提供该方法) - 将
LiveData.observer(LifecycleOwner owner, Observer<T> observer)
放在onCreate回调中,在Fragment显示时手动触发数据刷新,当然最好还是更新support版本来解决。
- 当你在
总结
Android Jetpack是一套组件开发工具集,旨在帮助我们轻松构建更稳定、更健壮、以及更可维护的应用程序。对于Google而言,推出Jetpack可以更好的管理和维护组件库;对开发者而言,使用Jetpack可以快速开发出高质量应用,也能看到官方在不同技术方案上的选择,以及新技术发展方向。从目前Jetpack组件布局来看,AndroidX、kotlin是需要我们使用和掌握的。
本文后半部分介绍了架构组件中lifecycle库的一些原理和最佳实践,但还不够全面深入,后面我会一一从源码角度分析各种组件实现原理,敬请关注。
既然是最佳实践,怎么能没有代码,这里分享下作者使用架构组件实现的项目代码,重点关注wanandroid
模块,其实现使用到了ViewModel+LiveData+Lifecycle+Room,按照官方推荐的架构实现,并完成各个组件独立的单元测试。
限于作者水平有限,文中定有错误和疏漏之处,恳请指出,与君共勉;若有不明白之处,欢迎随时评论交流。