1、介绍

你要建造什么

在这个代码库中,您从一个示例应用程序开始,该应用程序已经显示了GitHub存储库列表,从数据库加载数据并且由网络数据支持。 只要用户滚动并到达显示列表的末尾,就会触发新的网络请求,并将其结果保存在数据库中。

您将通过一系列步骤添加代码,在您进行时集成Paging库组件。 这些组件在步骤2中描述。

你需要什么

Android Studio 3.0或更高版本。

要熟悉以下架构组件:Room,LiveData,ViewModel以及“应用程序架构指南”中建议的架构。

2、设置您的环境

您还可以在GitHub上查看codelab。 初始状态位于主分支上,并在解决方案分支上查看解决方案。

3、分页库组件

分页库使您可以更轻松地在应用程序的UI中逐步和优雅地加载数据。

应用程序架构指南提出了一个具有以下主要组件的架构:

本地数据库,作为呈现给用户的数据的单一事实来源以及用户为更改该数据而采取的操作。

Web API服务

与数据库和API服务一起使用的存储库,提供统一的数据接口

ViewModel,提供特定于UI的数据

UI,显示ViewModel中数据的直观表示

Paging库可以处理所有这些组件并协调它们之间的交互,以便它可以从数据源中分页内容并在UI中显示该内容。

此代码库向您介绍了Paging库及其主要组件:

PagedList - 以异步方式在页面中加载数据的集合。 PagedList可用于从您定义的源加载数据,并使用RecyclerView在UI中轻松呈现。

DataSource和DataSource.Factory - DataSource是将数据快照加载到PagedList的基类。 DataSource.Factory负责创建DataSource。

LivePagedListBuilder - 基于DataSource.Factory和PagedList.Config构建LiveData 。

BoundaryCallback - 当PagedList到达可用数据的末尾时发出信号。

PagedListAdapter - 一个RecyclerView.Adapter,它在RecyclerView中显示来自PagedLists的分页数据。 PagedListAdapter在加载页面时监听PagedList加载回调,并在收到新的PagedLists时使用DiffUtil计算细粒度更新。

在此代码框中,您可以实现上述每个组件的示例。

4、工程概况

该应用程序允许您在GitHub中搜索其名称或描述包含特定单词的存储库。 将显示存储库列表,按降序排列,基于星号,然后按名称显示。 数据库是UI显示的数据的真实来源,它由网络请求支持。

按名称,通过RepoDao.reposByName中的LiveData对象检索存储库列表。 每当来自网络的新数据插入数据库时,LiveData将再次使用查询的整个结果发出。

当前实现有两个内存/性能问题:

一次加载数据库的整个repo表。

数据库的整个结果列表保存在内存中。

该应用程序遵循“应用程序架构指南”中推荐的体系结构,使用Room作为本地数据存储。 以下是每个包装中的内容:

api - 使用Retrofit包含Github API调用

db - 网络数据的数据库缓存

data - 包含存储库类,负责触发API请求并在数据库中保存响应

ui - 包含与使用RecyclerView显示活动相关的类

model - 包含Repo数据模型,它也是Room数据库中的一个表; 和

RepoSearchResult,UI用于观察搜索结果数据和网络错误的类

注意:GithubRepository和Repo类具有相似的名称,但用途却截然不同。 存储库类GithubRepository与代表GitHub代码存储库的Repo数据对象一起使用。

5、使用PagedList以块的形式加载数据

在我们当前的实现中,我们使用LiveData >从数据库中获取数据并将其传递给UI。 每当修改本地数据库中的数据时,LiveData都会发出更新的列表。 List 的替代方案是PagedList 。 PagedList是List的一个版本,它以块的形式加载内容。 与List类似,PagedList包含内容的快照,因此当通过LiveData传递PagedList的新实例时会发生更新。

创建PagedList时,它会立即加载第一个数据块,并在将来加载了内容时会再次扩展,。 PagedList的大小是每次传递期间加载的项目数。 该类支持无限列表和具有固定数量元素的非常大的列表。

用PagedList 替换List 的出现次数:

RepoSearchResult是UI用于显示数据的数据模型。 由于数据不再是LiveData >而是分页,因此需要将其替换为LiveData >。 在RepoSearchResult类中进行此更改。

SearchRepositoriesViewModel使用来自GithubRepository的数据。 更改ViewModel公开的repos val的类型,从LiveData >更改为LiveData >。

SearchRepositoriesActivity从ViewModel观察repos。 将List 中的观察者类型更改为PagedList 。

viewModel.repos.observe(this, Observer> {

showEmptyList(it?.size == 0)

adapter.submitList(it)

})

6、定义分页列表的数据源

agedList从源动态加载内容。 在我们的例子中,因为数据库是UI的主要真实来源,它也代表了PagedList的来源。 如果您的应用直接从网络获取数据并在没有缓存的情况下显示数据,则发出网络请求的类将成为您的数据源。

源由DataSource类定义。 要从可以更改的源(例如允许插入,删除或更新数据的源)中分页数据,您还需要实现知道如何创建DataSource的DataSource.Factory。 每当更新数据集时,DataSource都会失效并通过DataSource.Factory自动重新创建。

Room持久性库为与Paging库关联的数据源提供本机支持。 对于给定的查询,Room允许您从DAO返回DataSource.Factory并为您处理DataSource的实现。

更新代码以从Room获取DataSource.Factory:

RepoDao:更新reposByName()函数以返回DataSource.Factory 。

fun reposByName(queryString: String): DataSource.Factory

GithubLocalCache使用此功能。 将reposByName函数的返回类型更改为DataSource.Factory 。

7、构建和配置分页列表

要构建和配置LiveData ,请使用LivePagedListBuilder。 除了DataSource.Factory,您还需要提供PagedList配置,其中包括以下选项:

由PagedList加载的页面大小

加载到底有多远

第一次加载时要加载的项目数

是否要将空项添加到PagedList,以表示尚未加载的数据。

更新GithubRepository以构建和配置分页列表:

定义要由分页库检索的每页项目数。 在伴随对象中,添加另一个名为DATABASE_PAGE_SIZE的const val,并将其设置为20.然后,我们的PagedList将以20个项目的块为单位从DataSource中分页数据。

companion object {

private const val NETWORK_PAGE_SIZE = 50

private const val DATABASE_PAGE_SIZE = 20

}

注意:DataSource页面大小应该是几个屏幕的项目。 如果页面太小,您的列表可能会闪烁,因为页面内容未覆盖整个屏幕。 较大的页面大小有利于加载效率,但可能会增加显示更新的延迟。

在GithubRepository.search()方法中,进行以下更改:

删除lastRequestedPage初始化和对requestAndSaveData()的调用,但暂时不完全删除此函数。

创建一个新值以从cache.reposByName()中保存DataSource.Factory:

// Get data source factory from the local cache

val dataSourceFactory = cache.reposByName(query)

在search()函数中,从LivePagedListBuilder构造数据值。 LivePagedListBuilder是使用dataSourceFactory和您之前定义的数据库页面大小构造的。

fun search(query: String): RepoSearchResult {

// Get data source factory from the local cache

val dataSourceFactory = cache.reposByName(query)

// Get the paged list

val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()

// Get the network errors exposed by the boundary callback

return RepoSearchResult(data, networkErrors)

}

8、使RecyclerView适配器与PagedList一起使用

要将PagedList绑定到RecycleView,请使用PagedListAdapter。 每当加载PagedList内容时,PagedListAdapter都会收到通知,然后通知RecyclerView进行更新。

更新ReposAdapter以使用PagedList:

现在,ReposAdapter是一个ListAdapter。 使它成为PagedListAdapter:

class ReposAdapter : PagedListAdapter(REPO_COMPARATOR)

我们的应用程序最终编译! 运行它,看看它是如何工作的。

9、触发网络更新

目前,我们使用附加到RecyclerView的OnScrollListener来了解何时触发更多数据。 不过,我们可以让Paging库处理列表滚动。

删除自定义滚动处理:

SearchRepositoriesActivity:删除setupScrollListener()方法及其所有引用

SearchRepositoriesViewModel:删除listScrolled()方法和随播对象

删除自定义滚动处理后,我们的应用程序具有以下行为:

每当我们滚动时,PagedListAdapter都会尝试从特定位置获取项目。

如果该索引处的项目尚未加载到PagedList中,则Paging库会尝试从数据源获取数据。

当数据源没有任何更多数据要提供给我们时,会出现问题,因为从初始加载数据返回零项或者因为我们已经从DataSource到达数据的末尾。 要解决此问题,请实现BoundaryCallback。 当这两种情况发生时,该类会通知我们,因此我们知道何时请求更多数据。 由于我们的DataSource是一个由网络数据支持的Room数据库,因此回调告诉我们应该从API请求更多数据。

使用BoundaryCallback处理数据加载:

在数据包中,创建一个名为RepoBoundaryCallback的新类,该类实现PagedList.BoundaryCallback 。 因为此类处理特定查询的网络请求和数据库数据保存,所以将以下参数添加到构造函数:查询字符串,GithubService和GithubLocalCache。

在RepoBoundaryCallback中,重写onZeroItemsLoaded()和onItemAtEndLoaded()。

class RepoBoundaryCallback(

private val query: String,

private val service: GithubService,

private val cache: GithubLocalCache

) : PagedList.BoundaryCallback() {

override fun onZeroItemsLoaded() {

}

override fun onItemAtEndLoaded(itemAtEnd: Repo) {

}

}

将以下字段从GithubRepository移动到RepoBoundaryCallback:isRequestInProgress,lastRequestedPage和networkErrors。

从networkErrors中删除可见性修饰符。 为其创建支持属性,并将networkErrors的类型更改为LiveData 。 我们需要进行此更改,因为在内部,在RepoBoundaryCallback类中,我们可以使用MutableLiveData,但在类之外,我们只公开一个LiveData对象,其值无法修改。

// keep the last requested page.

// When the request is successful, increment the page number.

private var lastRequestedPage = 1

private val _networkErrors = MutableLiveData()

// LiveData of network errors.

val networkErrors: LiveData

get() = _networkErrors

// avoid triggering multiple requests in the same time

private var isRequestInProgress = false

在RepoBoundaryCallback中创建一个伴随对象,并在那里移动GithubRepository.NETWORK_PAGE_SIZE常量。

将GithubRepository.requestAndSaveData()方法移动到RepoBoundaryCallback。

更新requestAndSaveData()方法以使用支持属性_networkErrors。

每当Paging数据源通知我们源中没有项目(调用RepoBoundaryCallback.onZeroItemsLoaded()时)或者数据源中的最后一项已加载时,我们应该从网络请求数据并将其保存在缓存中( 当调用RepoBoundaryCallback.onItemAtEndLoaded()时)。 因此,从onZeroItemsLoaded()和onItemAtEndLoaded()调用requestAndSaveData()方法:

override fun onZeroItemsLoaded() {

requestAndSaveData(query)

}

override fun onItemAtEndLoaded(itemAtEnd: Repo) {

requestAndSaveData(query)

}

在创建PagedList时更新GithubRepository以使用BoundaryCallback:

在search()方法中,使用查询,服务和缓存构造RepoBoundaryCallback。

在search()方法中创建一个值,该值维护对RepoBoundaryCallback- 发现的网络错误的引用。

将边界回调设置为LivePagedListBuilder。

fun search(query: String): RepoSearchResult {

Log.d("GithubRepository", "New query: $query")

// Get data source factory from the local cache

val dataSourceFactory = cache.reposByName(query)

// Construct the boundary callback

val boundaryCallback = RepoBoundaryCallback(query, service, cache)

val networkErrors = boundaryCallback.networkErrors

// Get the paged list

val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)

.setBoundaryCallback(boundaryCallback)

.build()

// Get the network errors exposed by the boundary callback

return RepoSearchResult(data, networkErrors)

}

从GithubRepository中删除requestMore()函数

而已! 使用当前设置,Paging库组件是在正确的时间触发API请求,将数据保存在数据库中以及显示数据的组件。 因此,运行应用程序并搜索存储库。

10、包装

现在我们添加了所有组件,让我们退一步看看一切如何协同工作。

DataSource.Factory(由Room实现)创建DataSource。 然后,LivePagedListBuilder使用传入的DataSource.Factory,BoundaryCallback和PagedList配置构建LiveData 。 此LivePagedListBuilder对象负责创建PagedList对象。 创建PagedList时,会同时发生两件事:

LiveData将新的PagedList发送到ViewModel,ViewModel又将其传递给UI。 UI观察更改的PagedList并使用其PagedListAdapter更新呈现PagedList数据的RecyclerView。 (在下面的动画中用空方块表示)。

PagedList尝试从DataSource获取第一个数据块。 当DataSource为空时,例如,当应用程序第一次启动且数据库为空时,它会调用BoundaryCallback.onZeroItemsLoaded()。 在此方法中,BoundaryCallback从网络请求更多数据并将响应数据插入数据库中。

将数据插入DataSource后,将创建一个新的PagedList对象(由填充的方块在下面的动画中表示)。 然后,使用LiveData将此新数据对象传递给ViewModel和UI,并在PagedListAdapter的帮助下显示。

当用户滚动时,PagedList请求DataSource加载更多数据,查询数据库以获取下一个数据块。 当PagedList分页来自DataSource的所有可用数据时,会调用BoundaryCallback.onItemAtEndLoaded()。 BoundaryCallback从网络请求数据并将响应数据插入数据库中。 然后,基于新加载的数据重新填充UI。

android studio 分页,Android Paging codelab相关推荐

  1. 使用Android Studio搭建Android集成开发环境

    一.Android Studio简单介绍 2013年GoogleI/O大会首次发布了Android Studio IDE(Android平台集成开发环境).它基于Intellij IDEA开发环境,旨 ...

  2. android+图标+i_explore+无背景,Android Studio中Android Device Monitor中的File Explore不显示文...

    环境:操作系统是Mac,模拟器 问题:Android Studio中Android Device Monitor中的File Explore不显示文件 本人在自学文件存储,想查看"dada/ ...

  3. Android开发工具Android Studio、Android SDK和Genymotion完全配置

    所谓"工欲善其事,必先利其器".Android Studio 是谷歌推出一个Android集成开发工具,基于IntelliJ IDEA. 类似 Eclipse ADT,Androi ...

  4. 在Android Studio中将Android工程变为Library使用

    在Android Studio中将Android工程变为Library使用 转载2016-03-28 10:43:58 有时候网上会有些工程格式不是我们想要的Library格式可以直接给AS使用.这时 ...

  5. android studio - 解决Android Studio不停的Indexing的问题

    android studio - 解决Android Studio不停的Indexing的问题 参考文章: (1)android studio - 解决Android Studio不停的Indexin ...

  6. 在Mac上使用android studio调试android手机

    一.环境准备 1.安卓手机:Android版本为9 2.MacBook Pro 3.安卓数据线 二.adb介绍 1.adb概念 adb:全称是Android Debug Bridge,意思是Andro ...

  7. 【Android Studio】Android Studio 搭建开发环境(Linux Ubuntu篇)

    Windows篇:[Android Studio]Android Studio 搭建开发环境(Windows篇)_dandelionela的博客-CSDN博客 Ubuntu篇参考: Ubuntu 18 ...

  8. Android Studio 提示android.support.v4不存在的解决方法

    Android Studio 提示android.support.v4不存在的解决方法 参考文章: (1)Android Studio 提示android.support.v4不存在的解决方法 (2) ...

  9. android studio1.2.6,1.2.2 使用Android Studio开发Android APP | 菜鸟教程

    写在前面本节将介绍如何使用Android Studio开发Android APP,和前面Eclipse + ADT + SDK搭建Android开发环境一样,本节也只是介绍一些基本东西,深入的,比如快 ...

最新文章

  1. 阿里AI再揽2员大将:挖角高通、360,年薪百万美元
  2. 几点易被忽视的网络软故障排查
  3. matlab编程小结
  4. 你从未听说过的最重要的数据库,人类登月计划的功臣
  5. android 带记忆功能的播放器源码,【全开源可二开】带后台管理面板的在线音乐播放器Android应用APP源码...
  6. ClickOnce 部署概述
  7. 万字长文!分布式锁的实现全都在这里了
  8. java struts2 上传图片_Java框架Struts2实现图片上传功能
  9. 【Proteus仿真8086】往8086 内存中写入数据
  10. STL标准模版库---set操作
  11. 日志记录总结要整理的地方
  12. PLC控制一阶惯性纯滞后系统的大林(Dahlin)算法详解(SCL代码)
  13. 数学建模练习-----基于无线通信基站的室内定位问题
  14. 信息系统集成监理费收取标准_信息系统工程监理与咨询服务收费参考标准起草说明...
  15. TP-LINK路由器手动设置网关、DNS、开启DHCP并指定地址池IP范围
  16. python统计一个字符串中连在一起数字的个数.(如12asd25asfd,输出结果为2)
  17. 【Android】自定义控件实现自动补齐邮箱后缀的输入框
  18. Apollo阿波罗配置中心基本概述
  19. 如何训练一个通用人工智能
  20. Python+Selenium PO模式Web自动化测试实战

热门文章

  1. 手机鸿蒙OS框架,鸿蒙OS正式“开源”!区别于iOS和安卓,实现了真正的纯国产!...
  2. linux系统中删除一次性任务流程,Linux系统中的计划任务
  3. 肇庆计算机商务学校地址,肇庆市商业学校(商业技工学校)招生报名
  4. 印江中学2021高考成绩查询,贵州印江中学2021年排名
  5. flask对mysql数据库增删改查_flask后台框架1.4(mysql配置+数据库增删改查)-Go语言中文社区...
  6. html语言的网页制作,HTML语言的网页制作技巧与方法
  7. css定位能用android,绝对的CSS定位显示android html上的空白区域
  8. 均方根误差有没有单位_装载机电子秤的功能特点以及误差的解决办法
  9. oracle 10g crs_stop -all 命令后遇到 UNKNOWN
  10. linux 内核 锐龙,AMDGPU内核驱动程序在Linux 5.0上运行良好