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ù)文件也被刪除蒸苇。
- 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)系
以上便是個(gè)人在學(xué)習(xí) SQLiteDatabase 數(shù)據(jù)庫(kù)創(chuàng)建過(guò)程的體會(huì)和總結(jié)予弧,文中如有不妥或有更好的分析結(jié)果,歡迎大家指出湖饱。
如果文章對(duì)你有幫助掖蛤,就請(qǐng)留個(gè)贊吧!