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 版本.
Android-jetpack.PNG
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 是如何跟踪组件生命周期的呢?
lifecycle-states
上图中 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-lifecycle.PNG
关于 ViewModel 的生命周期就一句话: 在 Activity,Fragment 等组件整个生命周期过程中, ViewModel 的实例有且只有一个.
这样设计好处在哪呢?
可用 ViewModel 存储数据, 它能安全度过手机旋转等配置变更场景.
ViewModel 能很好的实现多个 Fragment 之间的数据共享.
如果界面和业务都比较复杂, ViewModel 会不会爆掉? 对于这种场景, 官方也给出了解决思路: 单一责任原则.
viewmodel_duty
上图为官方中文视频截图, 从单一责任原则考虑提出了实现建议.
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 之间通信模型
Communication between ViewModel and View
UserProfileActivity 引用 UserViewModel, 可观察其提供的 UserLiveData,StatusLiveData,PageStateLiveData 数据源变更分别处理数据显示, 页面 loading, 跳转等 UI 操作.
注意 Activity 和 ViewModel 之间是单向引用. 为避免内存泄漏, ViewModel 不能持有任何 Context 引用.
该模型如何响应用户事件的? 比如点击某个按钮, 需要提交信息给 server, 并在成功响应后刷新 UI, 这个过程中 ViewModel 和 View 是如何通信的? 这里简单描述下该过程, 首先是 Activity 将更新事件传递给 ViewModel,ViewModel 有将其委托给 Presenter 处理, Presenter 将处理状态和结果, 通过给图中指定的 LiveData 设置数据, liveData 就能将新数据回调给 Activity, 这样页面上所有操作就都能通过数据来驱动了.
LiveData
LiveData 是一个具有生命周期感知特性的可观察的数据保持类.
LiveData 只通知活跃状态 ( STARTED or RESUMED ) 的 Observer 更新, 并在 DESTROYED 状态时自动移除 Observers, 来避免内存泄漏.
始终保持最新数据. 举例: 1. 退后台的 Activity 在返回前台后会立即收到最新数据. 2. 配置变更导致 Activity 重建后也会立即收到最新数据.
共享资源. 单利模式共享同一个 LiveData.
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 -> Fragment.saveAllState -> Fragment.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, 按照官方推荐的架构实现, 并完成各个组件独立的单元测试.
image.PNG
限于作者水平有限, 文中定有错误和疏漏之处, 恳请指出, 与君共勉; 若有不明白之处, 欢迎随时评论交流, 也可加我个人微信(jiantaocd), 记得填写备注哦.
参考资料
Jetpack 官方文档
架构组件官方文档
Android-architecture-communication-between-viewmodel-and-view
Android Jetpack: ViewModel | 中文教学视频
[官方] 使用 Android Jetpack 加快应用开发速度
[官方] YouTube-Jetpack 系列视频
- [samples] Android-sunflower
- [samples] Android-architecture-components
来源: http://www.jianshu.com/p/4ad7aa0fc356