大家好,本周技术拆解官的第一篇文章,给大家带来的是我们的新主题《独家食用指南系列》。作为新主题的第一篇系列文章,这次给大家分享下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来说,最简单的方式当然是“懒人一键安装”了
sudo yum install sqlite-devel
安装好之后就可以和正常的数据库那样,大家就可以试试“增删改查”了,比如下面这样
- 首先我们指定一个db文件进入交互式的界面,指定db文件也是为了我们之后的建表的操作能够得到存储
当然在命令行的操作也并不是我们这次关心的重点,我们之后会利用Java的库来操作。
2 SQLite的调试工具
上面我们提到了SQLite它其实是一个关系型数据库,那么联想到MySQL的话,SQLite也有它的特定的可视化工具又或者可以说是调试工具,因为我们后面会利用这个工具来实时查看App的数据存储情况。
2.1 直接命令行调试
命令行调试的方式和上面我们说的在CentOS7操作数据库的方式基本类似,因为Android本身也是一个Linux系统,相关命令也是一致的。如果不嫌麻烦的话就是可以直接adb shell
进手机系统去操作。
2.2 SQLiteStudio工具
相比于命令行我想更多人会选择可视化的工具来操作,这里我推荐一下我自己目前在使用的工具SQLiteStudio,其实在选择工具的时候我也是在很多工具中纠结了一番,不过其他工具不是不能实时连接Android机调试就是在安全加密方面功能方面差一截,所以最后直接选择了SQLiteStudio了。
最新版本的SQLiteStudio是SQLiteStudio3.2,地址:https://sqlitestudio.pl/,
大家可以去官网下载,这也是个开源项目,要是大家有问题也可以去Github打扰那位大佬。
整体界面是这样的
照例演示下添加新数据库的流程
Database->Add a Database->打开自己选定好的db文件即可。
3 SQLite 基础类介绍
上面我们讲了SQLite的定义以及调试工具,下面我们要正式讲讲SQLite的使用了,这一篇我们暂且不深入源码,之后我会单独出一篇源码分析。在讲如何使用SQLite数据库之前,有必要介绍一下SQLite两个重要的类:SQLiteDatabase 和 SQLiteOpenHelper,这是SQLite数据库API中最基础的两个类。
3.1 SQLiteDatabase
在Android的自带的SQLite库中,SQLite所有的操作都来源于SQLiteDatabase ,另一个类SQLiteOpenHelper也是基于该类衍生而来进行数据库的创建和版本管理的。我们简单看看这个类的分析
可以发现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提供了两个创建数据库的方法
从源码中我们可以看到,这两个方法有个共同点,也就是都调用了getDatabaseLocked这个方法,我们再看getDatabaseLocked这个方法
根据上面的分析我们可以看到SQLiteOpenHelper是如何一步步通过调用SQLiteDatabase的方法来生成一个db实例的,也就表明了SQLiteOpenHelper是一个更高维度的SQLiteDatabase的封装。
3.4 SQLiteDatabase对象生成流程
总结了SQLiteOpenHelper和SQLiteDatabase之间的关系后,我们捋一下SQLiteDatabase对象的生成流程,也可以当成我们之后在开发过程中使用的SQLite的一个模板,我们可以以下三种方式可以来生成SQLiteDatabase。
- 继承SQLiteOpenHelper,调用getWritableDatabase / getReadableDatabase打开或创建数据库(推荐初学者使用)
- 调用SQLiteDatabase.openOrCreateDatabase打开或创建数据库
- 调用Context.openOrCreateDatabase打开或创建数据库
三种方法最终都是要调用SQLiteDatabase.openDatabase方法
既然都调用了SQLiteDatabase.openDatabase,那我们看看它的源码
解释一下各个参数
- 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文件路径相关的
final File filePath = mContext.getDatabasePath(mName);
这段代码我们得到的路径是
/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,如下
4.2 创建SQLiteCattleActivity
创建好了MySQLiteOpenHelper之后,我们需要在一个新的Activity中来使用它
这里我们使用两种方法来创建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选项。
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界面进行远程连接了。
添加的步骤和之前类似,不过数据类型方面我们选择Android SQLite,之后在选择db文件上,我们选择port forwarding,连接到某台手机的12121端口(也就是之前说的)SQLiteStudioService的实例监听的端口,之后就可以愉快的调试了。
5 食用荐语
以上就是关于Android端SQlite的浅尝辄止的独家食用指南了,由于篇幅问题,只是简单的写了几个方面,不过相信大家也能懂得SQlite开发的基本流程了。当然,这只是SQlite这部分的第一篇文章,之后还有两篇文章会关注另外两个方面,包括SQlite的安全版本以及SQlite的实现源码分析。