open database:
*使用ContextImpl#openOrCreateDatabase(),返回一个SQLiteDatabase对象。
*可以去继承SQLiteOpenHelper,然后调用getWritableDatabase()可以获取SQLiteDatabase,使用SQLiteOpenHelper去获取,那么就要复写onCreate和onUpgrade方法。
分析SQLiteOpenHelper#getWritableDatabase():
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
return mDatabase;
}
}
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
try {
if (DEBUG_STRICT_READONLY && !writable) {
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else {
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);//创建数据库单例对象,后面会重点分析
}
}
onConfigure(db);
final int version = db.getVersion();//获取版本号
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
db.beginTransaction();
try {
if (version == 0) {//如果版本号为0,则是第一次创建数据库,需要创建数据库文件。就要调用onCreate,所以之后就不用调用onCreate了
onCreate(db);
} else {
if (version > mNewVersion) {//如果version
复写SQLiteOpenHelper的onCreate和onUpgrade:
onCreate中一般就是创建数据库表格。可以使用CREATE table IF NOT EXISTS
onUpgrade,看看SettingsProvider的DatabaseHelper#onUpgrade():
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
Log.w(TAG, "Upgrading settings database from version " + oldVersion + " to "
+ currentVersion);
int upgradeVersion = oldVersion;
// Pattern for upgrade blocks:
//
// if (upgradeVersion == [the DATABASE_VERSION you set] - 1) {
// .. your upgrade logic..
// upgradeVersion = [the DATABASE_VERSION you set]
// }
if (upgradeVersion == 20) {
/*
* Version 21 is part of the volume control refresh. There is no
* longer a UI-visible for setting notification vibrate on/off (in
* our design), but the functionality still exists. Force the
* notification vibrate to on.
*/
loadVibrateSetting(db, true);
upgradeVersion = 21;
}
if (upgradeVersion < 22) {
upgradeVersion = 22;
// Upgrade the lock gesture storage location and format
upgradeLockPatternLocation(db);
}
if (upgradeVersion < 23) {
db.execSQL("UPDATE favorites SET iconResource=0 WHERE iconType=0");
upgradeVersion = 23;
}
if (upgradeVersion == 23) {
db.beginTransaction();
try {
db.execSQL("ALTER TABLE favorites ADD spanX INTEGER");
db.execSQL("ALTER TABLE favorites ADD spanY INTEGER");
// Shortcuts, applications, folders
db.execSQL("UPDATE favorites SET spanX=1, spanY=1 WHERE itemType<=0");
// Photo frames, clocks
db.execSQL(
"UPDATE favorites SET spanX=2, spanY=2 WHERE itemType=1000 or itemType=1002");
// Search boxes
db.execSQL("UPDATE favorites SET spanX=4, spanY=1 WHERE itemType=1001");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
upgradeVersion = 24;
}
//... ...太多了省略一些
if (upgradeVersion < 116) {
if (mUserHandle == UserHandle.USER_OWNER) {
db.beginTransaction();
SQLiteStatement stmt = null;
try {
stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
+ " VALUES(?,?);");
loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
if (stmt != null) stmt.close();
}
}
upgradeVersion = 116;
}
if (upgradeVersion < 117) {
db.beginTransaction();
try {
String[] systemToSecure = {
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED
};
moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, systemToSecure, true);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
upgradeVersion = 117;
}
if (upgradeVersion < 118) {
// Reset rotation-lock-for-accessibility on upgrade, since it now hides the display
// setting.
db.beginTransaction();
SQLiteStatement stmt = null;
try {
stmt = db.compileStatement("INSERT OR REPLACE INTO system(name,value)"
+ " VALUES(?,?);");
loadSetting(stmt, Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
if (stmt != null) stmt.close();
}
upgradeVersion = 118;
}
// *** Remember to update DATABASE_VERSION above!
if (upgradeVersion != currentVersion) {
Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion
+ ", must wipe the settings provider");
db.execSQL("DROP TABLE IF EXISTS global");
db.execSQL("DROP TABLE IF EXISTS globalIndex1");
db.execSQL("DROP TABLE IF EXISTS system");
db.execSQL("DROP INDEX IF EXISTS systemIndex1");
db.execSQL("DROP TABLE IF EXISTS secure");
db.execSQL("DROP INDEX IF EXISTS secureIndex1");
db.execSQL("DROP TABLE IF EXISTS gservices");
db.execSQL("DROP INDEX IF EXISTS gservicesIndex1");
db.execSQL("DROP TABLE IF EXISTS bluetooth_devices");
db.execSQL("DROP TABLE IF EXISTS bookmarks");
db.execSQL("DROP INDEX IF EXISTS bookmarksIndex1");
db.execSQL("DROP INDEX IF EXISTS bookmarksIndex2");
db.execSQL("DROP TABLE IF EXISTS favorites");
onCreate(db);
// Added for diagnosing settings.db wipes after the fact
String wipeReason = oldVersion + "/" + upgradeVersion + "/" + currentVersion;
db.execSQL("INSERT INTO secure(name,value) values('" +
"wiped_db_reason" + "','" + wipeReason + "');");
}
}
在SQLiteOpenHelper#getDatabaseLocked()中调用完onUpgrade后,会调用SQLiteDatabase#setVersion(),更新版本号。这个新的版本号在构造SQLiteOpenHelper时传入,也就是其子类调用父类构造方法传入。所以使用这种方法升级时,在onUpGrade方法中做任何操作,如新增或删除一行,新建或删除一个表,新增或删除一列。可以参考SettingsProvider的DatabaseHelper的使用。SettingsProvider中还使用了SQLiteStatement去构造操作语句,最后调用exec方法执行该语句。那这一段来解析:
if (upgradeVersion < 118) {//本来数据库版本号是117,更新代码的时候,构造DatabaseOpenHelper传入版本号118,就会进入这里
// Reset rotation-lock-for-accessibility on upgrade, since it now hides the display
// setting.
db.beginTransaction();每次使用SQLiteDatabase执行一次操作时,如果使用transaction,就是记录者模式,可以回退。
SQLiteStatement stmt = null;
try {
//插入或者覆盖一行到system表中,更新字段name和value,后面会使用bindxxx方法绑定对应字段的数据
stmt = db.compileStatement("INSERT OR REPLACE INTO system(name,value)"
+ " VALUES(?,?);");
loadSetting(stmt, Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0);
db.setTransactionSuccessful();//transaction标准操作
} finally {
db.endTransaction();//transaction标准操作
if (stmt != null) stmt.close();//标准操作
}
upgradeVersion = 118;
}
看看绑定数据过程,在loadSetting()中:
private void loadSetting(SQLiteStatement stmt, String key, Object value) {
stmt.bindString(1, key);//绑定第一个字段的数据
stmt.bindString(2, value.toString());第二字段数据
stmt.execute();//执行该SQL语句
}
SQLiteDatabase家族中的几位重要成员。
SQLitDatabase家族部分成员
相关类的说明如下:
SQLiteOpenHelper是一个帮助(Utility)类,用于方便开发者创建和管理数据库。
SQLiteQueryBuilder是一个帮助类,用于帮助开发者创建SQL语句。
SQLiteDatabase代表SQLite数据库,它内部封装了一个Native层的sqlite3实例。
Android提供了3个类SQLiteProgram、SQLiteQuery和SQLiteStatement用于描述和SQL语句相关的信息。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#openDatabase()方法中,调用关系SQLiteDatabase#openDatabase() ->open()->openInner() ->SQLiteConnectionPool#open()->SQLiteConnection#open()->nativeopen()
看看SQLiteConnection#open():
private void open() {
//mConnectionPtr是一个long类型,保存的是android_base_SQLiteConnection.cpp的Connection机构体实例的指针
mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
mConfiguration.label,
SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
setPageSize();
setForeignKeyModeFromConfiguration();
setWalModeFromConfiguration();
setJournalSizeLimit();
setAutoCheckpointInterval();
setLocaleFromConfiguration();
// Register custom functions.
final int functionCount = mConfiguration.customFunctions.size();
for (int i = 0; i < functionCount; i++) {
SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
nativeRegisterCustomFunction(mConnectionPtr, function);
}
}
下面分析一下SQLiteConnection的jni接口nativeopen(),该jni方法定义在framework/base/core/jni/android_base_SQLiteConnection.cpp中,源码如下:
static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
int sqliteFlags;
if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
} else if (openFlags & SQLiteConnection::OPEN_READONLY) {
sqliteFlags = SQLITE_OPEN_READONLY;
} else {
sqliteFlags = SQLITE_OPEN_READWRITE;
}
const char* pathChars = env->GetStringUTFChars(pathStr, NULL);
String8 path(pathChars);
env->ReleaseStringUTFChars(pathStr, pathChars);
const char* labelChars = env->GetStringUTFChars(labelStr, NULL);
String8 label(labelChars);
env->ReleaseStringUTFChars(labelStr, labelChars);
sqlite3* db;
//在这里调用sqlite3.c的api接口,把得到的连接的指针保存在db中。记得这里是要传引用,就是db的引用,db的引用就是&db,如果直接传db,就是传值了
int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL);
if (err != SQLITE_OK) {
throw_sqlite3_exception_errcode(env, err, "Could not open database");
return 0;
}
// Check that the database is really read/write when that is what we asked for.
if ((sqliteFlags & SQLITE_OPEN_READWRITE) && sqlite3_db_readonly(db, NULL)) {
throw_sqlite3_exception(env, db, "Could not open the database in read/write mode.");
sqlite3_close(db);
return 0;
}
// Set the default busy handler to retry automatically before returning SQLITE_BUSY.
err = sqlite3_busy_timeout(db, BUSY_TIMEOUT_MS);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, db, "Could not set busy timeout");
sqlite3_close(db);
return 0;
}
// Register custom Android functions.
err = register_android_functions(db, UTF16_STORAGE);
if (err) {
throw_sqlite3_exception(env, db, "Could not register Android SQL functions.");
sqlite3_close(db);
return 0;
}
/// M: dialer search support @{
#ifdef MTK_DIALER_SEARCH_SUPPORT
err = register_dialer_search_custom_functions(db);
if (err) {
err = register_dialer_search_android_functions(db);
if (err) {
throw_sqlite3_exception(env, db, "Could not register dialer search functions.");
sqlite3_close(db);
return 0;
}
}
#endif
/// @}
// Create wrapper object.
SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label);//这是一个结构体,包含了数据库链接的指针
// Enable tracing and profiling if requested.
if (enableTrace) {
sqlite3_trace(db, &sqliteTraceCallback, connection);
}
if (enableProfile) {
sqlite3_profile(db, &sqliteProfileCallback, connection);
}
ALOGV("Opened connection %p with label '%s'", db, label.string());
return reinterpret_cast(connection);//通过reinterpret_cast把SQLiteConnection指针转换成jlong,jlong的值应该就是指针的值,就是所指向的对象的地址
}
SQLiteConnection结构体的代码如下:
struct SQLiteConnection {
// Open flags.
// Must be kept in sync with the constants defined in SQLiteDatabase.java.
enum {
OPEN_READWRITE = 0x00000000,
OPEN_READONLY = 0x00000001,
OPEN_READ_MASK = 0x00000001,
NO_LOCALIZED_COLLATORS = 0x00000010,
CREATE_IF_NECESSARY = 0x10000000,
};
sqlite3* const db;
const int openFlags;
const String8 path;
const String8 label;
volatile bool canceled;
SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
db(db), openFlags(openFlags), path(path), label(label), canceled(false) { }
};
SQLite的native api都是定义在/external/sqlite/dist/sqlite3.c,这个文件包含了15万行的代码。如果需要一些效率的话,其实可以自己构建一个小型的sqlite框架,牺牲拓展性来换取效率。因为android的SQLiteDatabase框架层层调用,虽然拓展性好,但是效率肯定是稍微低一点。
nativeopen()方法调用了sqlite3.c中的sqlite3_open_v2(),看源码:
SQLITE_API int SQLITE_STDCALL sqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
){
return openDatabase(filename, ppDb, (unsigned int)flags, zVfs);
}
看openDatabase(),非常长:自己看吧,不分析了:
/*
** This routine does the work of opening a database on behalf of
** sqlite3_open() and sqlite3_open16(). The database filename "zFilename"
** is UTF-8 encoded.
*/
static int openDatabase(
const char *zFilename, /* Database filename UTF-8 encoded */
sqlite3 **ppDb, /* OUT: Returned database handle */
unsigned int flags, /* Operational flags */
const char *zVfs /* Name of the VFS to use */
){
sqlite3 *db; /* Store allocated handle here */
int rc; /* Return code */
int isThreadsafe; /* True for threadsafe connections */
char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */
char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */
#ifdef SQLITE_ENABLE_API_ARMOR
if( ppDb==0 ) return SQLITE_MISUSE_BKPT;
#endif
*ppDb = 0;
#ifndef SQLITE_OMIT_AUTOINIT
rc = sqlite3_initialize();
if( rc ) return rc;
#endif
/* Only allow sensible combinations of bits in the flags argument.
** Throw an error if any non-sense combination is used. If we
** do not block illegal combinations here, it could trigger
** assert() statements in deeper layers. Sensible combinations
** are:
**
** 1: SQLITE_OPEN_READONLY
** 2: SQLITE_OPEN_READWRITE
** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
*/
assert( SQLITE_OPEN_READONLY == 0x01 );
assert( SQLITE_OPEN_READWRITE == 0x02 );
assert( SQLITE_OPEN_CREATE == 0x04 );
testcase( (1<<(flags&7))==0x02 ); /* READONLY */
testcase( (1<<(flags&7))==0x04 ); /* READWRITE */
testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */
if( ((1<<(flags&7)) & 0x46)==0 ){
return SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */
}
if( sqlite3GlobalConfig.bCoreMutex==0 ){
isThreadsafe = 0;
}else if( flags & SQLITE_OPEN_NOMUTEX ){
isThreadsafe = 0;
}else if( flags & SQLITE_OPEN_FULLMUTEX ){
isThreadsafe = 1;
}else{
isThreadsafe = sqlite3GlobalConfig.bFullMutex;
}
if( flags & SQLITE_OPEN_PRIVATECACHE ){
flags &= ~SQLITE_OPEN_SHAREDCACHE;
}else if( sqlite3GlobalConfig.sharedCacheEnabled ){
flags |= SQLITE_OPEN_SHAREDCACHE;
}
/* Remove harmful bits from the flags parameter
**
** The SQLITE_OPEN_NOMUTEX and SQLITE_OPEN_FULLMUTEX flags were
** dealt with in the previous code block. Besides these, the only
** valid input flags for sqlite3_open_v2() are SQLITE_OPEN_READONLY,
** SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, SQLITE_OPEN_SHAREDCACHE,
** SQLITE_OPEN_PRIVATECACHE, and some reserved bits. Silently mask
** off all other flags.
*/
flags &= ~( SQLITE_OPEN_DELETEONCLOSE |
SQLITE_OPEN_EXCLUSIVE |
SQLITE_OPEN_MAIN_DB |
SQLITE_OPEN_TEMP_DB |
SQLITE_OPEN_TRANSIENT_DB |
SQLITE_OPEN_MAIN_JOURNAL |
SQLITE_OPEN_TEMP_JOURNAL |
SQLITE_OPEN_SUBJOURNAL |
SQLITE_OPEN_MASTER_JOURNAL |
SQLITE_OPEN_NOMUTEX |
SQLITE_OPEN_FULLMUTEX |
SQLITE_OPEN_WAL
);
/* Allocate the sqlite data structure */
db = sqlite3MallocZero( sizeof(sqlite3) );
if( db==0 ) goto opendb_out;
if( isThreadsafe ){
db->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
if( db->mutex==0 ){
sqlite3_free(db);
db = 0;
goto opendb_out;
}
}
sqlite3_mutex_enter(db->mutex);
db->errMask = 0xff;
db->nDb = 2;
db->magic = SQLITE_MAGIC_BUSY;
db->aDb = db->aDbStatic;
assert( sizeof(db->aLimit)==sizeof(aHardLimit) );
memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit));
db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
db->autoCommit = 1;
db->nextAutovac = -1;
db->szMmap = sqlite3GlobalConfig.szMmap;
db->nextPagesize = 0;
db->nMaxSorterMmap = 0x7FFFFFFF;
db->flags |= SQLITE_ShortColNames | SQLITE_EnableTrigger | SQLITE_CacheSpill
#if !defined(SQLITE_DEFAULT_AUTOMATIC_INDEX) || SQLITE_DEFAULT_AUTOMATIC_INDEX
| SQLITE_AutoIndex
#endif
#if SQLITE_DEFAULT_CKPTFULLFSYNC
| SQLITE_CkptFullFSync
#endif
#if SQLITE_DEFAULT_FILE_FORMAT<4
| SQLITE_LegacyFileFmt
#endif
#ifdef SQLITE_ENABLE_LOAD_EXTENSION
| SQLITE_LoadExtension
#endif
#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS
| SQLITE_RecTriggers
#endif
#if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS
| SQLITE_ForeignKeys
#endif
#if defined(SQLITE_REVERSE_UNORDERED_SELECTS)
| SQLITE_ReverseOrder
#endif
;
sqlite3HashInit(&db->aCollSeq);
#ifndef SQLITE_OMIT_VIRTUALTABLE
sqlite3HashInit(&db->aModule);
#endif
/* Add the default collation sequence BINARY. BINARY works for both UTF-8
** and UTF-16, so add a version for each to avoid any unnecessary
** conversions. The only error that can occur here is a malloc() failure.
**
** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating
** functions:
*/
createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0);
createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0);
createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0);
createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0);
if( db->mallocFailed ){
goto opendb_out;
}
/* EVIDENCE-OF: R-08308-17224 The default collating function for all
** strings is BINARY.
*/
db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0);
assert( db->pDfltColl!=0 );
/* Parse the filename/URI argument. */
db->openFlags = flags;
rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_NOMEM ) db->mallocFailed = 1;
sqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg);
sqlite3_free(zErrMsg);
goto opendb_out;
}
/* Open the backend database driver */
rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0,
flags | SQLITE_OPEN_MAIN_DB);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_IOERR_NOMEM ){
rc = SQLITE_NOMEM;
}
sqlite3Error(db, rc);
goto opendb_out;
}
sqlite3BtreeEnter(db->aDb[0].pBt);
db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db);
sqlite3BtreeLeave(db->aDb[0].pBt);
db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
/* The default safety_level for the main database is 'full'; for the temp
** database it is 'NONE'. This matches the pager layer defaults.
*/
db->aDb[0].zName = "main";
db->aDb[0].safety_level = 3;
db->aDb[1].zName = "temp";
db->aDb[1].safety_level = 1;
db->magic = SQLITE_MAGIC_OPEN;
if( db->mallocFailed ){
goto opendb_out;
}
/* Register all built-in functions, but do not attempt to read the
** database schema yet. This is delayed until the first time the database
** is accessed.
*/
sqlite3Error(db, SQLITE_OK);
sqlite3RegisterBuiltinFunctions(db);
/* Load automatic extensions - extensions that have been registered
** using the sqlite3_automatic_extension() API.
*/
rc = sqlite3_errcode(db);
if( rc==SQLITE_OK ){
sqlite3AutoLoadExtensions(db);
rc = sqlite3_errcode(db);
if( rc!=SQLITE_OK ){
goto opendb_out;
}
}
#ifdef SQLITE_ENABLE_FTS1
if( !db->mallocFailed ){
extern int sqlite3Fts1Init(sqlite3*);
rc = sqlite3Fts1Init(db);
}
#endif
#ifdef SQLITE_ENABLE_FTS2
if( !db->mallocFailed && rc==SQLITE_OK ){
extern int sqlite3Fts2Init(sqlite3*);
rc = sqlite3Fts2Init(db);
}
#endif
#ifdef SQLITE_ENABLE_FTS3
if( !db->mallocFailed && rc==SQLITE_OK ){
rc = sqlite3Fts3Init(db);
}
#endif
#ifdef SQLITE_ENABLE_ICU
if( !db->mallocFailed && rc==SQLITE_OK ){
rc = sqlite3IcuInit(db);
}
#endif
#ifdef SQLITE_ENABLE_RTREE
if( !db->mallocFailed && rc==SQLITE_OK){
rc = sqlite3RtreeInit(db);
}
#endif
#ifdef SQLITE_ENABLE_DBSTAT_VTAB
if( !db->mallocFailed && rc==SQLITE_OK){
int sqlite3_dbstat_register(sqlite3*);
rc = sqlite3_dbstat_register(db);
}
#endif
/* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking
** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking
** mode. Doing nothing at all also makes NORMAL the default.
*/
#ifdef SQLITE_DEFAULT_LOCKING_MODE
db->dfltLockMode = SQLITE_DEFAULT_LOCKING_MODE;
sqlite3PagerLockingMode(sqlite3BtreePager(db->aDb[0].pBt),
SQLITE_DEFAULT_LOCKING_MODE);
#endif
if( rc ) sqlite3Error(db, rc);
/* Enable the lookaside-malloc subsystem */
setupLookaside(db, 0, sqlite3GlobalConfig.szLookaside,
sqlite3GlobalConfig.nLookaside);
sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT);
opendb_out:
sqlite3_free(zOpen);
if( db ){
assert( db->mutex!=0 || isThreadsafe==0
|| sqlite3GlobalConfig.bFullMutex==0 );
sqlite3_mutex_leave(db->mutex);
}
rc = sqlite3_errcode(db);
assert( db!=0 || rc==SQLITE_NOMEM );
if( rc==SQLITE_NOMEM ){
sqlite3_close(db);
db = 0;
}else if( rc!=SQLITE_OK ){
db->magic = SQLITE_MAGIC_SICK;
}
*ppDb = db;
#ifdef SQLITE_ENABLE_SQLLOG
if( sqlite3GlobalConfig.xSqllog ){
/* Opening a db handle. Fourth parameter is passed 0. */
void *pArg = sqlite3GlobalConfig.pSqllogArg;
sqlite3GlobalConfig.xSqllog(pArg, db, zFilename, 0);
}
#endif
return sqlite3ApiExit(0, rc);
}
关于一些jni机制和native 语言的小结:
jni调用,java层和native层通过jni层连接,而java层和jni层间数据类型是有对应的,可以说java层和native的基本数据类型是比较容易转换传递的,而native和java间的实例互相持有必须变成基本类型来传递,那么只能是整型,一般是long。native给java层传递引用,通过reinterpret_cast
要在java层保存native对象的指针时,要注意创建该native对象的方式,要使用new关键字去创建,否则会被自动回收。导致使用指针时发生不可预测的错误。关于c++创建对象:https://blog.csdn.net/b1480521874/article/details/83031254
头文件:
头文件内容并没有固定的格式要求,不过一般为防止嵌套引用给编译器带来死锁或者没必要的开销,一般约定整个头文件中所有内容在一个条件编译下,即如下格式:
#ifndef 宏名
#define 宏名
//头文件主体
#endif
这样可以保证一个头文件在一个源文件中最多只被引用一次。为避免宏名重复,宏名一般由头文件名转换而来,如果头文件名是xxx.h,那么宏名一般定义为:
_XXX_H_
即前后各加一个下划线,同时文件名中除数字、字母、下划线以外的字符均转换为下划线_。
在sqlite3.c、sqlite.h中,每个方法都使用SQLITE_API和SQLITE_STDCALL去修饰,这应该只是一个提示作用,说明该方法是标准的sqlite的api接口。这个两个宏在sqlite3.c、sqlite.h都有定义,但是后面没有跟任何东西,定义方式是:
#ifndef SQLITE_API
# define SQLITE_API
#endif
#ifndef SQLITE_STDCALL
# define SQLITE_STDCALL
#endif
所以最后编译的时候,进行替代的时候,这两个宏就相当于什么都没有。
sqilte3的标准接口都在sqlite3.c一个文件中,而sqlite3_android.cpp是android加的接口。其中各自对应的头文件是sqlite3.h和sqlite3_android.h。
关于native语言的编译:在编译过程中,只会先去找到头文件,然后没有定义的方法如果在头文件中有声明,就可以编译通过。这个跟import的作用是一致的,头文件还多了个定义宏的功能。然后再链接的过程中才会去找对应的定义的那个二进制文件。链接过程中,各个源文件间应该是一个集合,就是说链接是必须是知道大家的彼此存在的。除非通过动态链接的方式调用。所以应该也有个像Android.mk文件中的指定src的一个键值对去指定此次编译涉及到哪些源文件。至于头文件,默认去include该头文件的源文件所在目录查找,然后再去其他路径找,至于去哪些路径找,应该跟编译器有关,或许在哪个地方可以指定。
jni和native编译时不会去检查函数是否必然会有return。如果运行时函数返回值非void,又没有return,那么程序会直接崩溃
数据库的关闭
SQLiteDatabase什么时候应该关闭,这个问题不好回答,应该在某些回调方法中吧,如onstop,但是一定要在相应的onResume中重新获取。但是一定要注意,当你关闭时,有其他线程正在通过该引用访问数据库,那么就会出错了,这个要小心。在使用前要判断是否为null,还要判断是否已关闭。
----------------------------------------------------------------------------------------------------------------------------
以下内容转自:https://blog.csdn.net/Innost/article/details/47254697#t10
SQLite编译完成后将生成一个libsqlite.so,大小仅为300多KB,native层的SQLite API由libsqlite.so提供。
使用SQLite API开发的Android native程序示例的代码如下:
[-->SqliteTest.cpp::libsqlite示例]
#include
#include //包含sqlite API头文件,这里的3是SQLite的版本号
#include
#define LOG_TAG "SQLITE_TEST" //定义该进程logcat输出的标签
#include
#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;
}
数据库触发器(Trigger)
跟ContentProvider的contentObserver的功能相似
触发器(Trigger)是数据库开发技术中一个常见的术语。其本质非常简单,就是在指定表上发生特定事情时,数据库需要执行的某些操作
db.execSQL("CREATE TRIGGER IF NOT EXISTSimages_cleanup DELETE ON images " +
"BEGIN " +
"DELETE FROM thumbnails WHERE image_id = old._id;" +
"SELECT _DELETE_FILE(old._data);" +
"END");
CREATE TRIGGER IF NOT EXITSimages_cleanup:如果没有定义名为images_cleanup的触发器,就创建一个名为images_cleanup的触发器。
DELETE ON images:设置该触发器的触发条件。显然,当我们对images表执行delete操作时,该触发器将被触发。
BEGIN和END之间则定义了该触发器要执行的动作。从前面的代码可知,它将执行两项操作:
1. 删除thumbnails(缩略图)表中对应的信息。为什么要删除缩略图呢?因为原图的信息已经不存在了,留着它没用。
2. 执行_DELETE_FILE函数,其参数是old.data。从名字上来看,这个函数的功能应为删除文件。为什么要删除此文件?原因也很简单,数据库都没有该项信息了,还留着图片干什么!另外,如不删除文件,下一次媒体扫描时就又会把它们找到。
函数注册:
将函数注册到sqlite中,就是根据宏就可以调用方法:
调用sqlite3_android.cpp中的register_android_functions方法就会注册android定义的一些方法,看看注册删除文件那个方法
//注册_DELETE_FILE对应的函数为delete_file
//_DELETE_FILE类似为宏名,delete_file为真正要注册的方法,其中该方法也在sqlite3_android.cpp中
err =sqlite3_create_function(handle, "_DELETE_FILE", 1, SQLITE_UTF8,
NULL, delete_file, NULL, NULL);
更多Android SQLite架构的应用可以看看https://blog.csdn.net/liuhe688/article/details/6715983