SQLite--SQLiteDatabase、SQLiteOpenHelper、sqlite3.c--(jni、头文件)--源码分析基于Android M

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<mNewVersion就需要更新数据库,调用onUpgrade()
                            onDowngrade(db, version, mNewVersion);
                        } else {
                            onUpgrade(db, version, mNewVersion);
                        }
                    }
                    db.setVersion(mNewVersion);//设置database版本号为新的版本号
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
            }

            onOpen(db);

            if (db.isReadOnly()) {
                Log.w(TAG, "Opened " + mName + " in read-only mode");
            }

            mDatabase = db;
            return db;
        } finally {
            mIsInitializing = false;
            if (db != null && db != mDatabase) {
                db.close();
            }
        }
    }

复写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<jlong>(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<jlong>(实例指针),可以把实例变成引用让java层持有。而要使用该对象,则把他传到jni层使用就好了。jni层可以使用jni类型数据,也可以使用native类型数据,可以直接创建native中定义的类(或者结构体)的实例。而native和jni之间的基本类型转换只要使用强制转换就行,就是jint j=1;int i=(int)j;


头文件:

头文件内容并没有固定的格式要求,不过一般为防止嵌套引用给编译器带来死锁或者没必要的开销,一般约定整个头文件中所有内容在一个条件编译下,即如下格式:

#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该头文件的源文件所在目录查找,然后再去其他路径找,至于去哪些路径找,应该跟编译器有关,或许在哪个地方可以指定。


数据库的关闭

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 <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;

}

数据库触发器(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


猜你喜欢

转载自blog.csdn.net/b1480521874/article/details/80257931