Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 創(chuàng)建過(guò)程源碼分析

閃存
Android 存儲(chǔ)優(yōu)化系列專題
  • SharedPreferences 系列

Android 之不要濫用 SharedPreferences
Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失

  • ContentProvider 系列(待更)

《Android 存儲(chǔ)選項(xiàng)之 ContentProvider 啟動(dòng)過(guò)程源碼分析》
《Android 存儲(chǔ)選項(xiàng)之 ContentProvider 深入分析》

  • 對(duì)象序列化系列

Android 對(duì)象序列化之你不知道的 Serializable
Android 對(duì)象序列化之 Parcelable 深入分析
Android 對(duì)象序列化之追求完美的 Serial

  • 數(shù)據(jù)序列化系列(待更)

《Android 數(shù)據(jù)序列化之 JSON》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 使用》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 源碼分析》

  • SQLite 存儲(chǔ)系列

Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 創(chuàng)建過(guò)程源碼分析
Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 源碼分析
數(shù)據(jù)庫(kù)連接池 SQLiteConnectionPool 源碼分析
SQLiteDatabase 啟用事務(wù)源碼分析
SQLite 數(shù)據(jù)庫(kù) WAL 模式工作原理簡(jiǎn)介
SQLite 數(shù)據(jù)庫(kù)鎖機(jī)制與事務(wù)簡(jiǎn)介
SQLite 數(shù)據(jù)庫(kù)優(yōu)化那些事兒


存儲(chǔ)優(yōu)化系列專題中侍郭,先后為大家介紹了 SharedPreferences询吴、ContentProvider掠河、對(duì)象/數(shù)據(jù)序列化等存儲(chǔ)和優(yōu)化方法。先回顧下前面介紹的存儲(chǔ)方法的使用場(chǎng)景猛计,少量的 key:value 數(shù)據(jù)可以直接使用 SharedPreferences唠摹,稍微復(fù)雜一些的數(shù)據(jù)類型也可以通過(guò)序列化成 JSON 或者 Protocol Buffers 保存,并且在開(kāi)發(fā)中獲取或者修改數(shù)據(jù)也很簡(jiǎn)單奉瘤。

不過(guò)這幾種方法并不能覆蓋所有的存儲(chǔ)場(chǎng)景勾拉,數(shù)據(jù)量在幾百上千條這個(gè)量級(jí)時(shí)它們的性能還可以接受,但如果是幾萬(wàn)條的數(shù)據(jù)記錄時(shí)盗温,而且如何實(shí)現(xiàn)快速地對(duì)某幾條數(shù)據(jù)做增刪改查呢劫窒?

對(duì)于大數(shù)據(jù)的存儲(chǔ)場(chǎng)景神年,我們需要考慮穩(wěn)定性吃既、性能和可擴(kuò)展性渺杉,這個(gè)時(shí)候就輪到該篇文章的“主角”數(shù)據(jù)庫(kù)登場(chǎng)了。

SQLite 簡(jiǎn)介

雖然市面上有很多的數(shù)據(jù)庫(kù)吼驶,但受限于庫(kù)體積和存儲(chǔ)空間惩激,適合移動(dòng)端使用的還真不多店煞。當(dāng)然最廣泛的還是我們今天要介紹的 SQLite蟹演。但同樣還是有一些其它不錯(cuò)的選擇,例如創(chuàng)業(yè)團(tuán)隊(duì)的 Realm顷蟀、Google 的 LevelDB 等酒请。

SQLite 是一款完全由 C 語(yǔ)言開(kāi)發(fā)的開(kāi)源、嵌入式關(guān)系型數(shù)據(jù)庫(kù)鸣个,最早發(fā)布于 2000 年羞反,它沒(méi)有獨(dú)立運(yùn)行進(jìn)程,它與所服務(wù)的應(yīng)用程序在應(yīng)用程序進(jìn)程空間內(nèi)共生共存囤萤。它的代碼與應(yīng)用程序代碼也是在一起的昼窗,或者說(shuō)嵌入其中,作為托管它的程序的一部分涛舍。

數(shù)據(jù)庫(kù)這個(gè)主題內(nèi)容非常多澄惊,而且相信有很多小伙伴學(xué)習(xí)數(shù)據(jù)庫(kù)都是從入門到放棄,本人也是從最開(kāi)始懵懂到不斷的摸索探究富雅,總算是對(duì)它有了進(jìn)一步的認(rèn)識(shí)掸驱。

在 Android 平臺(tái),系統(tǒng)已經(jīng)內(nèi)置了 SQLite 數(shù)據(jù)庫(kù)作為數(shù)據(jù)持久存儲(chǔ)的一種方案没佑。Android 為 SQLite 數(shù)據(jù)庫(kù)提供的第一個(gè)也是最重要的類就是 SQLiteOpenHelper毕贼,該類可以供開(kāi)發(fā)人員擴(kuò)展實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的創(chuàng)建、打開(kāi)或使用數(shù)據(jù)庫(kù)的重要任務(wù)和行為蛤奢。

該篇文章主要是通過(guò)源碼角度分析 Android 平臺(tái)提供的 SQLiteDatabase 的創(chuàng)建過(guò)程源碼分析鬼癣。有關(guān) SQLiteDatabase 更詳細(xì)的內(nèi)容請(qǐng)參考后續(xù)文章《Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 源碼分析》和《Android 存儲(chǔ)選項(xiàng)之 SQLite 優(yōu)化那些事兒》陶贼。

1. SQLiteOpenHelper

在 Android 中如果要使用 SQLite 數(shù)據(jù)庫(kù),那 SQLiteOpenHelper 可能就是我們第一個(gè)接觸的類待秃,從它的命名其實(shí)我們也可以看出它屬于數(shù)據(jù)庫(kù)操作的輔助類骇窍,實(shí)際上它內(nèi)部也是圍繞 SQLiteDatabase 完成一系列任務(wù)和行為。

下面就從一個(gè)例子開(kāi)始分析 SQLiteDatabase 的創(chuàng)建過(guò)程:

public final class SampleDBHelper extends SQLiteOpenHelper{

    public SampleHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //創(chuàng)建數(shù)據(jù)庫(kù)表
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //升級(jí)
    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //降級(jí)
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        //數(shù)據(jù)庫(kù)被打開(kāi)
    }

    @Override
    public void onConfigure(SQLiteDatabase db) {
        //配置相關(guān)
    }
}

在 SampleDBHelper 示例中锥余,我們重寫 SQLiteOpenHelper 的相關(guān)方法腹纳,其中 onCreate 和 onUpgrade 是必須要實(shí)現(xiàn)的;其余方法則是在數(shù)據(jù)庫(kù)滿足相關(guān)狀態(tài)時(shí)觸發(fā)驱犹。那它們的調(diào)用時(shí)機(jī)分別是怎么樣的呢嘲恍?

先看下 SQLiteOpenHelper 的構(gòu)造方法:

public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
            @Nullable CursorFactory factory, int version) {
    //context 上下文
    //name 數(shù)據(jù)庫(kù)名稱
    //factory 是Cursor工廠,用于自定義指定Cursor
    //version 當(dāng)前版本雄驹,決定了onCreate佃牛、onUpgrade、onDowngrade的調(diào)用時(shí)機(jī)
    //errorHandler 用于指定發(fā)生異常時(shí)回調(diào)
    this(context, name, factory, version, null);
}
  • context 定義應(yīng)用程序運(yùn)行的環(huán)境医舆,包含應(yīng)用程序所需的共享資源俘侠;

  • name 表示數(shù)據(jù)庫(kù)名稱;

  • factory 表示 Cursor 的創(chuàng)建工廠 SQLiteDatabase.CursorFactory蔬将,用于指定自定義創(chuàng)建 Cursor 實(shí)例爷速,主要用于在查詢數(shù)據(jù)庫(kù)時(shí)為其指定自定義的 Cursor;

      public Cursor query(CursorFactory factory, String[] selectionArgs){
          //...省略
          if (factory == null) {
              cursor = new SQLiteCursor(this, mEditTable, query);
          } else {
              //配置的CursorFactory在這里調(diào)用霞怀,自定義Cursor
              cursor = factory.newCursor(mDatabase, this, mEditTable, query);
          }
          //...省略
      }
    

Cursor 是完成對(duì)數(shù)據(jù)查詢后的數(shù)據(jù)獲取接口封裝惫东。

  • version 表示當(dāng)前數(shù)據(jù)庫(kù)版本號(hào),它將決定 onCreate毙石、onUpgrade廉沮、onDowngrade 方法的調(diào)用時(shí)機(jī);

  • errorHandler DatabaseErrorHandler 表示當(dāng)前發(fā)生錯(cuò)誤時(shí)的回調(diào)接口徐矩。

      mErrorHandler.onCorruption(this);
    

另外 SQLiteOpenHelper 中還提供 close 關(guān)閉 SQLiteOpenHelper 中的 SQLite 數(shù)據(jù)庫(kù)滞时。數(shù)據(jù)庫(kù)被關(guān)閉后,任何對(duì)數(shù)據(jù)庫(kù)的操作都是不允許的滤灯。
getReadableDatabase 和 getWriteableDatabase 方法行為相似坪稽,從它們的名字也可以看出,它們只有一點(diǎn)不同 getReadableDatabase 以只讀方式打開(kāi) SQLiteOpenHelper 對(duì)象中指定的數(shù)據(jù)庫(kù)力喷,也就說(shuō)任何想要修改數(shù)據(jù)庫(kù)的行為都是不允許的刽漂。getWriteableDatabase 也是打開(kāi)數(shù)據(jù)庫(kù),但它允許數(shù)據(jù)庫(kù)正常的讀/寫操作弟孟。

如果因?yàn)槟撤N原因贝咙,數(shù)據(jù)庫(kù)不能進(jìn)行寫操作,那么 getWriteableDatabase 將以只讀方式打開(kāi)數(shù)據(jù)庫(kù)拂募,并拋出一個(gè)類型為 SQLiteException 的異常庭猩】咚可以利用 isReadOnly 方法測(cè)試數(shù)據(jù)庫(kù)是否可寫。然后可以使用 getWriteableDatabase 方法重新打開(kāi)一個(gè)只讀的數(shù)據(jù)庫(kù)蔼水,使它能夠正常讀寫震糖。

2. SQLiteDatabase

SQLiteOpenHelper 是在 Android 平臺(tái)處理 SQLite 數(shù)據(jù)庫(kù)相關(guān)的第一個(gè)類,那接下來(lái)說(shuō)的 SQLiteDatabase 則是最關(guān)鍵的類了趴腋。SQLiteDatabase 在概念上很容易理解吊说,與 SQLite C API 的底層數(shù)據(jù)庫(kù)對(duì)象相似,實(shí)際可能比想象的復(fù)雜的多颁井。

但實(shí)際上 SQLiteDatabase 內(nèi)部的每一個(gè)方法都有自己的用途懈凹,而且大部分方法都是為完成一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)任務(wù)洼专,比如表的選擇雾袱、插入腿箩、更新和刪除語(yǔ)句垢乙。這里只是列舉一些重要的方法,不過(guò)這部分內(nèi)容并不是我們今天要討論的追逮,你可以參考下一篇《Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 源碼分析》一文酪刀。

  • SQLiteDatabase 的創(chuàng)建和版本管理

通過(guò) SQLiteOpenHelper 的 getReadableDatabase / getWriteableDatabase 方法分別獲得只讀 / 可讀寫的 SQLiteDatabase 實(shí)例。

//獲得一個(gè)只讀的數(shù)據(jù)庫(kù)
public SQLiteDatabase getReadableDatabase() {
    synchronized (this) {
        return getDatabaseLocked(false);
    }
}

//獲得一個(gè)可讀寫的數(shù)據(jù)庫(kù)
public SQLiteDatabase getWritableDatabase() {
    synchronized (this) {
        return getDatabaseLocked(true);
    }
}

它們最終調(diào)用 getDatabaseLocked 方法钮孵,SQLiteOpenHelper 中默認(rèn)會(huì)對(duì)應(yīng)一個(gè) SQLiteDatabase 實(shí)例:

private SQLiteDatabase getDatabaseLocked(boolean writable) {
    //SQLiteDatabase作為SQLiteOpenHelper的成員
    if (mDatabase != null) {
        //SQLiteOpenHelper中默認(rèn)保存當(dāng)前SQLiteDatabase實(shí)例
        if (!mDatabase.isOpen()) {
            //如果數(shù)據(jù)庫(kù)已經(jīng)關(guān)閉骂倘,需要重新打開(kāi)
            mDatabase = null;
        } else if (!writable || !mDatabase.isReadOnly()) {
            //如果數(shù)據(jù)庫(kù)沒(méi)有關(guān)閉
            //只要是該數(shù)據(jù)庫(kù)是可寫則直接返回
            //否則當(dāng)前申請(qǐng)是只讀則直接返回
            return mDatabase;
        }
    }

    if (mIsInitializing) {
        //避免重復(fù)的初始化階段
        throw new IllegalStateException("getDatabase called recursively");
    }

    SQLiteDatabase db = mDatabase;
    try {
        mIsInitializing = true;

        if (db != null) {
            if (writable && db.isReadOnly()) {
                //但是當(dāng)前數(shù)據(jù)是只讀狀態(tài),需要獲取可寫的數(shù)據(jù)庫(kù)
                //此時(shí)需要根據(jù)配置選項(xiàng)SQLiteDatabaseConfiguration重新打開(kāi)數(shù)據(jù)庫(kù)
                db.reopenReadWrite();
            }
        } else if (mName == null) {
            //數(shù)據(jù)庫(kù)名稱為null巴席,為Memory Db
            db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
        } else {
            //獲取數(shù)據(jù)庫(kù)保存路徑
            final File filePath = mContext.getDatabasePath(mName);
            SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
            try {
                //創(chuàng)建數(shù)據(jù)庫(kù)操作SQLiteDatabase實(shí)例
                db = SQLiteDatabase.openDatabase(filePath, params);
                // Keep pre-O-MR1 behavior by resetting file permissions to 660
                setFilePermissionsForDb(filePath.getPath());
            } catch (SQLException ex) {
                if (writable) {
                    throw ex;
                }
                Log.e(TAG, "Couldn't open " + mName
                        + " for writing (will try read-only):", ex);
                params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
                db = SQLiteDatabase.openDatabase(filePath, params);
            }
        }
        //onConfigure回調(diào)历涝,配置數(shù)據(jù)庫(kù)相關(guān)
        onConfigure(db);
        //獲取當(dāng)前數(shù)據(jù)庫(kù)版本,默認(rèn)為0
        final int version = db.getVersion();
        //mNewVersion是自行設(shè)置的版本號(hào)漾唉,一般默認(rèn)第一個(gè)版本為1
        //如果首次創(chuàng)建傳遞0荧库,此時(shí)將不再執(zhí)行
        if (version != mNewVersion) {
            if (db.isReadOnly()) {
                //此時(shí)如果是只讀狀態(tài),將無(wú)法對(duì)數(shù)據(jù)庫(kù)做任何更改
                throw new SQLiteException("Can't upgrade read-only database from version " +
                        db.getVersion() + " to " + mNewVersion + ": " + mName);
            }

            //例如當(dāng)前數(shù)據(jù)庫(kù)version==3赵刑,而mMinimumSupportedVersion==5分衫,此時(shí)
            //該數(shù)據(jù)不再不支持,將其刪除
            if (version > 0 && version < mMinimumSupportedVersion) {
                File databaseFile = new File(db.getPath());
                //這個(gè)被@hide了
                onBeforeDelete(db);
                db.close();
                if (SQLiteDatabase.deleteDatabase(databaseFile)) {
                    mIsInitializing = false;
                    //刪除成功重新創(chuàng)建數(shù)據(jù)庫(kù)
                    return getDatabaseLocked(writable);
                } else {
                    //刪除失敗要拋出異常
                    throw new IllegalStateException("Unable to delete obsolete database "
                            + mName + " with version " + version);
                }
            } else {
                //開(kāi)啟事務(wù)
                db.beginTransaction();
                try {
                    //數(shù)據(jù)庫(kù)第一次創(chuàng)建執(zhí)行onCreate
                    //默認(rèn)數(shù)據(jù)庫(kù)第一次創(chuàng)建時(shí)version==0
                    if (version == 0) {
                        onCreate(db);
                    } else {
                        if (version > mNewVersion) {
                            //降級(jí)
                            onDowngrade(db, version, mNewVersion);
                        } else {
                            //升級(jí)
                            onUpgrade(db, version, mNewVersion);
                        }
                    }
                    //設(shè)置當(dāng)前最后版本
                    db.setVersion(mNewVersion);
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
            }
        }

        //onOpen被回調(diào)
        onOpen(db);

        if (db.isReadOnly()) {
            Log.w(TAG, "Opened " + mName + " in read-only mode");
        }
        //賦值給當(dāng)前成員
        mDatabase = db;
        return db;
    } finally {
        mIsInitializing = false;
        if (db != null && db != mDatabase) {
            db.close();
        }
    }

快速瀏覽下該方法般此,是否發(fā)現(xiàn)前面在 SampleDBHelper 示例中重寫的相關(guān)方法都會(huì)在這里體現(xiàn)出來(lái)蚪战。下面就具體分析下它們的執(zhí)行過(guò)程。

首先方法的開(kāi)始部分铐懊,SQLiteOpenHelper 中會(huì)持有 SQLiteDatabase 實(shí)例邀桑,根據(jù)當(dāng)前打開(kāi)數(shù)據(jù)庫(kù)模式是否符合操作需要,則直接返回科乎。

如果數(shù)據(jù)庫(kù)已經(jīng)打開(kāi) Read-Only(只讀) 狀態(tài)壁畸,此時(shí) reopenReadWrite 方法更新成可寫的。

如果數(shù)據(jù)庫(kù)的名稱 name == null喜喂,此時(shí) SQLiteDatabase.createInMemory 方法創(chuàng)建一個(gè) in-memory 類型 Database瓤摧。

真正創(chuàng)建數(shù)據(jù)庫(kù)操作是 SQLiteDatabase.openDatabase 方法竿裂,這里將 SQLiteDatabase.OpenParams 作為參數(shù),先簡(jiǎn)單看下它的構(gòu)造方法照弥,其內(nèi)部保存了數(shù)據(jù)庫(kù)創(chuàng)建的相關(guān)配置項(xiàng)腻异。

private OpenParams(int openFlags, CursorFactory cursorFactory,
            DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount,
            long idleConnectionTimeout, String journalMode, String syncMode) {
        //打開(kāi)模式
        mOpenFlags = openFlags;
        //Cursor工廠
        mCursorFactory = cursorFactory;
        //DatabaseErrorHandler
        mErrorHandler = errorHandler;
        mLookasideSlotSize = lookasideSlotSize;
        mLookasideSlotCount = lookasideSlotCount;
        //空閑連接超時(shí)時(shí)間
        mIdleConnectionTimeout = idleConnectionTimeout;
        //日志模式,3.12.0之后為WAL模式
        mJournalMode = journalMode;
        //同步模式这揣,提交數(shù)據(jù)之后采取的同步會(huì)數(shù)據(jù)庫(kù)文件的策略
        mSyncMode = syncMode;
    }

其實(shí) OpenParams 主要是保存了當(dāng)前數(shù)據(jù)庫(kù) SQLiteDatabase 打開(kāi)時(shí)相關(guān)配置選項(xiàng)悔常,這在后面內(nèi)容會(huì)驗(yàn)證到該部分。

繼續(xù)向下分析给赞,我們看下在上面示例中重寫的相關(guān)方法的回調(diào)過(guò)程:

  • onConfigure 方法

創(chuàng)建數(shù)據(jù)庫(kù) SQLiteDatabase 成功(SQLiteDatabase.openDatabase 方法正確返回)机打,此時(shí) onConfigure 方法被回調(diào),它的原型是一個(gè)空方法:

public void onConfigure(SQLiteDatabase db) {}

在該方法我們可以對(duì)數(shù)據(jù)庫(kù)做相關(guān)配置操作片迅,例如開(kāi)啟 WAL残邀,設(shè)置 PageSize 等信息。

  • onCreate 方法

如果數(shù)據(jù)庫(kù)第一次創(chuàng)建柑蛇,此時(shí)默認(rèn)版本號(hào)為 0( db.getVersion() )芥挣,而我們實(shí)現(xiàn) SQLiteOpenHelper 后默認(rèn)第一個(gè)版本一般為 1。此時(shí) onCreate 方法被回調(diào)耻台。

另外源碼中也可以看出空免,如果當(dāng)前版本號(hào)小于最小支持的版本 mMinimumSupportedVersion,此時(shí)要執(zhí)行刪除數(shù)據(jù)庫(kù)任務(wù)盆耽。

//刪除數(shù)據(jù)庫(kù)文件
SQLiteDatabase.deleteDatabase(databaseFile)
  • onDowngrade 方法

如果數(shù)據(jù)庫(kù)被設(shè)置過(guò)版本號(hào)蹋砚,并且大于當(dāng)前傳遞版本,表示降級(jí)操作摄杂,此時(shí) onDowngrade 方法被回調(diào):

public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    throw new SQLiteException("Can't downgrade database from version " +
            oldVersion + " to " + newVersion);
}

注意坝咐,當(dāng)出現(xiàn)數(shù)據(jù)庫(kù)降級(jí)時(shí),該方法必須被實(shí)現(xiàn)匙姜,否則默認(rèn)拋出異常畅厢。

  • onUpgrade 方法

與 onDowngrade 方法正好相反,表示當(dāng)前升級(jí)數(shù)據(jù)庫(kù)操作氮昧。最后數(shù)據(jù)庫(kù)會(huì)保存當(dāng)前最后一次版本號(hào)。

  • onOpen 方法

以上執(zhí)行過(guò)程全部結(jié)束后浦楣,onOpen 方法被回調(diào)表示數(shù)據(jù)庫(kù)已經(jīng)打開(kāi)袖肥。

大家是否有注意到,整個(gè)數(shù)據(jù)表創(chuàng)建振劳、升/降級(jí)操作過(guò)程椎组,系統(tǒng)默認(rèn)為我們開(kāi)啟了事務(wù),以提高執(zhí)行效率历恐。

事務(wù)使用的標(biāo)準(zhǔn)格式一般如下:

...省略

db.beginTransaction();

try{

    // do something ...
    
   db.setTrainsactionSuccessful();
}finally{
    db.endTrainsaction();
}

另外還有一些其它需要說(shuō)明的地方

  • 數(shù)據(jù)庫(kù)保存位置

我們可以直接使用 SQLiteDatabase.createDatabase 單獨(dú)指定數(shù)據(jù)庫(kù)創(chuàng)建位置寸癌,但默認(rèn)使用 SQLiteOpenHelper 時(shí)如下:

 //獲取數(shù)據(jù)庫(kù)保存路徑
 final File filePath = mContext.getDatabasePath(mName);

這里實(shí)際調(diào)用到 ContextImpl 的 getDatabasePath 方法

@Override
public File getDatabasePath(String name) {
    File dir;
    File f;

    if (name.charAt(0) == File.separatorChar) {
        //如果name自定義存儲(chǔ)路徑:/storage/emulated/0/database/test.db
        String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
        //自定義路徑:/storage/emulated/0/database
        dir = new File(dirPath);
        //數(shù)據(jù)庫(kù)名稱:test.db
        name = name.substring(name.lastIndexOf(File.separatorChar));
        f = new File(dir, name);

        if (!dir.isDirectory() && dir.mkdir()) {
            FileUtils.setPermissions(dir.getPath(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                -1, -1);
        }
    } else {
        //否則默認(rèn)/data/data/packageName/databases/test.db
        dir = getDatabasesDir();
        f = makeFilename(dir, name);
    }

    return f;
}

系統(tǒng)默認(rèn)數(shù)據(jù)庫(kù)保存路徑為 /data/data/packeageName/database/name.db专筷;但是我們也可以指定自定義數(shù)據(jù)庫(kù)文件路徑。這樣做的唯一好處是不會(huì)隨著應(yīng)用數(shù)據(jù)清除或應(yīng)用刪除后數(shù)據(jù)庫(kù)文件也被刪除蒸苇。

  1. SQLiteDatabase 創(chuàng)建

通過(guò)上面的分析磷蛹,也證實(shí)了 SQLiteOpenHelper 的命名的含義,它的主要工作就是完成對(duì) SQLiteDatabase 的創(chuàng)建溪烤、關(guān)閉味咳、升/降級(jí)、配置等相關(guān)輔助管理檬嘀。

接下來(lái)我們從 SQLiteDatabase.openDatabase 方法開(kāi)始真正分析 SQLiteDatabase 的創(chuàng)建過(guò)程槽驶。

//創(chuàng)建數(shù)據(jù)庫(kù)操作SQLiteDatabase實(shí)例
db = SQLiteDatabase.openDatabase(filePath, params);

//最終會(huì)調(diào)用如下
private static SQLiteDatabase openDatabase(@NonNull String path,
        @NonNull OpenParams openParams) {
    Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
    //創(chuàng)建SQLiteDatabase實(shí)例
    //前面提到OpenParams此時(shí)作為參數(shù)
    //將OpenParams配置參數(shù)傳入SQLiteDatabase
    SQLiteDatabase db = new SQLiteDatabase(
            path, 
            openParams.mOpenFlags,
            openParams.mCursorFactory, 
            openParams.mErrorHandler,
            openParams.mLookasideSlotSize, 
            openParams.mLookasideSlotCount,
            openParams.mIdleConnectionTimeout,
            openParams.mJournalMode, 
            openParams.mSyncMode);
    //調(diào)用SQLiteDatabase的open
    db.open(); 
    return db;
}

直接創(chuàng)建 SQLiteDatabase 實(shí)例并調(diào)用它的 open 方法。SQLiteDatabase 構(gòu)造方法如下:

private SQLiteDatabase(final String path, final int openFlags,
        CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
        int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
        String journalMode, String syncMode) {
    mCursorFactory = cursorFactory;
    mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
    //SQLiteDatabase的創(chuàng)建模式鸳兽,這個(gè)對(duì)于數(shù)據(jù)庫(kù)的并發(fā)訪問(wèn)非常重要
    //主要影響數(shù)據(jù)庫(kù)連接池打開(kāi)模式
    mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
    mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
    mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
    // Disable lookaside allocator on low-RAM devices
    if (ActivityManager.isLowRamDeviceStatic()) {
        //如果是低內(nèi)存設(shè)備掂铐,系統(tǒng)好像規(guī)定低于512MB為低內(nèi)存設(shè)備
        //不過(guò)發(fā)展到現(xiàn)在就算是2G都算是小內(nèi)存了
        mConfigurationLocked.lookasideSlotCount = 0;
        mConfigurationLocked.lookasideSlotSize = 0;
    }
    long effectiveTimeoutMs = Long.MAX_VALUE;
    // Never close idle connections for in-memory databases
    if (!mConfigurationLocked.isInMemoryDb()) {
        //不是內(nèi)存級(jí)數(shù)據(jù)庫(kù),如果name傳遞null,此時(shí)為inMemory
        if (idleConnectionTimeoutMs >= 0) {
            //空閑連接超時(shí)時(shí)間后關(guān)閉
            effectiveTimeoutMs = idleConnectionTimeoutMs;
        } else if (DEBUG_CLOSE_IDLE_CONNECTIONS) {
            //DEBUG_CLOSE_IDLE_CONNECTIONS 默認(rèn)為false揍异,表示空閑連接不會(huì)被關(guān)閉
            effectiveTimeoutMs = SQLiteGlobal.getIdleConnectionTimeout();
        }
    }
    //配置到SQLiteDatabaseConfiguration中
    mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
    //日志模式
    mConfigurationLocked.journalMode = journalMode;
    //同步模式
    mConfigurationLocked.syncMode = syncMode;

    if (!SQLiteGlobal.isCompatibilityWalSupported() || (
            SQLiteCompatibilityWalFlags.areFlagsSet() && !SQLiteCompatibilityWalFlags
                    .isCompatibilityWalSupported())) {
        //不支持兼容性WAL
        //則禁用掉兼容性WAL
        mConfigurationLocked.openFlags |= DISABLE_COMPATIBILITY_WAL;
    }
}

SQLiteDatabase 構(gòu)造方法內(nèi)容有點(diǎn)多全陨,但是內(nèi)容卻比較單一,就是將前面保存相關(guān)創(chuàng)建配置的 OpenParams 信息保存到 SQLiteDatabaseConfiguartion 中蒿秦,在后續(xù)創(chuàng)建數(shù)據(jù)庫(kù)連接池和開(kāi)啟 WAL 模式等相關(guān)內(nèi)容時(shí)都會(huì)到該配置信息烤镐。

SQLiteDatabase 的 open 方法如下:

private void open() {
    try {
        try {
            //調(diào)用onpenInner
            openInner();
        } catch (SQLiteDatabaseCorruptException ex) {
            //這里回調(diào)DatabaseErrorHandler
            onCorruption();
            openInner();
        }
    } catch (SQLiteException ex) {
        Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
        close();
        throw ex;
    }
}

//這里直接調(diào)用了 openInner 方法
private void openInner() {
    //mLock是ConnectionPoolLocked
    synchronized (mLock) {
        assert mConnectionPoolLocked == null;
        //該mConfigurationLocked就是在構(gòu)造方法中創(chuàng)建的SQLiteDatabaseConfiguartion
        //打開(kāi)數(shù)據(jù)庫(kù)連接池,ConfigurationLocked作為參數(shù)
        mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
        mCloseGuardLocked.open("close");
    }

    synchronized (sActiveDatabases) {
        //緩存當(dāng)前SQLiteDatabase實(shí)例
        //sActiveDatabases是WeakHashMap
        sActiveDatabases.put(this, null);
    }
}

openInner 中開(kāi)始創(chuàng)建當(dāng)前 SQLiteDatabase 的數(shù)據(jù)庫(kù)連接池 SQLiteConnectionPool,并將 SQLiteDatabaseConfiguration 作為參數(shù):

public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
    if (configuration == null) {
        throw new IllegalArgumentException("configuration must not be null.");
    }

    //創(chuàng)建數(shù)據(jù)庫(kù)連接池
    SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
    //調(diào)用open 可能拋出異常
    pool.open();
    return pool;
}

一樣的調(diào)用套路棍鳖,直接創(chuàng)建 SQLiteConnecitonPool炮叶,并調(diào)用其 open 方法:

SQLiteConnectionPool 的構(gòu)造方法如下:

private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
    //copyOnWrite模式吧
    mConfiguration = new SQLiteDatabaseConfiguration(configuration);
    //設(shè)置連接池的大小
    setMaxConnectionPoolSizeLocked();
    // If timeout is set, setup idle connection handler
    // In case of MAX_VALUE - idle connections are never closed
    //在Long.MAX_VALUE下永遠(yuǎn)不會(huì)關(guān)閉連接
    if (mConfiguration.idleConnectionTimeoutMs != Long.MAX_VALUE) {
        setupIdleConnectionHandler(Looper.getMainLooper(),
                mConfiguration.idleConnectionTimeoutMs);
    }
}

將數(shù)據(jù)庫(kù)配置 configuration 保存,不過(guò)這里并不是直接賦值渡处,而是數(shù)據(jù) copy镜悉。

設(shè)置數(shù)據(jù)庫(kù)連接池大小:

//Android提供的SQLiteDatabase開(kāi)啟連接池與WAL模式是一回事
private void setMaxConnectionPoolSizeLocked() {
    if (!mConfiguration.isInMemoryDb()
            && (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
        //如果不是memoryDb医瘫,與打開(kāi)模式是ENABLE_WRITE_AHEAD_LOGGING
        //在這里其實(shí)可以注意到侣肄,SQLiteDatabase并沒(méi)有給我們提供設(shè)置連接池大小的API,
        //這里仍然取決于系統(tǒng)的配置
        mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
    } else {
        // We don't actually need to always restrict the connection pool size to 1
        // for non-WAL databases.  There might be reasons to use connection pooling
        // with other journal modes. However, we should always keep pool size of 1 for in-memory
        // databases since every :memory: db is separate from another.
        // For now, enabling connection pooling and using WAL are the same thing in the API.
        mMaxConnectionPoolSize = 1;
    }
}

這里不知道是否還記得在 SQLiteOpenHelper 的構(gòu)造方法醇份,如果傳入的數(shù)據(jù)庫(kù)名稱 name 為 null稼锅,此時(shí)就會(huì)滿足 isMemoryDb()。

openFlags & ENABLE_WRITE_AHEAD_LOGGING != 0

表示開(kāi)啟 WAL 預(yù)寫模式僚纷。關(guān)于 WAL 模式工作原理你可以參考這里矩距。

其實(shí) Android 并沒(méi)有提供設(shè)置數(shù)據(jù)庫(kù)連接池大小的 API(一些大型應(yīng)用一般會(huì)集成自己的數(shù)據(jù)庫(kù)源碼,來(lái)修改相關(guān)內(nèi)容)怖竭,此時(shí)需要我們開(kāi)啟 WAL 模式锥债,實(shí)際上 Android 提供的開(kāi)啟 WAL 模式與數(shù)據(jù)庫(kù)連接池就是一回事,看下系統(tǒng)默認(rèn)連接池的大小設(shè)置:

public static int getWALConnectionPoolSize() {
    int value = SystemProperties.getInt("debug.sqlite.wal.poolsize",
            Resources.getSystem().getInteger(
            com.android.internal.R.integer.db_connection_pool_size));
    //從這里可以得出結(jié)論:連接池默認(rèn)最小為2,最大取決于系統(tǒng)配置
    //不過(guò)在我個(gè)人這臺(tái)機(jī)器上測(cè)試發(fā)現(xiàn)得到的value==4.
    return Math.max(2, value);
}

從這里可以看出哮肚,當(dāng)開(kāi)啟 WAL 模式后登夫,系統(tǒng)默認(rèn)連接池最小為 2。最大取決于系統(tǒng)配置允趟。
ps:在我個(gè)人這臺(tái)測(cè)試機(jī)上系統(tǒng)配置大小為 4恼策。

關(guān)于連接池的作用補(bǔ)充說(shuō)明:SQLite 支持多線程并發(fā)模式,需要開(kāi)啟下面的配置拼窥,當(dāng)然 Android 系統(tǒng)默認(rèn)開(kāi)啟了多線程 Multi-thread 模式戏蔑。

PRAGMA SQLITE_THREADSAFE = 2

SQLite 鎖的粒度都是數(shù)據(jù)庫(kù)文件級(jí)別,并沒(méi)有實(shí)現(xiàn)表級(jí)甚至行級(jí)的鎖鲁纠,還有需要說(shuō)明的总棵,同一個(gè)句柄同一時(shí)間只有一個(gè)線程在操作,這個(gè)時(shí)候我們需要打開(kāi)連接池 Connection Pool改含。關(guān)于 SQLite 進(jìn)程與線程并發(fā)你可以參考《Android 存儲(chǔ)選項(xiàng)之 SQLite 優(yōu)化那些事兒》情龄。

回到 SQLiteConnectionPool 的構(gòu)造方法,創(chuàng)建空閑連接超時(shí)管理機(jī)制捍壤,需要說(shuō)明的是骤视,處于長(zhǎng)時(shí)間不使用的數(shù)據(jù)庫(kù)連接也是在浪費(fèi)系統(tǒng)資源,此時(shí)通過(guò)延遲關(guān)閉機(jī)制釋放那些長(zhǎng)時(shí)間處于空閑狀態(tài)的數(shù)據(jù)庫(kù)連接鹃觉。

public void setupIdleConnectionHandler(Looper looper, long timeoutMs) {
    synchronized (mLock) {
        //創(chuàng)建IdleConnectionHandler专酗,超時(shí)管理的Handler
        mIdleConnectionHandler = new IdleConnectionHandler(looper, timeoutMs);
    }
}

IdleConnectionHandler 繼承自 Handler:

private class IdleConnectionHandler extends Handler {
    //保存配置的空閑連接超時(shí)時(shí)間
    private final long mTimeout;

    IdleConnectionHandler(Looper looper, long timeout) {
        super(looper);
        mTimeout = timeout;
    }

    @Override
    public void handleMessage(Message msg) {
        // Skip the (obsolete) message if the handler has changed
        synchronized (mLock) {
            if (this != mIdleConnectionHandler) {
                return;
            }
            if (closeAvailableConnectionLocked(msg.what)) {
                //關(guān)閉空閑超時(shí)的SQLiteConnection
                
                //... 省略
            }
        }
    }

    void connectionReleased(SQLiteConnection con) {
        //根據(jù)連接id(ConnectionId),發(fā)送超時(shí)消息
        sendEmptyMessageDelayed(con.getConnectionId(), mTimeout);
    }

    void connectionAcquired(SQLiteConnection con) {
        // Remove any pending close operations
        removeMessages(con.getConnectionId());
    }

    void connectionClosed(SQLiteConnection con) {
        //移除當(dāng)前超時(shí)機(jī)制盗扇,說(shuō)明連接被重新使用
        removeMessages(con.getConnectionId());
    }
}

從源碼中我們很容易看出祷肯,通過(guò)發(fā)送延遲消息來(lái)關(guān)閉處于空閑狀態(tài)的數(shù)據(jù)庫(kù)連接,當(dāng)某個(gè)連接重新被使用時(shí)此時(shí)移除該超時(shí)關(guān)閉消息疗隶。超時(shí)時(shí)間就是在 OpenParams 中配置的 mIdleConnectionTimeout佑笋。它的默認(rèn)值是 Long 的最大值,可以理解成”永不“釋放斑鼻。

看下數(shù)據(jù)庫(kù)連接空閑超時(shí)關(guān)閉機(jī)制蒋纬,處理如下:

public SQLiteConnection acquireConnection(String sql, int connectionFlags,
        CancellationSignal cancellationSignal) {
    //獲取一個(gè)SQLiteConnection
    SQLiteConnection con = waitForConnection(sql, connectionFlags, cancellationSignal);
    synchronized (mLock) {
        if (mIdleConnectionHandler != null) {
            //移除該連接的超時(shí)關(guān)閉消息
            mIdleConnectionHandler.connectionAcquired(con);
        }
    }
    return con;
}

每當(dāng)我們從連接池獲取到一個(gè) SQLiteConnection 后,首先會(huì)根據(jù)該連接的 connectionId 移除當(dāng)前的回收 Message坚弱,mIdleConnectionHandler.connectionAcquired 方法表示當(dāng)前數(shù)據(jù)庫(kù)連接重新被使用蜀备。

當(dāng)該連接使用完成被 SQLiteConnectionPool 緩存后,又重新對(duì)該連接開(kāi)啟超時(shí)關(guān)閉機(jī)制:

public void releaseConnection(SQLiteConnection connection) {
    synchronized (mLock) {
        if (mIdleConnectionHandler != null) {
            //重新加入超時(shí)關(guān)閉機(jī)制
            mIdleConnectionHandler.connectionReleased(connection);
        }
    // ... 省略
}

SQLiteConnectionPool 構(gòu)造方法一系列任務(wù)完成后荒叶,接著看 SQLiteConnection 的 open 方法如下:

// Might throw
private void open() {
    // Open the primary connection.
    // This might throw if the database is corrupt.
    //創(chuàng)建數(shù)據(jù)庫(kù)主鏈接
    //只有主連接才可以寫數(shù)據(jù)庫(kù)琼掠,每一個(gè)數(shù)據(jù)庫(kù)連接池默認(rèn)僅有一條主連接
    mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
            true /*primaryConnection*/); // might throw
    // Mark it released so it can be closed after idle timeout
    synchronized (mLock) {
        if (mIdleConnectionHandler != null) {
            //由于此時(shí)還沒(méi)有任何使用,故開(kāi)啟空閑超時(shí)關(guān)閉機(jī)制
            mIdleConnectionHandler.connectionReleased(mAvailablePrimaryConnection);
        }
    }

    // Mark the pool as being open for business.
    mIsOpen = true;
    mCloseGuard.open("close");
}

數(shù)據(jù)庫(kù)連接池被創(chuàng)建后停撞,會(huì)默認(rèn)創(chuàng)建一條主連接 SQLiteConnection,每一個(gè)數(shù)據(jù)庫(kù)連接池默認(rèn)僅有一條主連接,只有主連接可以對(duì)數(shù)據(jù)庫(kù)寫操作戈毒,另外還包含若干條非主連接艰猬。

// Might throw.
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
        boolean primaryConnection) {
    //當(dāng)前連接的唯一id
    final int connectionId = mNextConnectionId++;
    //創(chuàng)建連接
    return SQLiteConnection.open(this, configuration,
            connectionId, primaryConnection); // might throw
}

connectionId 標(biāo)識(shí)當(dāng)前數(shù)據(jù)庫(kù)連接的唯一 connectionId,該 id 就是用于在連接處于空閑狀態(tài)時(shí)的唯一標(biāo)志埋市。 SQLiteConnection 靜態(tài)方法 open 如下:

// Called by SQLiteConnectionPool only.
static SQLiteConnection open(SQLiteConnectionPool pool,
        SQLiteDatabaseConfiguration configuration,
        int connectionId, boolean primaryConnection) {
    //創(chuàng)建SQLiteConnection冠桃,每個(gè)SQLiteConnection對(duì)一個(gè)native層一個(gè)操作句柄
    SQLiteConnection connection = new SQLiteConnection(pool, configuration,
            connectionId, primaryConnection);
    try {
        //調(diào)用自己的open方法
        connection.open();
        return connection;
    } catch (SQLiteException ex) {
        connection.dispose(false);
        throw ex;
    }
}

一樣的 API 套路,直接創(chuàng)建 SQLiteConnection道宅,并調(diào)用其 open 方法食听。SQLiteConnection 的構(gòu)造方法:

private SQLiteConnection(SQLiteConnectionPool pool,
        SQLiteDatabaseConfiguration configuration,
        int connectionId, boolean primaryConnection) {
    //持有數(shù)據(jù)庫(kù)連接池
    mPool = pool;
    //操作日志
    mRecentOperations = new OperationLog(mPool);
    //也持有數(shù)據(jù)庫(kù)配置信息
    mConfiguration = new SQLiteDatabaseConfiguration(configuration);
    //該鏈接的唯一id
    mConnectionId = connectionId;
    //是否是主鏈接
    mIsPrimaryConnection = primaryConnection;
    //是否是只讀模式
    mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
    //緩存預(yù)編譯后的SQL語(yǔ)句,內(nèi)部采用LRU算法
    mPreparedStatementCache = new PreparedStatementCache(
            mConfiguration.maxSqlCacheSize);
    mCloseGuard.open("close");
}

SQLiteConnection 同樣保存了當(dāng)前數(shù)據(jù)庫(kù)配置信息 SQLiteDatabaseConfiguration污茵、還包括當(dāng)前連接的唯一 connectionId樱报、是否是主連接等。

這里需要說(shuō)明的是 PreparedStatementCache 本質(zhì)是一個(gè) LRU 緩存泞当,它用于緩存編譯 SQL 語(yǔ)句后的 PreparedStatement 對(duì)象迹蛤,一個(gè) SQLiteConnecion 包含多個(gè) PreparedStatement,可以使用它多次高效的執(zhí)行同一個(gè) SQL 語(yǔ)句襟士。

SQLiteConnection 的 open 方法如下:

private void open() {
    //創(chuàng)建數(shù)據(jù)庫(kù)操作句柄
    //同一個(gè)句柄同一時(shí)間只能有同一個(gè)線程在操作
    //SQLiteDatabase使用ThreadLocal解決多線程操作問(wèn)題
    mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
            mConfiguration.label,
            SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME,
            mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
    //設(shè)置頁(yè)緩存大小
    setPageSize();
    setForeignKeyModeFromConfiguration();
    //根據(jù)Configuration設(shè)置WAL模式
    setWalModeFromConfiguration();
    //設(shè)置日志限制大小
    setJournalSizeLimit();
    //設(shè)置檢查點(diǎn)信息
    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);
    }
}

這里通過(guò) nativeOpen 方法獲取一個(gè)數(shù)據(jù)庫(kù)操作連接(native 層 SQLiteConnection)盗飒,每個(gè) Java 層 SQLiteConnection 都會(huì)對(duì)應(yīng)一個(gè) native 層 SQLiteConnection 數(shù)據(jù)庫(kù)連接。每個(gè) native 層 SQLiteConnection 都會(huì)持有一個(gè)數(shù)據(jù)庫(kù)操作句柄:

static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
115        jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
116    int sqliteFlags;
117    if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
118        sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
119    } else if (openFlags & SQLiteConnection::OPEN_READONLY) {
120        sqliteFlags = SQLITE_OPEN_READONLY;
121    } else {
122        sqliteFlags = SQLITE_OPEN_READWRITE;
123    }
124
125    const char* pathChars = env->GetStringUTFChars(pathStr, NULL);
126    String8 path(pathChars);
127    env->ReleaseStringUTFChars(pathStr, pathChars);
128
129    const char* labelChars = env->GetStringUTFChars(labelStr, NULL);
130    String8 label(labelChars);
131    env->ReleaseStringUTFChars(labelStr, labelChars);
132
       //數(shù)據(jù)庫(kù)操作句柄
133    sqlite3* db;
       //打開(kāi)一個(gè)數(shù)據(jù)庫(kù)
134    int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL);
135    if (err != SQLITE_OK) {
           //是否正確打開(kāi)
136        throw_sqlite3_exception_errcode(env, err, "Could not open database");
137        return 0;
138    }
139
140    // Check that the database is really read/write when that is what we asked for.
141    if ((sqliteFlags & SQLITE_OPEN_READWRITE) && sqlite3_db_readonly(db, NULL)) {
           //如果打開(kāi)數(shù)據(jù)庫(kù)模式與當(dāng)前不匹配
142        throw_sqlite3_exception(env, db, "Could not open the database in read/write mode.");
143        sqlite3_close(db);
144        return 0;
145    }
146
147    // Set the default busy handler to retry automatically before returning SQLITE_BUSY.
148    err = sqlite3_busy_timeout(db, BUSY_TIMEOUT_MS);
149    if (err != SQLITE_OK) {
           //設(shè)置默認(rèn)超時(shí)機(jī)制
150        throw_sqlite3_exception(env, db, "Could not set busy timeout");
151        sqlite3_close(db);
152        return 0;
153    }
154
155    // Register custom Android functions.
156    err = register_android_functions(db, UTF16_STORAGE);
157    if (err) {
158        throw_sqlite3_exception(env, db, "Could not register Android SQL functions.");
159        sqlite3_close(db);
160        return 0;
161    }
162
163    // Create wrapper object.
       //創(chuàng)建數(shù)據(jù)庫(kù)連接陋桂,內(nèi)部持有數(shù)據(jù)庫(kù)操作句柄
164    SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label);
165
166    // Enable tracing and profiling if requested.
167    if (enableTrace) {
168        sqlite3_trace(db, &sqliteTraceCallback, connection);
169    }
170    if (enableProfile) {
171        sqlite3_profile(db, &sqliteProfileCallback, connection);
172    }
173
174    ALOGV("Opened connection %p with label '%s'", db, label.string());
175    return reinterpret_cast<jlong>(connection);
176}

SQLiteConnection 是數(shù)據(jù)庫(kù)操作真正開(kāi)始的地方逆趣,每一個(gè) SQLiteConnection 內(nèi)部持有一個(gè)數(shù)據(jù)庫(kù)操作句柄。同一個(gè)句柄同一時(shí)間只有一個(gè)線程在操作嗜历,這也是我們要開(kāi)啟數(shù)據(jù)庫(kù)連接池 Connection Pool 提高數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)的目的宣渗。

SQLiteConnection 中持有對(duì)應(yīng) native 層的匿名內(nèi)存描述符 mConnectionPtr。以數(shù)據(jù)庫(kù)查詢?yōu)槔?/p>

public int executeForCursorWindow(String sql, Object[] bindArgs,
        CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
        CancellationSignal cancellationSignal) {

        // ... 省略
        
        try {
            //獲取復(fù)用編譯SQL語(yǔ)句后的對(duì)象
            final PreparedStatement statement = acquirePreparedStatement(sql);
            try {
                try {
                    //mConnectionPtr對(duì)應(yīng)native層SQLiteConnection
                    //mStatementPtr對(duì)應(yīng)nativePrepardStatement
                    //mWindowPtr對(duì)應(yīng)native的CursorWindow秸脱,存放查詢結(jié)果集
                    final long result = nativeExecuteForCursorWindow(
                            mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
                            startPos, requiredPos, countAllRows);
                    actualPos = (int)(result >> 32);
                    countedRows = (int)result;
                    filledRows = window.getNumRows();
                    window.setStartPosition(actualPos);
                    return countedRows;
                } finally {
                    detachCancellationSignal(cancellationSignal);
                }
            } finally {
                releasePreparedStatement(statement);
            }
        } catch (RuntimeException ex) {
            mRecentOperations.failOperation(cookie, ex);
            throw ex;
        } finally {
           //...省略
        }
        
        // ... 省略
}

調(diào)用 SQLiteConnection 的 native 方法 nativeExecuteForCursorWindow 完成數(shù)據(jù)庫(kù)操作過(guò)程落包。

static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,
666        jlong connectionPtr, jlong statementPtr, jlong windowPtr,
667        jint startPos, jint requiredPos, jboolean countAllRows) {
       //轉(zhuǎn)換成native層SQLiteConnection
668    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
       //轉(zhuǎn)換成對(duì)應(yīng)native層Statement
669    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
       //對(duì)應(yīng)native層CursorWindow
670    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
671
672    status_t status = window->clear();
673    if (status) {
674        String8 msg;
675        msg.appendFormat("Failed to clear the cursor window, status=%d", status);
676        throw_sqlite3_exception(env, connection->db, msg.string());
677        return 0;
678    }
 //... 省略
}

SQLiteDatabase 的操作最后都交由 SQLiteConnection 來(lái)完成;SQLiteConnection 表示一條數(shù)據(jù)庫(kù)操作連接摊唇,是真正執(zhí)行數(shù)據(jù)庫(kù)操作開(kāi)始的地方咐蝇。

本文只是重點(diǎn)介紹了 Android 提供的數(shù)據(jù)庫(kù)框架 SQLiteDatabase 的創(chuàng)建過(guò)程源碼分析,關(guān)于這部分更詳細(xì)分析可以繼續(xù)閱讀文章開(kāi)頭給出的 SQLite 存儲(chǔ)系列的其他文章巷查。

總結(jié)

Android 系統(tǒng)為我們提供了 SQLiteOpenHelper 輔助完成 SQLiteDatabase 的創(chuàng)建有序,包括數(shù)據(jù)表創(chuàng)建、升/降級(jí)岛请、打開(kāi)旭寿、關(guān)閉等操作。通過(guò) getReadableDatabase / getWriteableDatabase 分別獲取只讀或可讀/寫的 SQLiteDatabase 對(duì)象崇败。

SQLiteConnectionPool 主要為提高數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)性能盅称;SQLiteDatabase 根據(jù)配置信息 SQLiteDatabaseConfiguration 創(chuàng)建 SQLiteConnectionPool 連接池肩祥,包括連接池大小、WAL 模式缩膝、空閑連接超時(shí)等混狠。SQLiteConnectionPool 緩存所有數(shù)據(jù)庫(kù)操作連接 SQLiteConnection。

數(shù)據(jù)庫(kù)連接池 SQLiteConnectionPool 被創(chuàng)建后疾层,會(huì)默認(rèn)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)主連接 SQLiteConnection将饺。每個(gè) Java 層 SQLiteConnection 對(duì)應(yīng)一個(gè) native 層 SQLiteConnection,每個(gè) SQLiteConnection 中又包含一個(gè)數(shù)據(jù)庫(kù)操作句柄痛黎。

簡(jiǎn)單整理下整個(gè)關(guān)系

SQLiteDatabase創(chuàng)建過(guò)程

以上便是個(gè)人在學(xué)習(xí) SQLiteDatabase 數(shù)據(jù)庫(kù)創(chuàng)建過(guò)程的體會(huì)和總結(jié)予弧,文中如有不妥或有更好的分析結(jié)果,歡迎大家指出湖饱。

如果文章對(duì)你有幫助掖蛤,就請(qǐng)留個(gè)贊吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琉历,一起剝皮案震驚了整個(gè)濱河市坠七,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旗笔,老刑警劉巖彪置,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蝇恶,居然都是意外死亡拳魁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門撮弧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)潘懊,“玉大人,你說(shuō)我怎么就攤上這事贿衍∈谥郏” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵贸辈,是天一觀的道長(zhǎng)释树。 經(jīng)常有香客問(wèn)我,道長(zhǎng)擎淤,這世上最難降的妖魔是什么奢啥? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮嘴拢,結(jié)果婚禮上桩盲,老公的妹妹穿的比我還像新娘。我一直安慰自己席吴,他們只是感情好赌结,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布捞蛋。 她就那樣靜靜地躺著,像睡著了一般姑曙。 火紅的嫁衣襯著肌膚如雪襟交。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天伤靠,我揣著相機(jī)與錄音,去河邊找鬼啼染。 笑死宴合,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的迹鹅。 我是一名探鬼主播卦洽,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斜棚!你這毒婦竟也來(lái)了阀蒂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤弟蚀,失蹤者是張志新(化名)和其女友劉穎蚤霞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體义钉,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昧绣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捶闸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夜畴。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖删壮,靈堂內(nèi)的尸體忽然破棺而出贪绘,到底是詐尸還是另有隱情,我是刑警寧澤央碟,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布税灌,位于F島的核電站,受9級(jí)特大地震影響硬耍,放射性物質(zhì)發(fā)生泄漏垄琐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一经柴、第九天 我趴在偏房一處隱蔽的房頂上張望狸窘。 院中可真熱鬧,春花似錦坯认、人聲如沸翻擒。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)陋气。三九已至劳吠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巩趁,已是汗流浹背痒玩。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留议慰,地道東北人蠢古。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像别凹,于是被迫代替她去往敵國(guó)和親草讶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354