Jetpack 成员 Paging3 数据库实践以及源码分析(一)
- 如果评论区没有及时回复,欢迎来公众号:ByteCode 咨询
- 公众号:ByteCode。致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、算法、译文、系统源码相关的文章
前言
前几天 Google 更新了几个 Jetpack 新成员 Hilt、Paging 3、App Startup 等等,在之前的文章里面分了 App Startup 是什么、App Startup 为我们解决了什么问题,如果之前没有看过可以点击下面连接前往查看文章和代码。
- Jetpack 最新成员 AndroidX App Startup 实践以及原理分析
- AppStartup 代码地址:https://github.com/hi-dhl/AndroidX-Jetpack-Practice
今天这边文章主要来分析 Paging3,Paging3 会分为三篇文章,详细的分析其原理,每篇文章都有完整的项目示例。
- Jetpack 成员 Paging3 数据库实践以及源码分析(一)
- Jetpack 成员 Paging3 网络实践及原理分析(二)
- Jetpack 成员 Paging3 使用 RemoteMediator 实现加载网络分页数据并更新到数据库中(三)
通过这篇文章你将学习到以下内容:
- Paging3 是什么?
- Paging3 在项目中的架构以及类的职能源码分析?
- 如何在项目中正确使用 Paging3?
- 数据映射(Data Mapper)是什么?
- Kotlin Flow 是什么?
在分析之前我们先来了解一下本文实战项目中用到的技术:
使用 Koin 作为依赖注入,可以看我之前写的篇文章:[译][2.4K Star] 放弃 Dagger 拥抱 Koin。
使用 Composing builds 作为依赖库的版本管理,可以看我之前写篇文章:再见吧 buildSrc, 拥抱 Composing builds 提升 Android 编译速度。
JDataBinding 是我基于 DataBinding 封装的库,可以看我之前写篇文章:项目中封装 Kotlin + Android Databinding。
数据映射(Data Mapper): 将数据源的实体,转换为上层用到的 model,在项目中起到了很大重要,我看了很多项目的,这个概念很少被提及到,看国外的大牛的写的文章时,它们提及到了这个概念,后面会对它详细的分析。
项目中用到了一些 Kotlin 技巧,可以查看我另外一篇文章:为数不多的人知道的 Kotlin 技巧以及 原理解析。
还有 Paging 3、Room、Anko、Repository 设计模式、MVVM 架构等等。
Paging3 是什么?
Paging 是一个分页库,它可以帮助您从本地存储或通过网络加载显示数据。这种方法使你的 App 更有效地使用网络带宽和系统资源。
Paging3 是使用 Kotlin 协程完全重写的库,经历了从 Paging1x 到 Paging2x 在到现在的 Paging3,深刻领悟到 Paging3 比 Paging1 和 Paging2 真的方便了很多。
Google 推荐使用 Paging 作为 App 架构的一部分,它可以很方便的和 Jetpack 组件集成,Paging3 包含了以下功能:
- 在内存中缓存分页数据,确保您的 App 在使用分页数据时有效地使用系统资源。
- 内置删除重复数据的请求,确保您的 App 有效地使用网络带宽和系统资源。
- 可配置 RecyclerView 的 adapters,当用户滚动到加载数据的末尾时自动请求数据。
- 支持 Kotlin 协程和 Flow, 以及 LiveData 和 RxJava。
- 内置的错误处理支持,包括刷新和重试等功能。
Paging3 的架构以及类的职能源码分析
Google 推荐我们使用 Paging3 时,在应用程序的三层中操作,以及它们如何协同工作加载和显示分页数据,如下图所示:
但是我个人认为应该在增加一层 Data Mapper (下面会有详细的介绍),如下图所示:
数据映射(Data Mapper)将数据源的实体,转换为上层用到的 model,往往会被我们忽略掉,但是在项目中起到了很大重要,我看了很多项目的,这个概念很少被提及到,我只在国外的大牛的写的文章中,它们提及到了这个概念。关于数据映射(Data Mapper) 后面会单独写一篇文章,配合 Demo 去验证,这里只是简单提及一下。
Data Mapper
在一个快速开发的项目中,为了越快完成第一个版本交付,下意识的将数据源和 UI 绑定到一起,当业务逐渐增多,数据源变化了,上层也要一起变化,导致后期的重构工作量很大,核心的原因耦合性太强了。
使用数据映射(Data Mapper)优点如下:
- 数据源的更改不会影响上层的业务。
- 糟糕的后端实现不会影响上层的业务 (想象一下,如果你被迫执行2个网络请求,因为后端不能在一个请求中提供你需要的所有信息,你会让这个问题影响你的整个代码吗)。
- Data Mapper 便于做单元测试,确保不会因为数据源的变化,而影响上层的业务。
- 在本文案例项目 Paging3Simple 中会用到 Data Mapper 作为数据映射,在代码中有详细的注释。
Repository layer
在 Repository layer 中的主要使用 Paging3 组件中的 PagingSource,每个 PagingSource 对象定义一个数据源以及如何从该数据源查找数据, PagingSource 对象可以从任何一个数据源加载数据,包括网络数据和本地数据。
PagingSource 是一个抽象类,其中有两个重要的方法 load 和 和 getRefreshKey,load 方法如下所示:
abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> |
这是一个挂起函数,实现这个方法来触发异步加载,另外一个 getRefreshKey 方法
open fun getRefreshKey(state: PagingState<Key, Value>): Key? = null |
该方法只在初始加载成功且加载页面的列表不为空的情况下被调用。
在这一层中还有另外一个 Paging3 的组件 RemoteMediator,RemoteMediator 对象处理来自分层数据源的分页,例如具有本地数据库缓存的网络数据源。
ViewModel layer
在 ViewModel layer 层主要用到了 Paging3 的组件 Pager,Pager 是主要的入口页面,在其构造方法中接受 PagingConfig、initialKey、remoteMediator、pagingSourceFactory,代码如下所示:
class Pager<Key : Any, Value : Any> |
今天这篇文章和项目主要用到了 PagingConfig 和 PagingSource,PagingSource 上面已经说过了,所以我们主要来分一下 PagingConfig。
val pagingConfig = PagingConfig( |
将 ViewModel 层连接到 UI 层用到了 Paging3 的组件 PagingData,PagingData 对象是分页数据的容器,它查询一个 PagingSource 对象并存储结果。
Google 推荐我们将组件 Pager 放到 ViewModel layer,但是我更喜欢放到 Repository layer,详见下文。
UI layer
在 UI layer 中的主要到了 Paging3 的组件 PagingDataAdapter,PagingDataAdapter 是一个处理分页数据的可回收视图适配器,您可以使用 AsyncPagingDataDiffer 组件来构建自己的自定义适配器,本文中用到是 PagingDataAdapter。
Paging 3 如何在项目中使用
在 App 模块中的 build.gradle 文件中添加以下代码:
dependencies { |
接下来我将按照上面说的每层去实现,首先我们先来看一下项目的结构。
- bean: 存放上层需要的 model,会和 RecyclerView 的 Adapter 绑定在一起。
- loca: 存放和本地数据库相关的操作。
- mapper: 数据映射,主要将数据源的实体 转成上层的 model。
- repository:主要来处理和数据源相关的操作(本地、网络、内存中缓存等等)。
- di: 和依赖注入相关。
- ui:数据的展示。
数据库部分
@Dao |
关于 Dao 这里需要解释一下, queryAllData 方法返回了一个 PagingSource,后面会通过 Pager 转换成 flow<PagingData<Value>>
。
Repository 部分
通过 Koin 注入 RepositoryFactory,通过 RepositoryFactory 管理相关的 Repository,RepositoryFactory 代码如下:
class RepositoryFactory(val appDataBase: AppDataBase) { |
这里主要是生成 PagingConfig 和 Data Mapper 然后传递给 PersonRepositoryImpl,我们来看一下 PersonRepositoryImpl 相关代码。
class PersonRepositoryImpl( |
Pager 是主要的入口页面,在其构造方法中接受 PagingConfig、pagingSourceFactory。
pagingSourceFactory: () -> PagingSource<Key, Value> |
pagingSourceFactory 是一个 lambda 表达式,在 Kotlin 中可以直接用花括号表示,在花括号内,执行加载数据库的数据的请求。
最后调用 flow 返回 Flow<PagingData<Value>>
,然后通过 Flow 的 map 将数据库实体 PersonEntity 转换成上层用到的实体 Person。
Flow 库是在 Kotlin Coroutines 1.3.2 发布之后新增的库,也叫做异步流,类似 RxJava 的 Observable,本文主要用到了 Flow 当中的 map 方法进行数据转换,简单实例如下所示:
flow{ |
到这里我们在回过去看,项目中 pagingData.map { mapper2Person.map(it) }
这行代码,其中 mapper2Person 是我们自己实现的 Data Mapper,代码如下所示:
class PersonEntity2PersonMapper : Mapper<PersonEntity, Person> { |
数据库实体 PersonEntity 转换为 上层用到的实体 Person。
UI 部分
通过 koin 依赖注入 MainViewModel,并传递参数 Repository。
class MainViewModel(val repository: Repository) : ViewModel() { |
在 Activity 当中注册 observe,并将数据绑定给 Adapter,如下所示:
mMainViewModel.pageDataLiveData3.observe(this, Observer { data -> |
知识扩充
刚才我们调用了 asLiveData 方法转为 LiveData,其实还有两种方法(作为了解即可)。
方法一
在 LifeCycle 2.2.0 之前使用的方法,使用两个 LiveData,一个是可变的,一个是不可变的,如下所示:
// 私有的 MutableLiveData 可变的,对内访问 |
- 准备一私有的 MutableLiveData,只对内访问。
- 对外暴露不可变的 LiveData。
- 将值赋值给 _pageDataLiveData。
方法二
在 LifeCycle 2.2.0 之后,可以用更精简的方法来完成,使用 LiveData 协程构造方法 (coroutine builder)。
val pageDataLiveData2 = liveData { |
liveData 协程构造方法提供了一个协程代码块,产生的是一个不可变的 LiveData,emit() 方法则用来更新 LiveData 的数据。
最后添加左右滑动删除功能
调用 recyclerview 封装好的 ItemTouchHelper 实现 左右滑动删除 item 功能。
private fun initSwipeToDelete() { |
关于 Paging 加载本地数据到这里就结束了,我们将在下一篇文章讲解如何加载网络数据,最后上一个效果图。
总结
这篇文章主要介绍了以下内容:
Paging3 是什么以及它的优点
Paging 是一个分页库,它可以帮助您从本地存储或通过网络加载和显示数据。这种方法使你的 App 更有效地使用网络带宽和系统资源,而 Paging3 是使用 Kotlin 协程完全重写的库:
- 在内存中缓存分页数据,确保您的 App 在使用分页数据时有效地使用系统资源。
- 内置删除重复数据的请求,确保您的 App 有效地使用网络带宽和系统资源。
- 可配置 RecyclerView 的 adapters,当用户滚动到加载数据的末尾时自动请求数据。
- 支持 Kotlin 协程和 Flow, 以及 LiveData 和 RxJava。
- 内置的错误处理支持,包括刷新和重试功能。
Paging3 的架构以及类的职能源码分析
- PagingSource:每个 PagingSource 对象定义一个数据源以及如何从该数据源查找数据。
- RemoteMediator:RemoteMediator 对象处理来自分层数据源的分页,例如具有本地数据库缓存的网络数据源。
- Pager:是主要的入口页面,在其构造方法中接受 PagingConfig、initialKey、remoteMediator、pagingSourceFactory。
- PagingDataAdapter:是一个处理分页数据的可回收视图适配器,您可以使用 AsyncPagingDataDiffer 组件来构建自己的自定义适配器。
数据映射(Data Mapper)
数据映射(Data Mapper)将数据源的实体,转换为上层用到的 model,往往会被我们忽略掉的,但是在项目中起到了很大重要,使用 数据映射(Data Mapper)优点如下:
- 数据源的更改不会影响上层的业务。
- 糟糕的后端实现不会影响上层的业务 (想象一下,如果你被迫执行2个网络请求,因为后端不能在一个请求中提供你需要的所有信息,你会让这个问题影响你的整个代码吗)。
- Data Mapper 便于做单元测试,确保不会因为数据源的变化,而影响上层的业务。
- 在本文案例项目 Paging3Simple 中会用到 Data Mapper 作为数据映射。
Kotlin Flow
Flow 库是在 Kotlin Coroutines 1.3.2 发布之后新增的库,也叫做异步流,类似 RxJava 的 Observable,本文主要用到了 flow 当中的 map 方法进行数据转换,如下面的例子所示:
flow{ |
到这里我相信应该理解了,项目中 pagingData.map { mapper2Person.map(it) }
这行代码的意思了。
GitHub 地址:https://github.com/hi-dhl/AndroidX-Jetpack-Practice
正在建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请在仓库右上角帮我点个赞,后面我会陆续完成更多 Jetpack 新成员的项目实践
- 本文作者:hi-dhl
- 本文标题:Jetpack 成员 Paging3 数据库实践以及源码分析(一)
- 本文链接:https://hi-dhl.com/2020/08/22/jetpack/02-Paging3/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 hi-dhl