独家食用指南系列|Android端SQLite的浅尝辄止
![](https://img-blog.csdnimg.cn/20201020210301905.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)大家好,本周技术拆解官的第一篇文章,给大家带来的是我们的新主题《**独家食用指南系列**》。作为新主题的第一篇系列文章,这次给大家分享下**Android**端**SQLite**的“**食用指南**”。之所以会拿**SQLite**作为系列的开篇文章,也是因为最近接触到逆向**Android**端**SQLite**数据库的工作,查阅了很多资料,我也想给这次调研做下总结,所以接下来我会分三篇文章来给大家好好讲讲这次逆向**Android**端**SQLite**数据库的经历,首当其冲的第一篇,也就是《**独家食用指南系列**|**Android**端**SQLite**的浅尝辄止》
> 本篇文章使用到的项目源码都在我的个人**Github**上面:https://github.com/lateautumn4lin/TechPaoding/tree/main/practice_demo/Cattle
# 1 认识SQLite
## 1.1 SQLite定义
**SQLite**是一款轻量级的关系型数据库,为什么是轻量级呢?主要是因为它占用资源很少,通常只需要几百K的内存就足够了,也因为它的结构足够简单,运算速度非常快,特别适合在移动设备上使用。
## 1.2 SQLite特点
- 轻量级
使用 **SQLite** 只需要带一个动态库(也就是**NDK**开发的**SO**库),就可以享受它的全部功能,而且那个动态库的尺寸想当小,因此对于移动端的使用来说可谓是“百利而无一害”。
- 独立性
数据库的核心引擎不需要依赖第三方软件,也不需要所谓的“安装”。
- 隔离性
数据库中所有的信息(比如表、视图、触发器等)都包含在一个文件夹内,方便管理和维护,具体的表现形式就是每个数据库就是一个**db**文件,相当于库与库之前是彼此隔离的。
- 跨平台
目前支持大部分操作系统,不至电脑操作系统更在众多的手机系统也是能够运行,比如:**Android**和**IOS**。
- 多语言接口
数据库支持多语言编程接口(在**Android**端话一般会使用**JDK**或者**NDK**来开发)。
- 安全性
数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据(涉及并发问题的处理方式类似于另一个关系型数据库-**MySQL**)。
- 弱类型的字段
同一列中的数据可以是不同类型(编码自由者的福音、编码强迫症的噩梦)
## 1.3 SQLite数据类型
**SQLite**具有以下五种常用的数据类型,都是比较常见的基本类型:
| 类型|含义|
|--|--|
| NULL | 值是一个 NULL 值 |
| INTEGER| 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中 |
| REAL| 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字 |
| TEXT| 值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储 |
| BLOB| 值是一个 blob 数据,完全根据它的输入存储 |
>以上就是关于**SQLite**的基本知识,大家大致了解就好,详细的信息可以看看它的**WIKI**:https://zh.wikipedia.org/wiki/SQLite
## 1.4 SQLite安装
关于**SQLite**的安装,这里我们举**CentOS7**来说,最简单的方式当然是“懒人一键安装”了
```bash
sudo yum install sqlite-devel
```
安装好之后就可以和正常的数据库那样,大家就可以试试“增删改查”了,比如下面这样
- 首先我们指定一个**db文件**进入交互式的界面,指定**db文件**也是为了我们之后的建表的操作能够得到存储
![](https://img-blog.csdnimg.cn/20201021105508733.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
- 然后就是正常对于数据库的“增删改查”了
![](https://img-blog.csdnimg.cn/20201021105628903.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
当然在命令行的操作也并不是我们这次关心的重点,我们之后会利用**Java**的库来操作。
# 2 SQLite的调试工具
上面我们提到了**SQLite**它其实是一个关系型数据库,那么联想到**MySQL**的话,**SQLite**也有它的特定的可视化工具又或者可以说是调试工具,因为我们后面会利用这个工具来实时查看**App**的数据存储情况。
## 2.1 直接命令行调试
命令行调试的方式和上面我们说的在**CentOS7**操作数据库的方式基本类似,因为**Android**本身也是一个**Linux**系统,相关命令也是一致的。如果不嫌麻烦的话就是可以直接`adb shell`进手机系统去操作。
## 2.2 SQLiteStudio工具
![](https://img-blog.csdnimg.cn/20201020220526117.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
相比于命令行我想更多人会选择可视化的工具来操作,这里我推荐一下我自己目前在使用的工具**SQLiteStudio**,其实在选择工具的时候我也是在很多工具中纠结了一番,不过其他工具不是不能实时连接**Android**机调试就是在安全加密方面功能方面差一截,所以最后直接选择了**SQLiteStudio**了。
>最新版本的**SQLiteStudio**是**SQLiteStudio3.2**,地址:https://sqlitestudio.pl/,
大家可以去官网下载,这也是个开源项目,要是大家有问题也可以去**Github**打扰那位大佬。
整体界面是这样的
![](https://img-blog.csdnimg.cn/20201020220105882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
照例演示下添加新数据库的流程
**Database**->**Add a Database**->打开自己选定好的**db文件**即可。
# 3 SQLite 基础类介绍
上面我们讲了**SQLite**的定义以及调试工具,下面我们要正式讲讲**SQLite**的使用了,这一篇我们暂且不深入源码,之后我会单独出一篇源码分析。在讲如何使用**SQLite**数据库之前,有必要介绍一下**SQLite**两个重要的类:**SQLiteDatabase** 和 **SQLiteOpenHelper**,这是**SQLite**数据库**API**中最基础的两个类。
## 3.1 SQLiteDatabase
在**Android**的自带的**SQLite**库中,**SQLite**所有的操作都来源于**SQLiteDatabase** ,另一个类**SQLiteOpenHelper**也是基于该类衍生而来进行数据库的创建和版本管理的。我们简单看看这个类的分析
![](https://img-blog.csdnimg.cn/20201020221647885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
![](https://img-blog.csdnimg.cn/20201020221742897.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
可以发现**insert**、**query**等熟悉的数据库操作的字眼,这些方法都是已经封装好的,我们只需要传入适当的参数即可完成诸如插入、更新、查询等操作。当然**SQLiteDatabase**也提供了直接执行**SQL**语句的方法,如
- execSQL
`db.execSQL("create table if not exists " + TABLE_NAME +"(id text primary key,name text)");`
- rawQuery
` db.rawQuery("SELECT * FROM test", null);`
## 3.2 SQLiteOpenHelper
**SQLiteOpenHelper**是**SQLiteDatabase**的辅助类,通过对**SQLiteDatabase**内部方法的封装简化了数据库创建与版本管理的操作。它是一个抽象类,一般情况下,我们需要继承并重写这两个父类方法:
- onCreate
在初次生成数据库时才会被调用,我们一般重写**onCreate**生成数据库表结构并添加一些应用使用到的初始化数据
- onUpgrade
当数据库版本有更新时会调用这个方法,我们一般会在这执行数据库更新的操作,例如字段更新、表的增加与删除等
此外父类方法中还有**onConfigure**、**onDowngrade**、**onOpen**,一般项目中很少用到它们,如果大家需要进一步了解可以去看看官方文档。
## 3.3 SQLiteOpenHelper与SQLiteDatabase的关系
介绍完了**SQLiteOpenHelper**以及**SQLiteDatabase**之后,那它们是怎么关联在一起的呢?我们从源码中来看看
**SQLiteOpenHelper**提供了两个创建数据库的方法
- getWritableDatabase
![](https://img-blog.csdnimg.cn/20201021105757609.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
- getReadableDatabase
![](https://img-blog.csdnimg.cn/2020102110581544.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
从源码中我们可以看到,这两个方法有个共同点,也就是都调用了**getDatabaseLocked**这个方法,我们再看**getDatabaseLocked**这个方法
- 第一步
![](https://img-blog.csdnimg.cn/20201021105844801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
- 第二步
![](https://img-blog.csdnimg.cn/20201021105907427.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
- 第三步
![](https://img-blog.csdnimg.cn/20201021105928527.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
根据上面的分析我们可以看到**SQLiteOpenHelper**是如何一步步通过调用**SQLiteDatabase**的方法来生成一个**db实例**的,也就表明了**SQLiteOpenHelper**是一个更高维度的**SQLiteDatabase**的封装。
## 3.4 SQLiteDatabase对象生成流程
总结了**SQLiteOpenHelper**和**SQLiteDatabase**之间的关系后,我们捋一下**SQLiteDatabase**对象的生成流程,也可以当成我们之后在开发过程中使用的**SQLite**的一个模板,我们可以以下三种方式可以来生成**SQLiteDatabase**。
- 继承**SQLiteOpenHelper**,调用**getWritableDatabase** / **getReadableDatabase**打开或创建数据库(推荐初学者使用)
- 调用**SQLiteDatabase.openOrCreateDatabase**打开或创建数据库
- 调用**Context.openOrCreateDatabase**打开或创建数据库
三种方法最终都是要调用**SQLiteDatabase.openDatabase**方法
![](https://img-blog.csdnimg.cn/20201020231838727.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
既然都调用了**SQLiteDatabase.openDatabase**,那我们看看它的源码
![](https://img-blog.csdnimg.cn/20201021105958274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
解释一下各个参数
- **String path**
数据库文件路径
- **CursorFactory factory**
用于构造自定义的Cursor子类对象,在执行查询操作时返回,若传入 null 则使用默认的factory构造Cursor
- **int flags**
用于控制数据库的访问模式,可传入的参数有
- **CREATE_IF_NECESSARY**:当数据库不存在时创建该数据库文件
- **ENABLE_WRITE_AHEAD_LOGGING**:绕过数据库的锁机制,以多线程操作数据库的方式进行读写
- **NO_LOCALIZED_COLLATORS**:打开数据库时,不根据本地化语言对数据库进行排序
- **OPEN_READONLY**:以只读方式打开数据库
- **OPEN_READWRITE**:以读写方式打开数据库
- **DatabaseErrorHandler errorHandler**
当检测到数据库损坏时进行回调的接口,一般没有特殊需要传入 null 即可
可以看到,我们通过**openDatabase**生成**SQLiteDatabase**的实例,并且将**db实例**的状态置为开启,最终返回**db实例**。
## 3.5 创建数据库的路径
**SQLiteDatabase**源码中有一行代码是关于**db文件**路径相关的
```java
final File filePath = mContext.getDatabasePath(mName);
```
这段代码我们得到的路径是
```java
/data/data/<package_name>/databases/
```
一般情况下我们在创建数据库时**path参数**只需传入“**xxx.db**”,系统自动会在该默认路径下创建名为“**xxx.db**”的数据库文件,这样做最大的好处就是安全,因为从**Android7**开始,**Android**的策略就限制了**App**彼此间的访问权限,这也使**App**的安全性得到了保证。
# 4 SQLite Demo开发
通过上面的关于**SQLiteDatabase**类的基本的了解,下面我们直接上手搞个**Demo**
## 4.1 创建MySQLiteOpenHelper
第一步我们使用最快捷的创建**SQLiteDatabase**的方式,也就是继承**SQLiteOpenHelper**来创建一个我们自己**MySQLiteOpenHelper**,如下
![](https://img-blog.csdnimg.cn/20201021110025594.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
## 4.2 创建SQLiteCattleActivity
创建好了**MySQLiteOpenHelper**之后,我们需要在一个新的**Activity**中来使用它
![](https://img-blog.csdnimg.cn/2020102111010174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
这里我们使用两种方法来创建**db实例**,分别是**SQLiteDatabase** 自身的**openOrCreateDatabase**和我们创建的**MySQLiteOpenHelper**的**getWritableDatabase**来创建
## 4.3 连接SQLiteStudio实时调试
如上面的两个步骤,我们开发好了基本的**App**,实现了基本的点击按钮完成“增删改查的功能”,但毕竟数据库是在手机里面,我们要如何进行实时的数据库调试呢?下面介绍一种方法是基于**SQLiteStudio**进行的实时调试方案
由于**SQLite**数据库是以**db文件**的形式保存在手机目录上的,我们无法直接便捷的实时获取到**SQLite**的内容,因此我们需要借助**SQLiteStudio**的端到端通信功能来获取**SQLite**的数据。
### 4.3.1 SQLiteStudio环境配置
第一步我们需要导出**SQLiteStudio **的**Remote Jar包**,导出的步骤是**Tools**->**Get Android Connect Jar File**来获取到**Jar包**,并把**Jar包**放在项目的**根目录/libs**下面。
我们需要开启**SQLiteStudio **的**SQLite**数据库调试的权限,步骤是通过**Tools**->**Open configuration dialog**打开配置界面,在插件栏勾选**SQLite**选项。
![](https://img-blog.csdnimg.cn/20201021001028930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
### 4.3.2 Android项目环境配置
配置好了**SQLiteStudio**之后,下面我们来配置我们的**App**项目,我们之前已经引入了**Remote Jar包**,放在我们的**根目录/libs**下面,下面我们还需要项目的**build.gradle文件**中在写入`implementation fileTree(include: ['*.jar'], dir: 'libs')`来保证**libs**下所有的**Jar包**能够正常的导入。
导入**Jar包**之后我们只需要在我们的**Activity**中加上一行代码`SQLiteStudioService.instance().start(this);`即可在我们的**Activity**一开启的时候就启动**SQLiteStudioService**的实例,监听**Android**的**xxx端口**,等待远程的**SQLiteStudio**来连接。
### 4.3.3 SQLiteStudio连接手机实时调试
配置好**App**之后,只需要启动**App**,便可以在目录下创建好**db文件**,然后我们就可以在**SQLiteStudio**界面进行远程连接了。
![](https://img-blog.csdnimg.cn/2020102100184627.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
![](https://img-blog.csdnimg.cn/2020102100184636.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzExNjkxMA==,size_16,color_FFFFFF,t_70#pic_center)
添加的步骤和之前类似,不过数据类型方面我们选择**Android SQLite**,之后在选择**db文件**上,我们选择**port forwarding**,连接到某台手机的**12121端口**(也就是之前说的)**SQLiteStudioService**的实例监听的端口,之后就可以愉快的调试了。
# 5 食用荐语
以上就是关于**Android**端**SQlite**的浅尝辄止的**独家食用指南**了,由于篇幅问题,只是简单的写了几个方面,不过相信大家也能懂得**SQlite**开发的基本流程了。当然,这只是**SQlite**这部分的第一篇文章,之后还有两篇文章会关注另外两个方面,包括**SQlite**的安全版本以及**SQlite**的实现源码分析。 arklearn 发表于 2020-10-29 14:18
感谢楼主的分享,我觉得可以去尝试学习这几个类,然后,改写一下之前的python sqlite 的helper
嗯嗯,可以尝试下,虽然这是android端的,不过sqlite的总体使用逻辑是相同的 lateautumn4lin 发表于 2020-10-29 14:33
嗯嗯,可以尝试下,虽然这是android端的,不过sqlite的总体使用逻辑是相同的
联系方式多少 沙发走起,感谢LZ!!! 一杆小淫枪 发表于 2020-10-26 17:03
沙发走起,感谢LZ!!!
感谢老哥 这个是学习教程吖,得好好瞅瞅 又想起培训机构的基础教程……:'(weeqw magic2019 发表于 2020-10-26 21:00
这个是学习教程吖,得好好瞅瞅
感谢老哥
xdfg 发表于 2020-10-26 21:34
又想起培训机构的基础教程……
是什么让你想起了{:1_929:} 不错,一会细看。 篇头图片很有意思 先看一会试一试