ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
1. SQLite轻装上阵 SQLite是一个轻量级的数据库,它和笔者之前接触的SQLServer或Oracle DB比起来,犹如蚂蚁和大象的区别。它“轻”到什么程度呢?笔者总结了SQLite具有的两个特点: - 从代码上看,SQLite所有的功能都实现在Sqlite3.c中,而头文件Sqlite3.h定义了它所支持的API。其中,Sqlite3.c文件包含12万行左右的代码,相当于一个中等偏小规模的程序。 - 从使用者角度的来说,SQLite编译完成后将生成一个libsqlite.so,大小仅为300多KB。 SQLite确实够轻,但这个“轻”还不是本节标题“轻装上阵”一词中的“轻”。为什么?此处先向读者们介绍一个直接使用SQLite API开发的Android native程序示例,该示例最终编译成的二进制可执行程序名为sqlitetest。 >[info] **注意**:本书后文所说的SQLite API特指libsqlite.so提供的Native层的API。 使用SQLite API开发的Android native程序示例的代码如下: **SqliteTest.cpp::libsqlite示例** ~~~ #include <unistd.h> #include <sqlite3.h> //包含sqlite API头文件,这里的3是SQLite的版本号 #include <stdlib.h> #define LOG_TAG "SQLITE_TEST" //定义该进程logcat输出的标签 #include <utils/Log.h> #ifndef NULL #defineNULL (0) #endif //声明数据库文件的路径 #define DB_PATH"/mnt/sdcard/sqlite3test.db" /* 声明一个全局的SQLite句柄,开发者无需了解该数据结构的具体内容,只要知道它代表了使用者 和数据库的一种连接关系即可。以后凡是针对特定数据库的操作,都需要传入对应的SQLite句柄 */ static sqlite3* g_pDBHandle = NULL; /* 定义一个宏,用于检测SQLite API调用的返回值,如果value不等于expectValue,则打印警告, 并退出程序。注意,进程退出后,系统会自动回收分配的内存资源。对如此简单的例子来说,读者大可 不必苛责。其中,sqlite3_errmsg函数用于打印错误信息 */ #define CHECK_DB_ERR(value,expectValue) \ do \ { \ if(value!= expectValue)\ {\ LOGE("Sqlite db fail:%s",g_pDBHandle==NULL?"db not \ connected":sqlite3_errmsg(g_pDBHandle));\ exit(0);\ }\ }while(0) int main(int argc, char* argv[]) { LOGD("Delete old DB file"); unlink(DB_PATH);//先删除旧的数据库文件 LOGD("Create new DB file"); /* 调用sqlite3_open创建一个数据库,并将和该数据库的连接环境保存在全局的SQLite句柄 g_pDBHandle中,以后操作g_pDBHandle就是操作DB_PATH对应的数据库 */ int ret =sqlite3_open(DB_PATH,&g_pDBHandle); CHECK_DB_ERR(ret,SQLITE_OK); LOGD("Create Table personal_info:"); /* 定义宏TABLE_PERSONAL_INFO,用于描述为本例数据库建立一个表所用的SQL语句, 不熟悉SQL语句的读者可先学习相关知识。从该宏的定义可知,将建立一个名为 personal_info的表,该表有4列,第一列是主键,类型是整型(SQLite中为INTEGER), 每加入一行数据该值会自动递增;第二列名为"name",数据类型是字符串(SQLite中为TEXT); 第三列名为“age”,数据类型是整型;第四列名为“sex”,数据类型是字符串 */ #defineTABLE_PERSONAL_INFO \ "CREATETABLE personal_info" \ "(ID INTEGER primary keyautoincrement," \ "nameTEXT," \ "age INTEGER,"\ "sex TEXT"\ ")" //打印TABLE_PERSONAL_INFO所对应的SQL语句 LOGD("\t%s\n",TABLE_PERSONAL_INFO); //调用sqlite3_exec执行前面那条SQL语句 ret =sqlite3_exec(g_pDBHandle,TABLE_PERSONAL_INFO,NULL,NULL,NULL); CHECK_DB_ERR(ret,SQLITE_OK); /* 定义插入一行数据所使用的SQL语句,注意最后一行中的问号,它表示需要在插入数据 前和具体的值绑定。插入数据库对应的SQL语句是标准的INSERT命令 */ LOGD("Insert one personal info into personal_info table"); #defineINSERT_PERSONAL_INFO \ "INSERT INTO personal_info"\ "(name,age,sex)"\ "VALUES"\ "(?,?,?)" //注意这一行语句中的问号 LOGD("\t%s\n",INSERT_PERSONAL_INFO); //sqlite3_stmt是SQLite中很重要的一个结构体,它代表了一条SQL语句 sqlite3_stmt* pstmt = NULL; /* 调用sqlite3_prepare初始化pstmt,并将其和INSERT_PERSONAL_INFO绑定。 也就是说,如果执行pstmt,就会执行INSERT_PERSONAL_INFO语句 */ ret =sqlite3_prepare(g_pDBHandle,INSERT_PERSONAL_INFO,-1,&pstmt,NULL); CHECK_DB_ERR(ret,SQLITE_OK); /* 调用sqlite3_bind_xxx为该pstmt中对应的问号绑定具体的值,该函数的第二个参数用于 指定第几个问号 */ ret =sqlite3_bind_text(pstmt,1,"dengfanping",-1,SQLITE_STATIC); ret =sqlite3_bind_int(pstmt,2,30); ret =sqlite3_bind_text(pstmt,3,"male",-1,SQLITE_STATIC); //调用sqlite3_step执行对应的SQL语句,该函数如果执行成功,我们的personal_info //表中将添加一条新记录,对应值为(1,dengfanping,30,male) ret =sqlite3_step(pstmt); CHECK_DB_ERR(ret,SQLITE_DONE); //调用sqlite3_finalize销毁该SQL语句 ret =sqlite3_finalize(pstmt); //下面将从表中查询name为"dengfanping"的person的age值 LOGD("select dengfanping's age from personal_info table"); pstmt =NULL; /* 重新初始化该pstmt,并将其和SQL语句“SELECT age FROM personal_info WHERE name = ?”绑定 */ ret =sqlite3_prepare(g_pDBHandle,"SELECT age FROM personal_info WHERE name =? ",-1,&pstmt,NULL); CHECK_DB_ERR(ret,SQLITE_OK); /* 绑定pstmt中第一个问号为字符串“dengfanping”,最终该SQL语句为 SELECTage FROM personal_info WHERE name = 'dengfanping' */ ret =sqlite3_bind_text(pstmt,1,"dengfanping",-1,SQLITE_STATIC); CHECK_DB_ERR(ret,SQLITE_OK); //执行这条查询语句 while(true)//在一个循环中遍历结果集 { ret =sqlite3_step(pstmt); if(ret ==SQLITE_ROW) { //从结果集中取出第一列(由于执行SELECT时只选择了age,故最终结果只有一列), //调用sqlite3_column_int返回结果集的第一列(从0开始)第一行的值 intmyage = sqlite3_column_int(pstmt, 0); LOGD("Gotdengfanping's age: %d\n",myage); } else //如果ret为其他值,则退出循环 break; } else LOGD("Find nothing\n"); //SELECT执行失败 ret =sqlite3_finalize(pstmt);//销毁pstmt if(g_pDBHandle) { LOGD("CloseDB"); //调用sqlite3_close关闭数据库连接并释放g_pDBHandle sqlite3_close(g_pDBHandle); g_pDBHandle = NULL; } return 0; } ~~~ 通过上述示例代码可发现,SQLite API的使用主要集中在以下几点上: - 创建代表指定数据库的sqlite3实例。 - 创建代表一条SQL语句的sqlite3_stmt实例,在使用过程中首先要调用sqlite3_prepare将其和一条代表SQL语句的字符串绑定。如该字符串含有通配符“?”,后续就需要通过sqlite3_bind_xxx函数为通配符绑定特定的值以生成一条完整的SQL语句。最终调用sqlite3_step执行这条语句。 - 如果是查询(即SELECT命令)命令,则需调用sqlite3_step函数遍历结果集,并通过sqlite3_column_xx等函数取出结果集中某一行指定列的值。 - 最后需调用sqlite3_finalize和sqlite3_close来释放sqlite3_stmt实例及sqlite3实例所占据的资源。 SQLite API的使用非常简单明了。不过很可惜,这份简单明了所带来的快捷,只供那些Native层程序开发者独享。对于Java程序员,他们只能使用Android在SQLite API之上所封装的SQLiteDatabase家族提供的类和相关API。笔者心中对这个封装的评价只有一个词 “叹为观止”,综合考虑到架构及系统的稳定性和可扩展性等各种情况,Android在 SQLite API之上进行了面向对象的封装和解耦等设计,最终呈现在大家面前的是一个庞大而复杂的SQLiteDatabase家族,其成员有61个之多(参阅frameworks/base/core/java/android/database目录中的文件)。 现在读者应该理解本节标题“SQLite轻装上阵”中“轻”的真正含义了。在后续的分析过程中,我们主要和SQLiteDatabase家族打交道。随着分析的深入,读者能逐渐见认识SQLiteDatabase的“厚重"。 2. SQLiteDatabase家族介绍 图7-4展示了SQLiteDatabase家族中的几位重要成员。 :-: ![](http://img.blog.csdn.net/20150803130857498?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-4 SQLitDatabase家族部分成员 图7-4中相关类的说明如下: - SQLiteOpenHelper是一个帮助(Utility)类,用于方便开发者创建和管理数据库。 - SQLiteQueryBuilder是一个帮助类,用于帮助开发者创建SQL语句。 - SQLiteDatabase代表SQLite数据库,它内部封装了一个Native层的sqlite3实例。 - Android提供了3个类SQLiteProgram、SQLiteQuery和SQLiteStatement用于描述和SQL语句相关的信息。从图7-4可知,SQLiteProgram是基类,它提供了一些API用于参数绑定。SQLiteQuery主要用于query查询操作,而SQLiteStatement用于query之外的一些操作(根据SDK的说明,如果SQLiteStatement用于query查询,其返回的结果集只能是1行*1列)。注意,在这3个类中,基类SQLiteProgram将保存一个指向Native层的sqlite3_stmt实例的变量,但是这个成员变量的赋值却和另外一个对开发者隐藏的类SQLiteComplieSql有关。从这个角度看,可以认为Native层sqlite3_stmt实例的封装是由SQLiteComplieSql完成的。这方面的知识在后文进行分析时即能见到。 - SQLiteClosable用于控制SQLiteDatabase家族中一些类的实例的生命周期,例如SQLiteDatabase实例和SQLiteQuery实例。每次使用这些实例对象前都需要调用acquireReference以增加引用计数,使用完毕后都需要调用releaseReferenece以减少引用计数。 * * * * * **提示**:读者见识了SQLiteDatabase家族中这几位成员后有何感想?是否觉得要真正搞清楚它们还需要花费一番工夫呢? * * * * * 下面来看MediaProvider是如何使用SQLiteDatabase的,重点关注SQLite数据库是如何创建的。