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数据库是如何创建的。
- 前言
- 第1章 搭建Android源码工作环境
- 1.1 Android系统架构
- 1.2 搭建开发环境
- 1.2.1 下载源码
- 1.2.2 编译源码
- 1.2.3 利用Eclipse调试system_process
- 1.3 本章小结
- 第2章 深入理解Java Binder和MessageQueue
- 2.1 概述
- 2.2 Java层中的Binder架构分析
- 2.2.1 Binder架构总览
- 2.2.2 初始化Java层Binder框架
- 2.2.3 addService实例分析
- 2.2.4 Java层Binder架构总结
- 2.3 心系两界的MessageQueue
- 2.3.1 MessageQueue的创建
- 2.3.2 提取消息
- 2.3.3 nativePollOnce函数分析
- 2.3.4 MessageQueue总结
- 2.4 本章小结
- 第3章 深入理解SystemServer
- 3.1 概述
- 3.2 SystemServer分析
- 3.2.1 main函数分析
- 3.2.2 Service群英会
- 3.3 EntropyService分析
- 3.4 DropBoxManagerService分析
- 3.4.1 DBMS构造函数分析
- 3.4.2 dropbox日志文件的添加
- 3.4.3 DBMS和settings数据库
- 3.5 DiskStatsService和DeviceStorageMonitorService分析
- 3.5.1 DiskStatsService分析
- 3.5.2 DeviceStorageManagerService分析
- 3.6 SamplingProfilerService分析
- 3.6.1 SamplingProfilerService构造函数分析
- 3.6.2 SamplingProfilerIntegration分析
- 3.7 ClipboardService分析
- 3.7.1 复制数据到剪贴板
- 3.7.2 从剪切板粘贴数据
- 3.7.3 CBS中的权限管理
- 3.8 本章小结
- 第4章 深入理解PackageManagerService
- 4.1 概述
- 4.2 初识PackageManagerService
- 4.3 PKMS的main函数分析
- 4.3.1 构造函数分析之前期准备工作
- 4.3.2 构造函数分析之扫描Package
- 4.3.3 构造函数分析之扫尾工作
- 4.3.4 PKMS构造函数总结
- 4.4 APK Installation分析
- 4.4.1 adb install分析
- 4.4.2 pm分析
- 4.4.3 installPackageWithVerification函数分析
- 4.4.4 APK 安装流程总结
- 4.4.5 Verification介绍
- 4.5 queryIntentActivities分析
- 4.5.1 Intent及IntentFilter介绍
- 4.5.2 Activity信息的管理
- 4.5.3 Intent 匹配查询分析
- 4.5.4 queryIntentActivities总结
- 4.6 installd及UserManager介绍
- 4.6.1 installd介绍
- 4.6.2 UserManager介绍
- 4.7 本章学习指导
- 4.8 本章小结
- 第5章 深入理解PowerManagerService
- 5.1 概述
- 5.2 初识PowerManagerService
- 5.2.1 PMS构造函数分析
- 5.2.2 init分析
- 5.2.3 systemReady分析
- 5.2.4 BootComplete处理
- 5.2.5 初识PowerManagerService总结
- 5.3 PMS WakeLock分析
- 5.3.1 WakeLock客户端分析
- 5.3.2 PMS acquireWakeLock分析
- 5.3.3 Power类及LightService类介绍
- 5.3.4 WakeLock总结
- 5.4 userActivity及Power按键处理分析
- 5.4.1 userActivity分析
- 5.4.2 Power按键处理分析
- 5.5 BatteryService及BatteryStatsService分析
- 5.5.1 BatteryService分析
- 5.5.2 BatteryStatsService分析
- 5.5.3 BatteryService及BatteryStatsService总结
- 5.6 本章学习指导
- 5.7 本章小结
- 第6章 深入理解ActivityManagerService
- 6.1 概述
- 6.2 初识ActivityManagerService
- 6.2.1 ActivityManagerService的main函数分析
- 6.2.2 AMS的 setSystemProcess分析
- 6.2.3 AMS的 installSystemProviders函数分析
- 6.2.4 AMS的 systemReady分析
- 6.2.5 初识ActivityManagerService总结
- 6.3 startActivity分析
- 6.3.1 从am说起
- 6.3.2 AMS的startActivityAndWait函数分析
- 6.3.3 startActivityLocked分析
- 6.4 Broadcast和BroadcastReceiver分析
- 6.4.1 registerReceiver流程分析
- 6.4.2 sendBroadcast流程分析
- 6.4.3 BROADCAST_INTENT_MSG消息处理函数
- 6.4.4 应用进程处理广播分析
- 6.4.5 广播处理总结
- 6.5 startService之按图索骥
- 6.5.1 Service知识介绍
- 6.5.2 startService流程图
- 6.6 AMS中的进程管理
- 6.6.1 Linux进程管理介绍
- 6.6.2 关于Android中的进程管理的介绍
- 6.6.3 AMS进程管理函数分析
- 6.6.4 AMS进程管理总结
- 6.7 App的 Crash处理
- 6.7.1 应用进程的Crash处理
- 6.7.2 AMS的handleApplicationCrash分析
- 6.7.3 AppDeathRecipient binderDied分析
- 6.7.4 App的Crash处理总结
- 6.8 本章学习指导
- 6.9 本章小结
- 第7章 深入理解ContentProvider
- 7.1 概述
- 7.2 MediaProvider的启动及创建
- 7.2.1 Context的getContentResolver函数分析
- 7.2.2 MediaStore.Image.Media的query函数分析
- 7.2.3 MediaProvider的启动及创建总结
- 7.3 SQLite创建数据库分析
- 7.3.1 SQLite及SQLiteDatabase家族
- 7.3.2 MediaProvider创建数据库分析
- 7.3.3 SQLiteDatabase创建数据库的分析总结
- 7.4 Cursor 的query函数的实现分析
- 7.4.1 提取query关键点
- 7.4.2 MediaProvider 的query分析
- 7.4.3 query关键点分析
- 7.4.4 Cursor query实现分析总结
- 7.5 Cursor close函数实现分析
- 7.5.1 客户端close的分析
- 7.5.2 服务端close的分析
- 7.5.3 finalize函数分析
- 7.5.4 Cursor close函数总结
- 7.6 ContentResolver openAssetFileDescriptor函数分析
- 7.6.1 openAssetFileDescriptor之客户端调用分析
- 7.6.2 ContentProvider的 openTypedAssetFile函数分析
- 7.6.3 跨进程传递文件描述符的探讨
- 7.6.4 openAssetFileDescriptor函数分析总结
- 7.7 本章学习指导
- 7.8 本章小结
- 第8章 深入理解ContentService和AccountManagerService
- 8.1 概述
- 8.2 数据更新通知机制分析
- 8.2.1 初识ContentService
- 8.2.2 ContentResovler 的registerContentObserver分析
- 8.2.3 ContentResolver的 notifyChange分析
- 8.2.4 数据更新通知机制总结和深入探讨
- 8.3 AccountManagerService分析
- 8.3.1 初识AccountManagerService
- 8.3.2 AccountManager addAccount分析
- 8.3.3 AccountManagerService的分析总结
- 8.4 数据同步管理SyncManager分析
- 8.4.1 初识SyncManager
- 8.4.2 ContentResolver 的requestSync分析
- 8.4.3 数据同步管理SyncManager分析总结
- 8.5 本章学习指导
- 8.6 本章小结