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

Android开发的这一年里,Jetpack的Room源码是怎么狠狠奖励我的?

简述

Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良好的开发体验,大大降低了SQLite的使用门槛。

Room是Google官方在SQLite基础上封装的一款数据持久库,是Jetpack全家桶的一员,和Jetpack其他库有着可以高度搭配协调的天然优势。Room使用APT技术,大大简化了使用SQLite的代码量,只需使用注解配合少量代码即可实现高效的数据库操作。

Room基本介绍

框架特点

相对于SQLiteOpenHelper等传统方法,使用Room操作SQLite有以下优势:

  • 编译期的SQL语法检查
  • 开发高效,避免大量模板代码
  • API设计友好,容易理解
  • 可以与RxJava、 LiveData 、 Kotlin Coroutines等进行桥接

添加依赖

dependencies {implementation "androidx.room:room-runtime:2.2.5"kapt "androidx.room:room-compiler:2.2.5"
}

基本组件

Room的使用,主要涉及以下3个组件

  • Database: 访问底层数据库的入口
  • Entity: 代表数据库中的表(table),一般用注解
  • Data Access Object (DAO): 数据库访问者

这三个组件的概念也出现在其他ORM框架中,有过使用经验的同学理解起来并不困难: 通过Database获取DAO,然后通过DAO查询并获取entities,最终通过entities对数据库table中数据进行读写

实例实战

​
insert:使用注解 @Insert,Room 会自动将所有参数在单个事物中插入数据库
@Insert
public fun inertUser (user: User)  // 单个参数可以返回 long
​
@Insert
public fun insertUserList (array: Array<User>)  // 参数为集合可以返回 long []
数据库添加 User
val user = User()
user.name = "赵云 编号 = $number"
val address = Address()
address.street = "成都接头"
address.state = "蜀汉"
address.city = "常山"
address.postCode = 10010
user.address = address
userDao.inertUser (user)  // 添加 User
添加数据结果:
​
​
upadte:使用 @Update 注解
@Update
public fun update (user: User)   // 可以让此方法返回一个 int 值,表示数据库中更新的行数  val user = User()
user.id = 1
user.name = "张翼德"
address.city = "涿郡"
.....
userDao.update(user)
点击 Update 后再查询结果:此时的赵云已经改为张翼徳了
​
​
delete:使用 @Delete 注解
@Delete 
public fun delete (user: User)   // 可以返回一个 int 值,表示从数据库中删除的行数val user = User()
user.id = 1    // 要删除的主键 id
userDao.delete(user)
点击 delete 后再次查询数据:编号为 1 的数据已被删除
​
​
查询信息 :@Query 注解对数据库执行读 / 写操作
@Query("SELECT * FROM user")
public fun selectAll (): Array<User>   // 查询所有数据@Query("SELECT * FROM user WHERE name = :name")
public fun selectUser (name:String): Array<User>   // 条件查询
返回列的子集:创建子类在每个属性中使用 @ColumnInfo (name = "name") 标记对应数据库中的列名
public class UserTuple {                  // 1、根据要查询的字段创建 POJO 对象      @ColumnInfo(name = "name")public var name: String? = null@ColumnInfo(name = "city")public var city: String? = null
}@Query ("SELECT name ,city FROM user")  // 2、查询的结果会映射到创建的对象中public List<UserTuple> loadFullName();val userList = userDao.loadFullName()
for (userTuple in userList) {stringBuilder.append(userTuple.name).append("  ").append(userTuple.city).append("\n")
}
输出的结果:只有 name 和 city 两列
​
​
范围条件查询 :查询城市中所有用户
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray)")
fun loadUserInCity(cityArray: Array<String>): List<UserTuple>val userList = userDao.loadUserInCity (arrayOf ("常山"))  // 查询常山,只会出现赵云不会出现张翼德
​
​
Observable 查询:使用 LiveData 作为查询方法的返回值,注册观察者后,数据表更改时自动更新 UI
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))
fun loadUserInCityLive(cityArray: Array<String>): LiveData<List<UserTuple>>
​
​
​
private lateinit var liveData: LiveData<Array<UserTuple>>  // 定义一个 LiveData
get() {
return userDao.loadUserInCityLive (arrayOf ("常山"))
}
​
val observer = Observer<Array<UserTuple>> {    // 定义一个观察者val stringBuilder = StringBuilder()for (index in it!!.indices) {val userTuple = it[index]stringBuilder.append(userTuple.name).append("  ").append(userTuple.name).append("  \n")}tv_main_show.text = stringBuilder.toString()
}
liveData.observe (this, observer)   // 注册观察者
运行结果:此时当添加数据时,UI 会自动更新;
​
RxJava 查询 :返回 Observable 实例可以使用 RxJava 订阅观察者
@Query("SELECT * FROM user WHERE id = :id LIMIT 1")
fun loadUserRxJava(id:Int) : Flowable<User>
​
​
userDao.loadUserRxJava(4).subscribe(Consumer {val stringBuilder = StringBuilder()stringBuilder.append(it.id).append("  ").append(it.name).append("  \n")tv_main_show.text = stringBuilder.toString()})
​
​Cursor 查询:返回 Cursor 对象
fun loadUserCursor(id:Int) : Cursor
多表查询:根据表的外键多表查询
@Query("SELECT user.name AS userName, pet.name AS petName "+ "FROM user, pet "+ "WHERE user.id = pet.user_id")

源码分析

从上面的Demo代码可以看出,Room有很多的注解,实际上Room正是通过APT注解处理器,自动生成了许多代码,避免使用者在为了使用数据库编写重复的模板代码。

1.Room.databaseBuilder.build()

数据库使用的入口,也就是构造RoomDatabase的实例,它里面会根据buider中做的配置,进行一系列赋值操作,生成一个 DatabaseConfiguration对象,然后传入的 class 对象,调用Class.newInstance()方法,获取到RoomDatabase的实例。 接着就是调用RoomDatabase.init进行初始化操作。

public T build() {//...省略DatabaseConfiguration configuration =new DatabaseConfiguration(mContext,mName,factory,mMigrationContainer,mCallbacks,mAllowMainThreadQueries,mJournalMode.resolve(mContext),mQueryExecutor,mTransactionExecutor,mMultiInstanceInvalidation,mRequireMigration,mAllowDestructiveMigrationOnDowngrade,mMigrationsNotRequiredFrom,mCopyFromAssetPath,mCopyFromFile,mCopyFromInputStream,mPrepackagedDatabaseCallback,mTypeConverters);T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);db.init(configuration);return db;
}

2.RoomDatabase.init()

调用了createOpenHelper方法,createOpenHelper方法实现在AppDatabase_Impl中,创建了RoomOpenHelper,RoomOpenHelper继承SupportSQLiteOpenHelper.Callback。 引申:注意这里的 AutoCloser对象,这里没有仔细研究它的代码,但是它应该是代理持有了一个 SupportSQLiteOpenHelper对象,可以实现在提交数据库事务之后,自动的判断并close数据库。 从这里就可以看出,Room实际上是对 SQLite的再次封装,但是通过 APT 以及其他辅助类,使得Room的比直接用SQLite要简便很多。

@Overrideprotected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration,new RoomOpenHelper.Delegate(2) {//...省略}
}
​
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {mOpenHelper = createOpenHelper(configuration);
​// Configure SqliteCopyOpenHelper if it is available:SQLiteCopyOpenHelper copyOpenHelper = unwrapOpenHelper(SQLiteCopyOpenHelper.class,mOpenHelper);if (copyOpenHelper != null) {copyOpenHelper.setDatabaseConfiguration(configuration);}
​AutoClosingRoomOpenHelper autoClosingRoomOpenHelper =unwrapOpenHelper(AutoClosingRoomOpenHelper.class, mOpenHelper);
​if (autoClosingRoomOpenHelper != null) {mAutoCloser = autoClosingRoomOpenHelper.getAutoCloser();mInvalidationTracker.setAutoCloser(mAutoCloser);}  // ... 省略
}            

3.AppDatabase.getUserDao()

AppDatabase是我们继承自RoomDatabase的抽象类,我们在里面添加了获取UserDao的方法,Room 会通过APT技术,在编译之后自动帮我们添加对应的实现。

@Overridepublic UserDao getUerDao() {if (_userDao != null) {return _userDao;} else {synchronized(this) {if(_userDao == null) {_userDao = new UserDao_Impl(this);}return _userDao;}}}

4.UserDao_Impl

可以看出来,UserDao_Impl根据注解,具体的实现了我们在 UserDao接口中添加的方法,其中的关键点就在于,根据我们添加在注解中的 sql 语句,生成了对数据库的访问代码。

@Override
public void insertUserRecord(final User user) {__db.assertNotSuspendingTransaction();__db.beginTransaction();try {__insertionAdapterOfUser.insert(user);__db.setTransactionSuccessful();} finally {__db.endTransaction();}
}
​
@Override
public User geUerById(final String id) {final String _sql = "select *from User where id = (?)";final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);int _argIndex = 1;if (id == null) {_statement.bindNull(_argIndex);} else {_statement.bindString(_argIndex, id);}__db.assertNotSuspendingTransaction();final Cursor _cursor = DBUtil.query(__db, _statement, false, null);try {final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");final User _result;if(_cursor.moveToFirst()) {final String _tmpId;if (_cursor.isNull(_cursorIndexOfId)) {_tmpId = null;} else {_tmpId = _cursor.getString(_cursorIndexOfId);}final String _tmpName;if (_cursor.isNull(_cursorIndexOfName)) {_tmpName = null;} else {_tmpName = _cursor.getString(_cursorIndexOfName);}_result = new User(_tmpId,_tmpName);} else {_result = null;}return _result;} finally {_cursor.close();_statement.release();}
}

总结

本文只是对Room的一个简单分析,正如在一开始的概述里面说的那样:Room是对SQLite数据库的抽象,它提供了很多便利的API和注解等,简化了使用者使用数据库的方式。本文没有分析 Room和 LiveData结合使用的情况,因为笔者公司的项目还没能引入 LiveData。

抛开这一点不谈,个人认为它有两个比较显著的优点: 1、当然就是通过APT技术,TypeConverter 等,简化了使用,减少了大量的访问数据库的模板代码。 2、是文中也有提到的,它对数据库升级的优化,除了可以避免两个分支同时升级数据库但是合并不冲突导致的错误之外,它还提供了对数据库升级做单元测试的工具类,安全性提升很多。

相关文章:

Android开发的这一年里,Jetpack的Room源码是怎么狠狠奖励我的?

简述 Android Jetpack的出现统一了Android开发生态&#xff0c;各种三方库逐渐被官方组件所取代。Room也同样如此&#xff0c;逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份&#xff0c;更是因为其良好的开发体验&#xff0c;大大降低了SQLite的使用门槛…...

推荐一款卸载软件的小工具-《UninstallToo》

目录 UninstallToo介绍 UninstallToo下载 UninstallToo使用 总结 UninstallToo介绍 Uninstall Tool 是一款可以用来替代“添加/删除程序”的工具。它允许您显示隐藏的安装程序&#xff0c;按名称过滤已安装程序的列表&#xff0c;强行写在程序&#xff0c;浏览注册表项目&a…...

线程池——JUC随记8

线程池使用方式 1、一池N线程&#xff08;Executors.newFixedThreadPool(n)&#xff09; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class ExecutorDemo {public static void main(String[] args) {ExecutorService execu…...

SpringCloudAlibaba微服务调用组件-Feign

SpringCloudAlibaba微服务调用组件-Feign 本项目代码与笔记已存放在Gitee仓库 地址&#xff1a; 代码&#xff0c;笔记 文章目录SpringCloudAlibaba微服务调用组件-Feign1. 什么是Feign1.1 优势2. Spring Cloud Alibaba快速整合OpenFeign1&#xff09;引入依赖2&#xff09;编写…...

高效学习方法论

2023.03.17 《程序员的三门课&#xff1a;技术精进、架构修炼、管理探秘 / 于君泽等著》学习笔记 学会学习一、高效学习的方法1、管理好自己的目标1&#xff09;评估能力2&#xff09;制定目标3&#xff09;评估目标2、利用好碎片时间3、在同一时间只做一件事二、高效学习的途径…...

C语言结构体(一篇学会)

C语言结构体 在C语言中&#xff0c;结构体是一种自定义的数据类型&#xff0c;它允许用户将不同类型的数据组合在一起。结构体由多个变量组成&#xff0c;这些变量称为结构体的成员。结构体成员可以是不同的数据类型&#xff0c;如整数、浮点数、字符或其他结构体等。 结构体…...

嵌入式软件开发之Linux下C编程

目录 前沿 Hello World&#xff01; 编写代码 编译代码 GCC编译器 gcc 命令 编译错误警告 编译流程 Makefile 基础 何为 Makefile Makefile 的引入 前沿 在 Windows 下我们可以使用各种各样的 IDE 进行编程&#xff0c;比如强大的 Visual Studio。但是在Ubuntu 下如何进…...

普通Java工程师 VS 优秀架构师

1 核心能力 1.1 要成为一名优秀的Java架构师 只懂技术还远远不够&#xff0c;懂技术/懂业务/懂管理的综合型人才&#xff0c;才是技术团队中的绝对核心。 不仅仅是架构师&#xff0c;所有的技术高端岗位&#xff0c;对人才的综合能力都有较高的标准。 架构路线的总设计师 规…...

Java:SpringBoot实现ApplicationEvent事件的监听和发布

通过发布订阅模式实现数据的异步处理&#xff0c;比如异步处理邮件发送 新建SpringBoot项目 项目结构 . ├── pom.xml └── src└── main├── java│ └── com│ └── example│ └── demo│ ├── Application.java│ …...

星戈瑞-Sulfo-Cyanine3 azide?磺酸基-Cy3-N3叠氮基水溶性染料

Sulfo-Cyanine3 azide&#xff1f; 品牌&#xff1a;星戈瑞 CAS号&#xff1a;2055138-89-9 外观&#xff1a; 暗红色晶体 分子量&#xff1a;720.83 分子式&#xff1a;C34H45N6NaO8S2 纯度&#xff1a;95% 储藏条件&#xff1a;-20C 下避光保存 Sulfo-Cyanine3 azide 是一种…...

十大经典排序算法(下)

&#x1f353;个人主页&#xff1a;bit.. &#x1f352;系列专栏&#xff1a;Linux(Ubuntu)入门必看 C语言刷题 数据结构与算法 HTML和CSS3 目录 1.6 快速排序 1. 算法步骤 2. 动图演示 3.代码实现 1.7 堆排序 1. 算法步骤 2. 动图演示 3. 代码实现 1.8 计数排…...

网络协议分析期末复习(四)

目录 0.前言 1.IP层对改善TCP性能支持的机制 2.TCP防止半开放连接的机制 3.TCP协议中强推位&#xff08;P&#xff09;和紧急位&#xff08;U&#xff09;的用法 4.TCP的流量控制和拥塞控制的异同点 异&#xff1a; &#xff08;1&#xff09;两者的特点不同&#xff1a;…...

Matlab对图像和视频的简单处理(图像视频文件读取和输出,转灰度图,取指定帧的图像)

文章目录1.图像文件的读取2.图像效果展示3.将彩色图转换为灰度图4.视频文件的读取5.读取视频中指定帧的图像6.图片文件的报错1.图像文件的读取 语法介绍&#xff1a; A imread(filename) A imread(filename, fmt)参数介绍&#xff1a; filename&#xff1a;要读取的图像文…...

ArrayList源码分析

ArrayList源码分析目标:一、 ArrayList的简介二、ArrayList原理分析2.1 ArrayList的数据结构源码分析2.2 ArrayList默认容量&最大容量2.3 为什么ArrayList查询快&#xff0c;增删慢&#xff1f;2.4 ArrayList初始化容量1、创建ArrayList对象分析&#xff1a;无参数2、创建A…...

SpringBoot IOC、DI、@Autowired、@Resource、作用域

一、初识Spring1.1 Spring是什么Spring是一个轻量级Java开发框架&#xff0c;目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的开源框架&#xff0c;为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构&#xff0c;Java开发者可以专…...

链表相关oj题

1.Leetcode203 移除链表元素 解题思路&#xff1a;从头节点开始进行元素删除&#xff0c;每删除一个元素&#xff0c;需要重新链接节点 struct ListNode* removeElements(struct ListNode* head, int val){struct ListNode*dummyheadmalloc(sizeof(struct ListNode));dummyhea…...

【Linux】操作系统(Operator System)

操作系统&#xff08;Operator System &#xff09;一、操作系统的概念二、操作系统的作用三、系统调用和库函数一、操作系统的概念 操作系统是一组控制和管理计算机软硬件资源&#xff0c;为用户提供便捷使用的计算机程序的集合&#xff0c;是配置在计算机硬件系统上的第一层…...

机器学习自学笔记——感知机

感知机预备知识 神经元 ​ 感知机算法最初是由科学家从脑细胞的神经凸起联想而来。如下图&#xff0c;我们拥有三个初始xxx值&#xff0c;x1,x2,x0x_1,x_2,x_0x1​,x2​,x0​。其中x01x_01x0​1为一个初始的常量&#xff0c;专业上称作“偏置”。每个xxx的值都会乘上一个权重…...

C++ Primer第五版_第三章习题答案(21~30)

文章目录练习3.21练习3.22练习3.23练习3.24练习3.25练习3.26练习3.27练习3.28练习3.29练习3.30练习3.21 请使用迭代器重做3.3.3节的第一个练习。 #include <vector> #include <iterator> #include <string> #include <iostream>using std::vector; usi…...

colmap+openmvs进行三维重建流程全记录

window下的colmapopenmvs进行三维重建流程全记录 1.colmap安装与配置 可参考&#xff1a;https://blog.csdn.net/weixin_44153180/article/details/129334018?spm1001.2014.3001.5501 2.openmvs安装与配置 可参考&#xff1a;https://blog.csdn.net/rdw1246010462/article…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

倒装芯片凸点成型工艺

UBM&#xff08;Under Bump Metallization&#xff09;与Bump&#xff08;焊球&#xff09;形成工艺流程。我们可以将整张流程图分为三大阶段来理解&#xff1a; &#x1f527; 一、UBM&#xff08;Under Bump Metallization&#xff09;工艺流程&#xff08;黄色区域&#xff…...