当前位置: 首页 > news >正文

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笔记&#xff08;二十二&#xff09;&#xff1a;Paging3分页加载库结合Compose的实现网络单一数据源访问一文中&#xff0c;实现了单一数据源的访问。在实际运行中&#xff0c;往往希望不是单纯地访问网络数据&#xff0c;更希望将访问的网络数据保存到移动终端的SQL…...

Python实现马赛克图片处理

文章目录 读取图片代码1、导入使用包2、读取图片 操作图片1、上下翻转2、左右翻转3、颜色颠倒4、降低图片精度5、打马赛克 说明&#xff1a; 在python中&#xff0c;图片可以看成一个三维的矩阵&#xff0c;第一维控制着垂直方向&#xff0c;第二维控制着水平方向&#xff0c;第…...

你能描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢?

一、生命周期是什么 生命周期&#xff08;Life Cycle&#xff09;的概念应用很广泛&#xff0c;特别是在政治、经济、环境、技术、社会等诸多领域经常出现&#xff0c;其基本涵义可以通俗地理解为“从摇篮到坟墓”&#xff08;Cradle-to-Grave&#xff09;的整个过程在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) 会议时间&#xff1a;2024年12月27日-29日 召开地点&#xff1a;中国西双版纳 大会官网&#xff1a;ICVISP 2024-2024 8th International Conference on Vision, Image and Signal Process…...

《HelloGitHub》第 93 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、Java、Go、C/C、Swift...让你在短时间内…...

JAVA B/S架构智慧工地源码,PC后台管理端、APP移动端

智慧工地系统充分利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术&#xff0c;以PC端&#xff0c;移动端&#xff0c;设备端三位一体的管控方式为企业现场工程管理提供了先进的技术手段。让劳务、设备、物料、安全、环境、能源、资料、计划、质量、视频监控等…...

【adb】--- win10 配置 adb环境 超详细 (持续更新中)

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【adb】--- win10 配置 adb环境 超详细 &…...

SQL注入安全漏洞详解

1. SQL注入的原理&#xff1a; SQL注入的攻击行为是通过用户可控参数中注入了SQL语法&#xff0c;改变原有SQL结构&#xff0c;以下两种情况可以造成SQL注入&#xff1a; 1.使用字符串拼接的方式构造SQL语句 2.未对用户可控参数进行严格的过滤&#xff0c;便把参数内容拼接到…...

数据结构与算法教程,数据结构C语言版教程!(第一部分、数据结构快速入门,数据结构基础详解)四

第一部分、数据结构快速入门&#xff0c;数据结构基础详解 数据结构基础&#xff0c;主要研究数据存储的方式。 本章作为数据结构的入门课程&#xff0c;主要让读者明白&#xff0c;数据结构到底是什么&#xff0c;常用的数据存储结构有哪些&#xff0c;数据结构和算法之间到底…...

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 语法一&#xff1a;内部样式 放在struct内 Styles commonStyle(){.backgroundColor(Color.Pink).padding(20px)}语法二&#xff1a;外部样式 Styles function commonStyle() {.backg…...

C++项目之酒店客房管理系统架构——设计模式应用场景详解(下)

5. 迭代器模式(Iterator Pattern):用于遍历客房列表。通过定义一个迭代器接口,可以遍历客房列表并访问每个客房的属性和状态。 代码中,Iterator是抽象迭代器,定义了迭代器的基本操作,包括判断是否还有下一项和获取下一项的方法。RoomIterator是具体迭代器,实现了具体的…...

RabbitMQ消息存储JSON格式反序列化

如果发送消息消息体为实体类对象数据&#xff0c;交换机接收消息经由路由键发送给队列。需要实现数据反序列化操作。实现JSON格式的反序列化操作 Rabbitmq的反序列化接口 MessageConverter&#xff0c;它的实现类有 Jackson2JsonMessageConverter的反序列化实现类&#xff0c…...

Java解决统计有序矩阵中的负数问题

Java解决统计有序矩阵中的负数问题 01 题目 给你一个 m * n 的矩阵 grid&#xff0c;矩阵中的元素无论是按行还是按列&#xff0c;都以非递增顺序排列。 请你统计并返回 grid 中 负数 的数目。 示例 1&#xff1a; 输入&#xff1a;grid [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-…...

【算法与数据结构】435、LeetCode无重叠区间

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;思路和【算法与数据结构】452、LeetCode用最少数量的箭引爆气球类似&#xff0c;也是排序找重叠区间。…...

【开题报告】基于SpringBoot的茶文化宣传网站设计与实现

1.研究背景和意义 1.1研究背景 茶文化是中国传统文化的重要组成部分&#xff0c;具有悠久的历史和丰富的内涵。茶文化不仅是一种饮食文化&#xff0c;更是一种生活方式和精神追求。然而&#xff0c;在当今快节奏的生活中&#xff0c;茶文化逐渐被人们所忽视。为了加强对茶文化…...

用通俗易懂的方式讲解大模型:基于 Langchain 和 ChatChat 部署本地知识库问答系统

之前写了一篇文章介绍基于 LangChain 和 ChatGLM 打造自有知识库问答系统&#xff0c;最近该项目更新了0.2新版本&#xff0c;这个版本与之前的版本差别很大&#xff0c;底层的架构发生了很大的变化。 该项目最早是基于 ChatGLM 这个 LLM&#xff08;大语言模型&#xff09;来…...

YOLO训练results.csv文件可视化(原模型与改进模型对比可视化)

一、单独一个文件可视化&#xff08;源码对应utils文件夹下的plots.py文件的plot_results类&#xff09; 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视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...