Android笔记(二十三):Paging3分页加载库结合Compose的实现分层数据源访问
在Android笔记(二十二):Paging3分页加载库结合Compose的实现网络单一数据源访问一文中,实现了单一数据源的访问。在实际运行中,往往希望不是单纯地访问网络数据,更希望将访问的网络数据保存到移动终端的SQLite数据库中,使得移动应用在离线的状态下也可以从数据库中获取数据进行访问。在本笔记中,将讨论多层次数据的访问,即结合网络资源+本地SQLite数据库中的数据的处理。在本笔记中,仍然采用Android笔记(二十二)中的网络资源:
上列展示的json数组包含了多个json对象,每个json对象的格式类似下列形式:
{"actors":"演员",
"directors":"导演",
"intro":"电影简介",
"poster":"http://localhost:5000/photo/s_ratio_poster/public/p2626067725.jpg",
"region":"地区",
"release":"发布年份",
"trailer_url":"https://localhost:5000/trailer/268661/#content",
"video_url":"https://localhost:5000/d04d3c0d2132a29410dceaeefa97e725/view/movie/M/402680661.mp4"}
一、分层次访问数据的架构
与单一数据源结构不同在于增加了RemoteMediator。当应用的已缓存数据用尽时,RemoteMediator 会充当来自 Paging 库的信号。可以使用此信号从网络加载更多数据并将其存储在本地数据库中,PagingSource 可以从本地数据库加载这些数据并将其提供给界面进行显示。
当需要更多数据时,Paging 库从 RemoteMediator 实现调用 load() 方法。这是一项挂起功能,因此可以放心地执行长时间运行的工作。此功能通常从网络源提取新数据并将其保存到本地存储空间。
此过程会处理新数据,但长期存储在数据库中的数据需要进行失效处理(例如,当用户手动触发刷新时)。这由传递到 load() 方法的 LoadType 属性表示。LoadType 会通知 RemoteMediator 是需要刷新现有数据,还是提取需要附加或前置到现有列表的更多数据。
通过这种方式,RemoteMediator 可确保应用以适当的顺序加载用户要查看的数据。
二、定义实体类
1.定义Film类
@Entity(tableName="films")
data class Film(@PrimaryKey(autoGenerate = false)@SerializedName("name")val name:String,@SerializedName("release")val release:String,@SerializedName("region")val region:String,@SerializedName("directors")val directors:String,@SerializedName("actors")val actors:String,@SerializedName("intro")val intro:String,@SerializedName("poster")val poster:String,@SerializedName("trailer_url")val trailer:String,@SerializedName("video_url")val video:String
)
在上述代码中,将Film类映射为数据库中的数据表films。对应的数据表结构如下所示:
2.定义FilmRemoteKey类
因为从网络访问每一个条电影记录需要知道记录的上一页和下一页的内容,因此定义FilmRemoteKey类,代码如下:
@Entity(tableName = "filmRemoteKeys")
data class FilmRemoteKey(@PrimaryKey(autoGenerate = false)val name:String,val prePage:Int?,val nextPage:Int?
)
FilmRemoteKey对应的数据表结构如下:
name表示电影名,也是关键字
prePage表示记录的上一页的页码,因为第一页的所有记录没有上一页,因此,前5条记录的prePage均为空
nextPage表示记录的下一页的页面。
三、定义网络访问
1.网络访问服务接口
interface FilmApi {@GET("film.json")suspend fun getData(@Query("page") page:Int,@Query("size") size:Int):List<Film>
}
2.Retrofit构建网络服务
object RetrofitBuilder {private const val BASE_URL = "http://10.0.2.2:5000/"private fun getRetrofit(): Retrofit {return Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()}val apiService:FilmApi = getRetrofit().create(FilmApi::class.java)
}
四、定义数据库的访问
1.电影数据访问对象的接口
@Dao
interface FilmDao {/*** 插入数据列表* @param films List<Film>*/@Insert(onConflict = OnConflictStrategy.REPLACE)suspend fun insertAll(films: List<Film>)/*** 检索所有的Film记录* @return PagingSource<Int, Film>*/@Query("select * from films")fun queryAll(): PagingSource<Int, Film>/*** Delete all* 删除表films中所有记录*/@Query("DELETE FROM films")suspend fun deleteAll()
}
2.电影页码数据访问对象的接口
@Dao
interface FilmRemoteKeyDao {@Query("SELECT * FROM filmRemoteKeys WHERE name = :name")suspend fun findByName(name:String):FilmRemoteKey@Insert(onConflict =OnConflictStrategy.REPLACE)suspend fun insertAllKeys(remoteKeys:List<FilmRemoteKey>)@Query("DELETE FROM filmRemoteKeys")suspend fun deleteAllKeys()
}
3.创建数据库
@Database(entities = [Film::class,FilmRemoteKey::class], version = 1)
abstract class FilmDatabase : RoomDatabase() {abstract fun filmDao(): FilmDaoabstract fun filmRemoteKeyDao():FilmRemoteKeyDaocompanion object{private var instance: FilmDatabase? = null/*** 单例模式创建为一个FilmDB对象实例*/@Synchronizedfun getInstance(context:Context = FilmApp.context): FilmDatabase {instance?.let{return it}return Room.databaseBuilder(context,FilmDatabase::class.java,"filmDB.db").build()}}
}
五、定义代码层
1.定义RemoteMediator类
@OptIn(ExperimentalPagingApi::class)
class FilmRemoteMediator(private val database:FilmDatabase,private val networkService:FilmApi
) : RemoteMediator<Int, Film>() {private val filmDao = database.filmDao()private val filmRemoteKeyDao = database.filmRemoteKeyDao()override suspend fun load(loadType: LoadType,state: PagingState<Int, Film>): MediatorResult {return try{/*** 从数据库获取缓存的当前页面*/val currentPage:Int = when(loadType){//UI初始化刷新LoadType.REFRESH-> {val remoteKey:FilmRemoteKey? = getRemoteKeyToCurrentPosition(state)remoteKey?.nextPage?.minus(1)?:1}//在当前列表头添加数据使用LoadType.PREPEND-> {val remoteKey = getRemoteKeyForTop(state)val prevPage = remoteKey?.prePage?:return MediatorResult.Success(remoteKey!=null)prevPage}//尾部加载更多的记录LoadType.APPEND->{val remoteKey = getRemoteKeyForTail(state)val nextPage = remoteKey?.nextPage?:return MediatorResult.Success(remoteKey!=null)nextPage}}/*** 联网状态下的处理* 获取网络资源* response*/val response = networkService.getData(currentPage,5)val endOfPaginationReached = response.isEmpty()val prePage = if(currentPage == 1) null else currentPage-1val nextPage = if(endOfPaginationReached) null else currentPage+1database.withTransaction{//刷新记录,需要删除原有的记录if(loadType == LoadType.REFRESH){filmDao.deleteAll()filmRemoteKeyDao.deleteAllKeys()}//获取的记录映射成对应的索引记录val keys:List<FilmRemoteKey> = response.map{film:Film->FilmRemoteKey(film.name,prePage,nextPage)}filmRemoteKeyDao.insertAllKeys(keys)filmDao.insertAll(response)}MediatorResult.Success(endOfPaginationReached)}catch(e:IOException){MediatorResult.Error(e)}catch(e:HttpException){MediatorResult.Error(e)}}/*** 获取当前位置对应的FilmRemoteKey* @param state PagingState<Int, Film>* @return FilmRemoteKey?*/private suspend fun getRemoteKeyToCurrentPosition(state:PagingState<Int,Film>):FilmRemoteKey?=state.anchorPosition?.let{position:Int->state.closestItemToPosition(position)?.name?.let{name:String->filmRemoteKeyDao.findByName(name)}}/*** 获取当前页面从头部第一个位置对应的FilmRemoteKey* @param state PagingState<Int, Film>* @return FilmRemoteKey?*/private suspend fun getRemoteKeyForTop(state:PagingState<Int,Film>):FilmRemoteKey?=state.pages.firstOrNull{ it:PagingSource.LoadResult.Page<Int,Film>->it.data.isNotEmpty()}?.data?.firstOrNull()?.let{film:Film->filmRemoteKeyDao.findByName(film.name)}/*** 获取当前尾部最后一个位置对应的FilmRemoteKey* @param state PagingState<Int, Film>* @return FilmRemoteKey?*/private suspend fun getRemoteKeyForTail(state:PagingState<Int,Film>):FilmRemoteKey?=state.pages.lastOrNull{it:PagingSource.LoadResult.Page<Int,Film>->it.data.isNotEmpty()}?.data?.lastOrNull()?.let{film:Film->filmRemoteKeyDao.findByName(film.name)}
}
2.定义PagingSource数据源
@ExperimentalPagingApi
class FilmRepository(private val filmApi:FilmApi,private val filmDatabase:FilmDatabase
) {fun getAllFilms(): Flow<PagingData<Film>> {val pagingSourceFactory:()->PagingSource<Int, Film> = {filmDatabase.filmDao().queryAll()}return Pager(config = PagingConfig(pageSize = 5),initialKey = null,remoteMediator = FilmRemoteMediator(filmDatabase,filmApi),pagingSourceFactory = pagingSourceFactory).flow}
}
六、定义视图模型层
@OptIn(ExperimentalPagingApi::class)
class MainViewModel(): ViewModel() {val filmRepository:FilmRepository = FilmRepository(RetrofitBuilder.apiService,FilmDatabase.getInstance())fun getFilms()=filmRepository.getAllFilms()
}
七、定义界面层
1.单独电影界面的定义
@Composable
fun FilmCard(film: Film?) {Card(modifier = Modifier.fillMaxSize().padding(2.dp),elevation = CardDefaults.cardElevation(5.dp),colors = CardDefaults.cardColors(containerColor = Color.DarkGray)){Column{Row(modifier = Modifier.fillMaxSize()){AsyncImage(modifier=Modifier.width(180.dp).height(240.dp),model = "${film?.poster}",contentDescription = "${film?.name}")Column{Text("${film?.name}",fontSize = 18.sp,color = Color.Green)Text("导演:${film?.directors}",fontSize = 14.sp,color = Color.White)Text("演员:${film?.actors}", fontSize = 14.sp,color = Color.Green)}}Text("${film?.intro?.subSequence(0,60)} ...",fontSize = 14.sp,color= Color.White)Row(horizontalArrangement = Arrangement.End,modifier = Modifier.fillMaxSize()){Text("More",fontSize=12.sp)IconButton(onClick ={}){Icon(imageVector = Icons.Default.MoreVert,tint= Color.Green,contentDescription = "更多...")}}}}
}
2.定义电影列表
@Composable
fun FilmScreen(mainViewmodel:MainViewModel){val films = mainViewmodel.getFilms().collectAsLazyPagingItems()Column(horizontalAlignment = Alignment.CenterHorizontally,modifier = Modifier.background(Color.White)){LazyColumn{items(films.itemCount){FilmCard(films[it])}}}
}
八、定义主活动MainActivity
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {val mainViewModel:MainViewModel = viewModel()Ch11_DemoTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {FilmScreen(mainViewmodel = mainViewModel)}}}}
}
参考文献
Paging库概览
https://developer.android.google.cn/topic/libraries/architecture/paging/v3-overview?hl=zh-cn
相关文章:
Android笔记(二十三):Paging3分页加载库结合Compose的实现分层数据源访问
在Android笔记(二十二):Paging3分页加载库结合Compose的实现网络单一数据源访问一文中,实现了单一数据源的访问。在实际运行中,往往希望不是单纯地访问网络数据,更希望将访问的网络数据保存到移动终端的SQL…...
Python实现马赛克图片处理
文章目录 读取图片代码1、导入使用包2、读取图片 操作图片1、上下翻转2、左右翻转3、颜色颠倒4、降低图片精度5、打马赛克 说明: 在python中,图片可以看成一个三维的矩阵,第一维控制着垂直方向,第二维控制着水平方向,第…...
你能描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢?
一、生命周期是什么 生命周期(Life Cycle)的概念应用很广泛,特别是在政治、经济、环境、技术、社会等诸多领域经常出现,其基本涵义可以通俗地理解为“从摇篮到坟墓”(Cradle-to-Grave)的整个过程在Vue中实…...
【经典算法】有趣的算法之---蚁群算法梳理
every blog every motto: You can do more than you think. 0. 前言 蚁群算法记录 1. 简介 蚁群算法(Ant Clony Optimization, ACO)是一种群智能算法,它是由一群无智能或有轻微智能的个体(Agent)通过相互协作而表现出智能行为,从而为求解复杂问题提供了一个新的可能性…...
第八届视觉、图像与信号处理国际会议(ICVISP 2024) | Ei, Scopus双检索
会议简介 Brief Introduction 2024年第八届视觉、图像与信号处理国际会议(ICVISP 2024) 会议时间:2024年12月27日-29日 召开地点:中国西双版纳 大会官网:ICVISP 2024-2024 8th International Conference on Vision, Image and Signal Process…...
《HelloGitHub》第 93 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、Java、Go、C/C、Swift...让你在短时间内…...
JAVA B/S架构智慧工地源码,PC后台管理端、APP移动端
智慧工地系统充分利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术,以PC端,移动端,设备端三位一体的管控方式为企业现场工程管理提供了先进的技术手段。让劳务、设备、物料、安全、环境、能源、资料、计划、质量、视频监控等…...
【adb】--- win10 配置 adb环境 超详细 (持续更新中)
在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。 【adb】--- win10 配置 adb环境 超详细 &…...
SQL注入安全漏洞详解
1. SQL注入的原理: SQL注入的攻击行为是通过用户可控参数中注入了SQL语法,改变原有SQL结构,以下两种情况可以造成SQL注入: 1.使用字符串拼接的方式构造SQL语句 2.未对用户可控参数进行严格的过滤,便把参数内容拼接到…...
数据结构与算法教程,数据结构C语言版教程!(第一部分、数据结构快速入门,数据结构基础详解)四
第一部分、数据结构快速入门,数据结构基础详解 数据结构基础,主要研究数据存储的方式。 本章作为数据结构的入门课程,主要让读者明白,数据结构到底是什么,常用的数据存储结构有哪些,数据结构和算法之间到底…...
mac安装k8s环境
安装kubectl brew install kubectl 确认一下安装的版本 kubectl version --client 如果想在本地运行kubernetes 需要安装minikube brew install minikube 需要注意安装minikube需要本地的docker服务是启动的 启动 默认连接的是google的仓库 minikube start 指定阿…...
HarmonyOS4.0系列——04、@Styles、@Extend、@Extend事件以及多态样式stateStyles
Styles、Extend、Extend事件以及多态样式stateStyles Styles 通用样式 类似于css中的class 语法一:内部样式 放在struct内 Styles commonStyle(){.backgroundColor(Color.Pink).padding(20px)}语法二:外部样式 Styles function commonStyle() {.backg…...
C++项目之酒店客房管理系统架构——设计模式应用场景详解(下)
5. 迭代器模式(Iterator Pattern):用于遍历客房列表。通过定义一个迭代器接口,可以遍历客房列表并访问每个客房的属性和状态。 代码中,Iterator是抽象迭代器,定义了迭代器的基本操作,包括判断是否还有下一项和获取下一项的方法。RoomIterator是具体迭代器,实现了具体的…...
RabbitMQ消息存储JSON格式反序列化
如果发送消息消息体为实体类对象数据,交换机接收消息经由路由键发送给队列。需要实现数据反序列化操作。实现JSON格式的反序列化操作 Rabbitmq的反序列化接口 MessageConverter,它的实现类有 Jackson2JsonMessageConverter的反序列化实现类,…...
Java解决统计有序矩阵中的负数问题
Java解决统计有序矩阵中的负数问题 01 题目 给你一个 m * n 的矩阵 grid,矩阵中的元素无论是按行还是按列,都以非递增顺序排列。 请你统计并返回 grid 中 负数 的数目。 示例 1: 输入:grid [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-…...
【算法与数据结构】435、LeetCode无重叠区间
文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:思路和【算法与数据结构】452、LeetCode用最少数量的箭引爆气球类似,也是排序找重叠区间。…...
【开题报告】基于SpringBoot的茶文化宣传网站设计与实现
1.研究背景和意义 1.1研究背景 茶文化是中国传统文化的重要组成部分,具有悠久的历史和丰富的内涵。茶文化不仅是一种饮食文化,更是一种生活方式和精神追求。然而,在当今快节奏的生活中,茶文化逐渐被人们所忽视。为了加强对茶文化…...
用通俗易懂的方式讲解大模型:基于 Langchain 和 ChatChat 部署本地知识库问答系统
之前写了一篇文章介绍基于 LangChain 和 ChatGLM 打造自有知识库问答系统,最近该项目更新了0.2新版本,这个版本与之前的版本差别很大,底层的架构发生了很大的变化。 该项目最早是基于 ChatGLM 这个 LLM(大语言模型)来…...
YOLO训练results.csv文件可视化(原模型与改进模型对比可视化)
一、单独一个文件可视化(源码对应utils文件夹下的plots.py文件的plot_results类) from pathlib import Path import matplotlib.pyplot as plt import pandas as pd def plot_results(fileruns/train/exp9/results.csv, dir):# Plot training results.c…...
uni-appcss语法
锋哥原创的uni-app视频教程: 2023版uniapp从入门到上天视频教程(Java后端无废话版),火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版),火爆更新中...共计23条视频,包括:第1讲 uni…...
java在线票务系统(选座)Myeclipse开发mysql数据库web结构java编程计算机网页项目
一、源码特点 java servlet 在线票务系统(选座)管理系统是一套完善的java web信息管理系统 系统采用serlvetdaobean(mvc模式),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要…...
Python 简易图形界面库easygui 对话框大全(续)
目录 EasyGUI库 主要特点 使用场景 对话框样式 10. 文件打开框 fileopenbox 11. 文件保存框 filesavebox 12. 目录打开框 diropenbox 13. 索引对话框 indexbox 14. 例外报告框 exceptionbox 15. 代码文本框 codebox 16. 密码输入框 passwordbox 17. 多重文本框 mul…...
电容器50ZLH56MEFC6.3X11
电容器 常用电子元器件类型 50ZLH56MEFC6.3X11 文章目录 电容器前言一、电容器二、50ZLH56MEFC6.3X11总结前言 电容器在电子电路中有许多重要的应用,如滤波、耦合、储能、定时等。不同类型的电容器具有不同的性能特点,例如电容量、工作电压、频率响应等。在选择和使用电容…...
vscode 支持c,c++编译调试方法
概述:tasks.jason launch.json settings.json一定要有,没有就别想跑。还有就是c 和c配置有区别,切记,下文有说 1.安装扩展插件。 2.安装编译器,gcc.我用的是x86_64-8.1.0-release-win32-seh-rt_v6-rev0.7z …...
MyBatis的缓存!!!!
为什么使用缓存? 首次访问时,查询数据库,并将数据存储到内存中;再次访问时直接访问缓存,减少IO、硬盘读写次数、提高效率 Mybatis中的一级缓存和二级缓存? 一级缓存: 它指的是mybatis中的SqlSession对象的…...
ToB还是ToC?工业级与消费级AR眼镜都能干什么?
随着科技的飞速发展,增强现实(AR)技术逐渐融入我们的日常生活。我国AR眼镜消费市场分为消费级和工业级应用。其中消费级主要分为游戏、影视、直播以及社交购物与旅游;工业级主要应用于医疗、汽车、工业、船舶、电力和仓储等专业领…...
设计模式-Java版本
文章目录 前言设计原则单一职责原则开闭原则里氏替换原则迪米特法则接口隔离原则依赖倒置原则 设计模式构建类型工厂模式抽象工厂建造者模式原型模式单例模式 结构型适配器模式桥接模式组合模式装饰器模式代理模式外观模式享元模式 行为模式责任链模式命令模式迭代器模式中介模…...
数据库中如何修改和删除字段
PS:在"[ ]"中的所有数据都是可修改的 添加表字段 ALTER TABLE [表名] add [添加的新字段名] [添加新的数据类型] COMMENT [昵称] alter:修改(后面一般加table表示修改表) add:添加一个字段 在这个里面c…...
在 Golang 应用程序中管理多个数据库
掌握在 Golang 项目中处理多个数据库的艺术 在当前软件开发领域中,处理单个应用程序内的多个数据库的需求越来越普遍。具有强大功能的 Golang 是处理此类任务的绝佳解决方案,无论您是与多个数据源合作还是仅为增强组织和可扩展性而分隔数据。在本文中&a…...
理解开源协议GPL、MIT、BSD、Apache License
开源协议是一种法律文件,规定了使用、修改和分享开源软件的规则和条件。以下是一些常见的开源协议及其相同点和区别:GPL(GNU General Public License):GPL 是一种比较严格的开源协议,要求使用者如果对开源软…...
网站哪些功能是PHP做的/整合营销传播方案
关于HeyUI Admin HeyUI Admin 是一个成熟的企业应用解决方案,是基于 vue2.0 和 heyui 组件库的中后端系统。 相关链接 github在线预览文档介绍 在这个系统中,除了提供了一些基础的示例,最重要的是提供了一个标准的前端系统架构模板。 如果你是…...
养生网站建设/西安自助建站
我看到了一段执行磁盘io的代码,如下所示:static bool is_aligned(unsigned char *buffer) {return ( ((unsigned long)buffer) & (DISK_PAGE_SIZE -1)) 0;}void do_write_IO(int fd, unsignced char *buffer, unsigned long buffer_bytes) {...if (…...
如何用服务器代替空间做网站/seo公司优化排名
2017-02-05 16:02:14你好,具体操作如下: 1、实现手机与电脑连接的第一步操作就是打开手机“USB调试”开关。依次点击“设置”-“应用程序”-“开发工具”,勾选其中的USB调试开关项即可。 2、当手机出...2016-12-30 14:09:481、登陆路由器&…...
wordpress怎么在导航栏添加搜索框/google搜索
你需要一份复印件:b a.copy()b a创建一个引用,因此a is b它们都指向内存中的相同位置,a.copy()实际上创建了一个新对象。在^{pr2}$如果使用basic slicing对数组进行切片,则id将不同,但任何更改都将反映在a和b中&…...
做网站需要多少人/唐山建站公司模板
Fedora28安装opencv-4.1.0+opencv_contrib-4.1.0 1.环境配置 OpenCV4.1.0和opencv_contrib-4.1.0的安装包大家可从GitHub上搜索下载,如果不想搜索,可从我的百度网盘下载(同时网盘内也上传了一些Cmake易下载失败的文件): 链接:https://pan.baidu.com/s/1aVCjkfw7Mh0rZe9iZ…...
13个实用平面设计网站/怎么注册一个网站
Python实现四边形检测和矫正操作 在图像处理中,四边形检测和矫正是一项重要的任务。本文将介绍如何使用Python实现对图像中四边形的检测和矫正。 首先,我们需要使用Python的OpenCV库来进行图像处理。通过使用OpenCV,我们可以很方便地对图像进行操作。 接下来,我们需要定…...