大家好,本周技术拆解官的第一篇文章,给大家带来的是我们的新主题《独家食用指南系列》。作为新主题的第一篇系列文章,这次给大家分享下AndroidSQLite的“食用指南”。之所以会拿SQLite作为系列的开篇文章,也是因为最近接触到逆向AndroidSQLite数据库的工作,查阅了很多资料,我也想给这次调研做下总结,所以接下来我会分三篇文章来给大家好好讲讲这次逆向AndroidSQLite数据库的经历,首当其冲的第一篇,也就是《独家食用指南系列|AndroidSQLite的浅尝辄止》

本篇文章使用到的项目源码都在我的个人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文件,相当于库与库之前是彼此隔离的。
  • 跨平台
    目前支持大部分操作系统,不至电脑操作系统更在众多的手机系统也是能够运行,比如:AndroidIOS
  • 多语言接口
    数据库支持多语言编程接口(在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的基本知识,大家大致了解就好,详细的信息可以看看它的WIKIhttps://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了。

最新版本的SQLiteStudioSQLiteStudio3.2,地址:https://sqlitestudio.pl/,
大家可以去官网下载,这也是个开源项目,要是大家有问题也可以去Github打扰那位大佬。

整体界面是这样的

照例演示下添加新数据库的流程

Database->Add a Database->打开自己选定好的db文件即可。

3 SQLite 基础类介绍

上面我们讲了SQLite的定义以及调试工具,下面我们要正式讲讲SQLite的使用了,这一篇我们暂且不深入源码,之后我会单独出一篇源码分析。在讲如何使用SQLite数据库之前,有必要介绍一下SQLite两个重要的类:SQLiteDatabaseSQLiteOpenHelper,这是SQLite数据库API中最基础的两个类。

3.1 SQLiteDatabase

Android的自带的SQLite库中,SQLite所有的操作都来源于SQLiteDatabase ,另一个类SQLiteOpenHelper也是基于该类衍生而来进行数据库的创建和版本管理的。我们简单看看这个类的分析


可以发现insertquery等熟悉的数据库操作的字眼,这些方法都是已经封装好的,我们只需要传入适当的参数即可完成诸如插入、更新、查询等操作。当然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

SQLiteOpenHelperSQLiteDatabase的辅助类,通过对SQLiteDatabase内部方法的封装简化了数据库创建与版本管理的操作。它是一个抽象类,一般情况下,我们需要继承并重写这两个父类方法:

  • onCreate
    在初次生成数据库时才会被调用,我们一般重写onCreate生成数据库表结构并添加一些应用使用到的初始化数据
  • onUpgrade
    当数据库版本有更新时会调用这个方法,我们一般会在这执行数据库更新的操作,例如字段更新、表的增加与删除等

此外父类方法中还有onConfigureonDowngradeonOpen,一般项目中很少用到它们,如果大家需要进一步了解可以去看看官方文档。

3.3 SQLiteOpenHelper与SQLiteDatabase的关系

介绍完了SQLiteOpenHelper以及SQLiteDatabase之后,那它们是怎么关联在一起的呢?我们从源码中来看看

SQLiteOpenHelper提供了两个创建数据库的方法

  • getWritableDatabase

  • getReadableDatabase

从源码中我们可以看到,这两个方法有个共同点,也就是都调用了getDatabaseLocked这个方法,我们再看getDatabaseLocked这个方法

  • 第一步

  • 第二步

  • 第三步

根据上面的分析我们可以看到SQLiteOpenHelper是如何一步步通过调用SQLiteDatabase的方法来生成一个db实例的,也就表明了SQLiteOpenHelper是一个更高维度的SQLiteDatabase的封装。

3.4 SQLiteDatabase对象生成流程

总结了SQLiteOpenHelperSQLiteDatabase之间的关系后,我们捋一下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和我们创建的MySQLiteOpenHelpergetWritableDatabase来创建

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的实例,监听Androidxxx端口,等待远程的SQLiteStudio来连接。

4.3.3 SQLiteStudio连接手机实时调试

配置好App之后,只需要启动App,便可以在目录下创建好db文件,然后我们就可以在SQLiteStudio界面进行远程连接了。

添加的步骤和之前类似,不过数据类型方面我们选择Android SQLite,之后在选择db文件上,我们选择port forwarding,连接到某台手机的12121端口(也就是之前说的)SQLiteStudioService的实例监听的端口,之后就可以愉快的调试了。

5 食用荐语

以上就是关于AndroidSQlite的浅尝辄止的独家食用指南了,由于篇幅问题,只是简单的写了几个方面,不过相信大家也能懂得SQlite开发的基本流程了。当然,这只是SQlite这部分的第一篇文章,之后还有两篇文章会关注另外两个方面,包括SQlite的安全版本以及SQlite的实现源码分析。