
Android 存儲(chǔ)優(yōu)化系列專題
- SharedPreferences 系列
《Android 之不要濫用 SharedPreferences》
《Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失》
- ContentProvider 系列(待更)
《Android 存儲(chǔ)選項(xiàng)之 ContentProvider 啟動(dòng)過程源碼分析》
《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)建過程源碼分析》
《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)化那些事兒》
在上一篇《Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 創(chuàng)建過程源碼分析》一文,為大家分析了 Android 平臺(tái)提供的數(shù)據(jù)庫(kù)操作對(duì)象 SQLiteDatabase 創(chuàng)建過程,以及關(guān)于 SQLiteDatabase、SQLiteConnectionPool、SQLiteConnection 三者之間的緊密關(guān)系。
如果說 SQLiteOpenHelper 可能是我們?cè)?Android 平臺(tái)使用 SQLite 數(shù)據(jù)庫(kù)接觸到的第一個(gè)類,那 SQLiteDatabase 可能就是那個(gè)最關(guān)鍵的類了,實(shí)際上它內(nèi)部的絕大多數(shù)函數(shù)也是圍繞數(shù)據(jù)庫(kù)操作進(jìn)行封裝的插入、刪除、更新、查詢和關(guān)閉等核心任務(wù)。該篇文章也是圍繞這些內(nèi)容對(duì) SQLiteDatabase 源碼做進(jìn)一步的分析。
SQLiteDatabase 介紹
SQLiteDatabase 對(duì)象在概念上比較容易理解,與 SQLite C API 的底層數(shù)據(jù)庫(kù)對(duì)象相似,但是實(shí)際情況要復(fù)雜得多。
SQLiteDatabase 類中包含 50 多個(gè)方法,每個(gè)方法都有自己的用途,而且每個(gè)方法相對(duì)獨(dú)立于其它方法,它們大部分都是為完成一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)任務(wù),比如表的選擇、插入、更新、刪除語句。這里只是列舉了一些重要的方法介紹。詳細(xì)你可以參照這里。
1. 使用 SQLiteDatabase 類執(zhí)行普通查詢操作
如同有很多創(chuàng)建 SQL 語句的方法,SQLiteDatabase 也提供了大量的方法在 SQLite 數(shù)據(jù)庫(kù)中運(yùn)行 SQL 語句。事實(shí)上 Android 提供了多達(dá) 16 種方法在 SQLite 數(shù)據(jù)庫(kù)中運(yùn)行一般或特殊的查詢。
這些函數(shù)包含 SQL 語句函數(shù),如單一表格插入、更新等,也包括一般的執(zhí)行 DML 和 DDL 的函數(shù)。這些函數(shù)分組顯示如下:
void execSQL(String sql)
void execSQL(String sql, Object[] bindArgs)
Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy,String having, String orderBy, String limit)
Cursor query(String table, String[] columns, String selection, String[ selectionArgs, String groupBy, String having, String orderBy)
Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
Cursor queryWithFactory(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
Cursor rawQuery(String sql, String[] selectionArgs)
Cursor rawQueryWithFactory(SQLiteDatabase,CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable)
雖然函數(shù)很多,但是它們可以歸結(jié)為三個(gè)核心函數(shù):execSQL、query 和 rawQuery,它們各自的使用場(chǎng)景相信大家也一定分的出。
2. 增加、刪除、修改等操作
上面是一系列數(shù)據(jù)庫(kù)查詢操作,另外對(duì)數(shù)據(jù)庫(kù)的插入、更新、刪除操作方法如下:
int delete(String table, String whereClause, String[] whereArgs)
long insert(String table, String nullColumnHack, ContentValues initialValues)
...
long replace(String table, String nullColumnHack, ContentValues initialValues)
...
int update(String table, ContentValues values, String whereClause, Stirng[] whereArgs)
...
文中并沒有將所有數(shù)據(jù)庫(kù)操作 API 進(jìn)行貼出,不過即使通過方法名我們也很容易理解每個(gè)方法的作用。這些方法看上去很吸引人,其實(shí)它們的設(shè)計(jì)主要也是為了面向?qū)ο蟮某绦騿T設(shè)計(jì)的。
3. compileStatement 函數(shù)
SQLiteDatabase 中還有一個(gè)方法比較重要,它就是 compileStatement:
publi SQLiteStatement compileStatement(String sql)
將一個(gè) SQL 語句作為參數(shù),返回一個(gè) SQLiteStatement 對(duì)象,事實(shí)上,正如函數(shù)名表示的,這個(gè)函數(shù)將編譯 SQL 語句并允許將其重用。關(guān)于它的使用將在后面一篇文章《Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 優(yōu)化那些事兒》進(jìn)行介紹。
4. SQLiteDatabase 類的事務(wù)管理
Android 很看重 SQLite 事務(wù)管理。在 SQLiteDatabase 中很多方法用來啟動(dòng)、結(jié)束和管理事務(wù)的。
void beginTransaction()
void beginTransactionWithListener(SQLiteTransactionListener transactionListener)
void endTransaction()
boolean inTransacetion()
void setTransactionSuccessful()
這些方法不用太多解釋。beginTransaction() 啟動(dòng) SQLite 事務(wù),endTransaction() 結(jié)束當(dāng)前的事務(wù)。重要的是,決定事務(wù)是否被提交或者回滾取決于事務(wù)是否被標(biāo)注了 “clean”。setTrancactionSuccessful() 函數(shù)用來設(shè)置這個(gè)標(biāo)志位,這個(gè)額外的步驟剛開始讓人反感,但事實(shí)上它保證了在事務(wù)提交前對(duì)所做更改的檢查。如果不存在事務(wù)或者事務(wù)已經(jīng)是成功狀態(tài),那么 setTransactionSuccessful() 函數(shù)就會(huì)拋出 IllegalStateException 異常。
isTransaction() 函數(shù)測(cè)試是否存在活動(dòng)事務(wù),如果有,則返回 true。
beginTransactionWithListener 函數(shù)接收 SQLiteTransaction-Listener 對(duì)象,SQLiteTransactionListener 負(fù)責(zé)監(jiān)聽與事務(wù)相關(guān)的一切操作,比如提交、回滾或者嵌套地開始一個(gè)新事物。
關(guān)于 SQLiteDatabase 事務(wù)源碼分析可以參考后續(xù)文章《SQLiteDatabase 啟用事務(wù)源碼分析》
5. SQLiteDatabase 中其它函數(shù)
SQLiteDatabase 中還涉及到一些其它方法:
public long getMaximumSize() 返回?cái)?shù)據(jù)庫(kù)允許的最大值
public int getVersion() 返回應(yīng)用創(chuàng)建數(shù)據(jù)庫(kù)的版本號(hào)
public boolean isDbLockedByCurrentThread() 測(cè)試當(dāng)前線程是否鎖住了數(shù)據(jù)庫(kù)。
public boolean isDbLockedByOtherThreads() 測(cè)試是否有其他線程鎖住了數(shù)據(jù)庫(kù)。
public static int releaseMemory() 釋放書庫(kù)不再需要的資源,比如內(nèi)存等。返回釋放資源的字節(jié)數(shù)。
經(jīng)過上面的一系列介紹,相信你對(duì) SQLiteDatabase 的工作內(nèi)容有了進(jìn)一步認(rèn)識(shí),接下來我們通過源碼的角度具體分析下數(shù)據(jù)庫(kù)的相關(guān)操作。
SQLiteCloseable
在分析 SQLiteDatabase 源碼之前我想先來說下 SQLiteCloseable 類。因?yàn)?SQLiteDatabase 中會(huì)出現(xiàn)大量該邏輯的操作,這里先對(duì)其進(jìn)行說明。
其實(shí)關(guān)于 SQLiteCloseable 完全可以理解成 Java 為 I/O 提供的 Closeable 標(biāo)準(zhǔn)接口,SQLiteCloseable 就是專門為數(shù)據(jù)庫(kù)釋放提供的標(biāo)準(zhǔn) API(SQLiteCloseable 也實(shí)現(xiàn)了 Closeable)。
在 android/database 包下類似 SQLiteDatabase、CursorWindow、SQLiteQuery 等都實(shí)現(xiàn)了該接口。
通過源碼分析它的工作過程,申請(qǐng)操作:
public void acquireReference() {
synchronized (this) {
if (mReferenceCount <= 0) {
//表示數(shù)據(jù)庫(kù)已經(jīng)關(guān)閉
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + this);
}
//引用計(jì)數(shù)++
mReferenceCount++;
}
}
對(duì)應(yīng)釋放操作:
public void releaseReference() {
boolean refCountIsZero = false;
synchronized (this) {
//釋放對(duì)該對(duì)象的引用
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
//當(dāng)前不存在任何引用
//通知關(guān)閉
onAllReferencesReleased();
}
}
從源碼我們可以看出,其內(nèi)部通過引用計(jì)數(shù)的方式確定當(dāng)前對(duì)象是否還被外部引用。
當(dāng)引用計(jì)數(shù) mReferenceCount == 0 時(shí),此時(shí)會(huì)調(diào)用 onAllReferencesReleased 方法,該方法原型:
/**
* Called when the last reference to the object was released by
* a call to {@link #releaseReference()} or {@link #close()}.
*/
protected abstract void onAllReferencesReleased();
這其實(shí)也比較好理解,當(dāng)涉及到具體釋放過程時(shí)就需要具體實(shí)現(xiàn)類自行實(shí)現(xiàn)該過程。
以 SQLiteDatabase 中的實(shí)現(xiàn)為例:
//在SQLiteDatabase中重寫SQLiteCloseable中的onAllReferencesReleased
protected void onAllReferencesReleased() {
dispose(false);
}
看下引用計(jì)數(shù)在具體實(shí)現(xiàn)類 SQLiteDatabase 中的工作過程,我們以查詢?yōu)槔?/p>
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
//引用計(jì)數(shù)++
acquireReference();
try {
//... 省略
} finally {
//釋放
releaseReference();
}
}
每次對(duì) SQLiteDatabase 的訪問操作,首先會(huì)通過 acquireReference 使引用計(jì)數(shù)++,訪問結(jié)束通過 releaseReference 引用計(jì)數(shù) --,此時(shí)如果 mReferenceCount == 0,回調(diào) SQLiteDatabase 中重寫的 onAllReferencesReleased 方法,內(nèi)部調(diào)用 dispose 方法:
private void dispose(boolean finalized) {
final SQLiteConnectionPool pool;
synchronized (mLock) {
if (mCloseGuardLocked != null) {
if (finalized) {
mCloseGuardLocked.warnIfOpen();
}
mCloseGuardLocked.close();
}
//當(dāng)前SQLiteDatabase對(duì)象的連接池
pool = mConnectionPoolLocked;
mConnectionPoolLocked = null;
}
if (!finalized) {
synchronized (sActiveDatabases) {
//移除在WeakHashMap中
sActiveDatabases.remove(this);
}
if (pool != null) {
//關(guān)閉連接池
pool.close();
}
}
}
關(guān)于 dispose 它接受一個(gè) boolean 類型的字段,表示當(dāng)前是主動(dòng)(close)關(guān)閉還是被動(dòng)(finalize 回調(diào))關(guān)閉。在 SQLiteDatabase、 SQLiteConnectionPool、SQLiteConnection 等都提供了該套路,以 SQLiteDatabase 的 finalize 為例:
@Override
protected void finalize() throws Throwable {
try {
dispose(true);
} finally {
super.finalize();
}
}
這也是 finalize 典型使用場(chǎng)景,雖然 finalize 方法 的調(diào)用時(shí)機(jī)不確定,但是我們可以利用該方法實(shí)現(xiàn)某個(gè)對(duì)象的兜底回收策略,減小或減少發(fā)生內(nèi)存泄漏的影響或可能。
在 SQLiteDatabase dispose 方法中,我們重點(diǎn)關(guān)注下數(shù)據(jù)庫(kù)連接池的關(guān)閉操作 SQLiteConnectionPool 的 close 方法:
public void close() {
dispose(false);
}
//一樣的套路,調(diào)用dispose方法
private void dispose(boolean finalized) {
//...省略
if (!finalized) {
synchronized (mLock) {
//已經(jīng)關(guān)閉的連接池將會(huì)拋出異常。
throwIfClosedLocked();
//標(biāo)志是否已經(jīng)關(guān)閉
mIsOpen = false;
//關(guān)閉所有數(shù)據(jù)庫(kù)連接SQLiteConnection
closeAvailableConnectionsAndLogExceptionsLocked();
final int pendingCount = mAcquiredConnections.size();
if (pendingCount != 0) {
Log.i(TAG, "The connection pool for " + mConfiguration.label
+ " has been closed but there are still "
+ pendingCount + " connections in use. They will be closed "
+ "as they are released back to the pool.");
}
wakeConnectionWaitersLocked();
}
}
}
dispose 方法中包含一個(gè)方法名非常長(zhǎng)的 closeAvailableConnectionsAndLogExceptionsLocked 方法如下:
private void closeAvailableConnectionsAndLogExceptionsLocked() {
//關(guān)閉所有連接池中緩存的非主連接
closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
if (mAvailablePrimaryConnection != null) {
//關(guān)閉主連接
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
mAvailablePrimaryConnection = null;
}
}
在數(shù)據(jù)庫(kù)連接池 SQLiteConnectionPool 中通過一個(gè) List 對(duì)象緩存所有非數(shù)據(jù)庫(kù)主連接,關(guān)閉該些連接方法如下:
private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
//當(dāng)前SQLiteDatabase對(duì)象的所有非主連連接都被mAvailableNonPrimaryConnections緩存
final int count = mAvailableNonPrimaryConnections.size();
for (int i = 0; i < count; i++) {
//關(guān)閉連接池中每個(gè)非主連接 SQLiteConnection
closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
}
//清空連接池緩存
mAvailableNonPrimaryConnections.clear();
}
直接遍歷該集合關(guān)閉所有連接(非主連接)并清空緩存。注意數(shù)據(jù)庫(kù)主連接并沒有被緩存在該 List 對(duì)象中,如上也是直接關(guān)閉它。
private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
try {
//直接關(guān)閉該連接SQLiteConnection
connection.close(); // might throw
if (mIdleConnectionHandler != null) {
//移除延時(shí)關(guān)閉連接機(jī)制
mIdleConnectionHandler.connectionClosed(connection);
}
} catch (RuntimeException ex) {
Log.e(TAG, "Failed to close connection, its fate is now in the hands "
+ "of the merciful GC: " + connection, ex);
}
}
直接調(diào)用 SQLiteConnection 的 close 方法,并移除該連接的延遲關(guān)閉消息。
//SQLiteConnection的close方法
void close() {
dispose(false);
}
//一樣的套路,還是調(diào)用dispose方法
private void dispose(boolean finalized) {
if (mCloseGuard != null) {
if (finalized) {
mCloseGuard.warnIfOpen();
}
mCloseGuard.close();
}
if (mConnectionPtr != 0) {
final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
//調(diào)用native層關(guān)閉對(duì)應(yīng)native層SQLiteConnection
nativeClose(mConnectionPtr);
//將對(duì)native持有句柄置為0
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
}
}
}
每個(gè) Java 層 SQLiteConnection 都會(huì)對(duì)應(yīng)一個(gè) native 層 SQLiteConnection,此時(shí)調(diào)用 nativeClose 方法關(guān)閉對(duì)應(yīng)的 native 對(duì)象,并將指向的匿名內(nèi)存描述符 mConnectionPtr 置為 0:
static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
//轉(zhuǎn)換成native層的SQLiteConnection對(duì)象
179 SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
180
181 if (connection) {
182 ALOGV("Closing connection %p", connection->db);
//關(guān)閉該數(shù)據(jù)庫(kù)操作句柄
183 int err = sqlite3_close(connection->db);
184 if (err != SQLITE_OK) {
185 // This can happen if sub-objects aren't closed first. Make sure the caller knows.
186 ALOGE("sqlite3_close(%p) failed: %d", connection->db, err);
187 throw_sqlite3_exception(env, connection->db, "Count not close db.");
188 return;
189 }
//刪除SQLiteConnection,釋放其占用內(nèi)存
191 delete connection;
192 }
193 }
SQLiteCloseable 輔助完成數(shù)據(jù)庫(kù)相關(guān)核心類的申請(qǐng)和釋放操作,然而它只是 SQLiteDatabase 操作涉及到的其中一個(gè)細(xì)節(jié),這樣的細(xì)節(jié)功能在 SQLiteDatabae 中還非常的多。感興趣的朋友可以更深入源碼進(jìn)行分析。
查詢操作
相比增加、刪除和更新等操作,數(shù)據(jù)庫(kù)的查詢可能是最復(fù)雜的一個(gè)過程,下面就我們就先從 SQLiteDatabase 的查詢操作開始分析,然后再去分析其它的數(shù)據(jù)庫(kù)操作就會(huì)變得非常輕松。示例如下:
public String callInBackground(@NonNull SQLiteDatabase db) {
db.beginTransactionNonExclusive();
Cursor cursor = null;
try {
//這里直接使用SQL語句進(jìn)行查詢
cursor = db.rawQuery("SELECT name FROM user LIMIT 100", null);
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
final String name = cursor.getString(cursor.getColumnIndex("name"));
DbLog.e(TAG, "name: " + name);
}
}
db.setTransactionSuccessful();
} catch (Exception e) {
DbLog.print(e);
} finally {
db.endTransaction();
if (cursor != null) {
cursor.close();
}
}
return null;
}
直接使用 SQL 語句調(diào)用 SQLiteDatabase 的 rawQuery 方法,SQLiteDatabase 中所有的查詢操作最終都會(huì)從 rawQueryWithFactory 方法開始:
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
//引用計(jì)數(shù) ++
acquireReference();
try {
//創(chuàng)建SQLiteCursorDriver對(duì)象
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
cancellationSignal);
//使用SQLiteDirectCursorDriver完成查詢
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
//釋放引用計(jì)數(shù) --
releaseReference();
}
}
這里直接創(chuàng)建 SQLiteDirectCursorDriver,然后調(diào)用它的 query 方法:
public Cursor query(CursorFactory factory, String[] selectionArgs) {
//查詢使用的是SQLiteQuery
//實(shí)際SQLiteCursor內(nèi)部使用SQLiteQuery開始查詢數(shù)據(jù)
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs);
if (factory == null) {
//默認(rèn)返回SQLiteCursor
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
//配置的CursorFactory在這里調(diào)用,自定義Cursor
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
}
mQuery = query;
return cursor;
}
Cursor 本質(zhì)只是一個(gè)接口,約束了對(duì)查詢數(shù)據(jù)獲取的接口規(guī)則,實(shí)際查詢返回的 Cursor 類型是 SQLiteCursor。可以看到創(chuàng)建 SQLiteQuery 對(duì)象作為參數(shù)傳入 SQLiteCurosr。先看下 SQLiteQuery 的創(chuàng)建過程:
SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {
super(db, query, null, cancellationSignal);
mCancellationSignal = cancellationSignal;
}
SQLiteQuery 實(shí)際僅包含一個(gè)非常重要的方法 fillWindow,在遍歷 Cursor 時(shí)會(huì)分析到它;SQLiteQuery 大部分邏輯操作都在其父類 SQLiteProgram 中:
SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
CancellationSignal cancellationSignalForPrepare) {
mDatabase = db;
mSql = sql.trim();
//這里根據(jù)SQL語句判斷當(dāng)前的操作類型
int n = DatabaseUtils.getSqlStatementType(mSql);
switch (n) {
case DatabaseUtils.STATEMENT_BEGIN:
case DatabaseUtils.STATEMENT_COMMIT:
case DatabaseUtils.STATEMENT_ABORT:
mReadOnly = false;
mColumnNames = EMPTY_STRING_ARRAY;
mNumParameters = 0;
break;
default:
//查詢會(huì)走這里
boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
//這里作為出參入?yún)⒈4娌樵償?shù)據(jù)
SQLiteStatementInfo info = new SQLiteStatementInfo();
db.getThreadSession().prepare(mSql,
db.getThreadDefaultConnectionFlags(assumeReadOnly),
cancellationSignalForPrepare, info);
//將SQLiteStatementInfo 中數(shù)據(jù)賦值給當(dāng)前成員
mReadOnly = info.readOnly;
mColumnNames = info.columnNames;
mNumParameters = info.numParameters;
break;
}
// ... 省略
}
根據(jù) getSqlStatementType 判斷當(dāng)前操作類型,這里直接告訴大家:內(nèi)部根據(jù) SQL 語句前綴判斷其操作類型,查詢語句返回的是 STATEMENT_SELECT,此時(shí)會(huì)走 default 流程。
然后創(chuàng)建 SQLiteStatementInfo 對(duì)象,實(shí)際它內(nèi)部?jī)H包含三個(gè)成員:
public final class SQLiteStatementInfo {
/**
* The number of parameters that the statement has.
* SQL語句包含的參數(shù)數(shù)量
*/
public int numParameters;
/**
* The names of all columns in the result set of the statement.
* 返回查詢列名稱數(shù)組
*/
public String[] columnNames;
/**
* True if the statement is read-only.
* 是否是只讀的
*/
public boolean readOnly;
}
SQLiteStatementInfo 起到一個(gè)出參入?yún)⒈4鏀?shù)據(jù)的角色,后面會(huì)分析到;接著看 SQLiteDatabase 的 getThreadSession 方法,它返回一個(gè) SQLiteSession 對(duì)象:
SQLiteSession getThreadSession() {
//從當(dāng)前線程的ThreadLocal中嘗試獲取
return mThreadSession.get(); // initialValue() throws if database closed
}
在上篇《Android 存儲(chǔ)選項(xiàng)之 SQLiteDatabase 的創(chuàng)建過程源碼分析》中有給大家提到,SQLite 數(shù)據(jù)庫(kù)操作的同一個(gè)句柄同一時(shí)間只有一個(gè)線程在操作。 Android 中每個(gè) Java 層 SQLiteConnection 對(duì)應(yīng)一個(gè) native 層的 SQLiteConnection,每個(gè) SQLiteConnection 中會(huì)包含一個(gè) SQLite 數(shù)據(jù)庫(kù)操作句柄。
那它是如何保證多線程句柄操作的呢?實(shí)際就是通過 ThreadLocal(線程局部變量),看下它在 SQLiteDatabase 中的定義:
private final ThreadLocal<SQLiteSession> mThreadSession = ThreadLocal
.withInitial(this::createSession);
//如果獲取不到則會(huì)調(diào)用這里,為當(dāng)前線程創(chuàng)建
//一個(gè)新的SQLiteSession
SQLiteSession createSession() {
final SQLiteConnectionPool pool;
synchronized (mLock) {
throwIfNotOpenLocked();
pool = mConnectionPoolLocked;
}
return new SQLiteSession(pool);
}
ThreadLocal 能夠保證線程私有,簡(jiǎn)單來說,ThreadLocal 為每個(gè)使用該變量的線程提供私有的變量副本,所以每個(gè)線程都可以獨(dú)立地改變自己的部分,而不會(huì)影響到其它線程所對(duì)應(yīng)的副本。也就是數(shù)據(jù)庫(kù)操作的每個(gè)線程都會(huì)擁有自己獨(dú)立的 SQLiteSession,構(gòu)造方法如下:
public SQLiteSession(SQLiteConnectionPool connectionPool) {
if (connectionPool == null) {
throw new IllegalArgumentException("connectionPool must not be null");
}
//持有當(dāng)前SQLiteDatabase的數(shù)據(jù)庫(kù)連接池
mConnectionPool = connectionPool;
}
然后調(diào)用 SQLiteSeesion 的 prepare 方法:
public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal,
SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
//在數(shù)據(jù)庫(kù)連接池中獲取一個(gè)有效的連接SQLiteConnection
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
//調(diào)用SQLiteConnection的prepare方法
mConnection.prepare(sql, outStatementInfo); // might throw
} finally {
releaseConnection(); // might throw
}
}
//調(diào)用acquireConnection方法到數(shù)據(jù)庫(kù)連接池獲取一個(gè)有效的數(shù)據(jù)庫(kù)連接
private void acquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
//從數(shù)據(jù)庫(kù)連接池中獲取一個(gè)有效的連接
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // might throw
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
由于獲取數(shù)據(jù)庫(kù)連接的內(nèi)容相對(duì)比較多,主要涉及到 SQLiteConnectionPool 中的工作,你可以參考《Android 數(shù)據(jù)庫(kù)之 SQLiteConnectionPool 源碼分析》。
如果從連接池正常獲取到連接,調(diào)用它的 prepare 方法,SQLiteConnection 的 prepare 方法如下:
public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
try {
//獲取一個(gè)PreparedStatement對(duì)象
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
//outStatementInfo為在SQLiteProgram中傳入的
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
//獲取當(dāng)前查詢的所有列長(zhǎng)度
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
//如果獲取到列長(zhǎng)度為0,此時(shí)為空數(shù)組
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
//根據(jù)長(zhǎng)度創(chuàng)建String[]
outStatementInfo.columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
//按照順序獲取所有的列名,保存在SQLiteStatementInfo的String數(shù)組中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
}
}
} finally {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
優(yōu)先根據(jù) SQL 語句通過 acquirePreparedStatement 方法嘗試獲取一個(gè)復(fù)用的 PreparedStatement 對(duì)象,PreparedStatement 表示預(yù)編譯后的 SQL 語句對(duì)象,SQL 語句被預(yù)編譯并存儲(chǔ)在 PreparedStatement 對(duì)象中,然后可以使用此對(duì)象多次高效地執(zhí)行該語句??聪?PreparedStatement 的聲明:
private static final class PreparedStatement {
// Next item in pool.
//鏈表結(jié)構(gòu)
public PreparedStatement mPoolNext;
//對(duì)應(yīng)執(zhí)行的SQL語句
public String mSql;
//對(duì)應(yīng)native層PreparedStatement
public long mStatementPtr;
//參數(shù)數(shù)量
public int mNumParameters;
//操作類型
public int mType;
//是否只讀
public boolean mReadOnly;
// True if the statement is in the cache.
//是否在緩存中
public boolean mInCache;
//是否正在使用
public boolean mInUse;
}
每個(gè)數(shù)據(jù)庫(kù)連接池都會(huì)緩存執(zhí)行過的 PreparedStatement,其內(nèi)部通過 LRU 緩存完成,在 SQLiteConnection 中定義如下:
//緩存預(yù)編譯后的SQL語句,內(nèi)部采用LRU算法
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
還記得在創(chuàng)建 SQLiteDatabase 過程中執(zhí)行的相關(guān)配置項(xiàng),其中就包含了每個(gè) SQLiteConnection 緩存 PreparedStatement 的最大數(shù)量 maxSqlCacheSize,它默認(rèn)大小是 25。
看下 PreparedStatement 的創(chuàng)建過程:
private PreparedStatement acquirePreparedStatement(String sql) {
//根據(jù)SQL在緩存獲取PreparedStatement
//mPreparedStatementCache是個(gè)LRU
PreparedStatement statement = mPreparedStatementCache.get(sql);
boolean skipCache = false;
if (statement != null) {
if (!statement.mInUse) {
return statement;
}
// The statement is already in the cache but is in use (this statement appears
// to be not only re-entrant but recursive!). So prepare a new copy of the
// statement but do not cache it.
skipCache = true;
}
//根據(jù)SQLiteConnection和sql語句創(chuàng)建native層PreparedStatement
final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
try {
//獲取參數(shù)長(zhǎng)度
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
//操作類型
final int type = DatabaseUtils.getSqlStatementType(sql);
//是否只讀
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
//創(chuàng)建Java層PreparedStatement對(duì)象,內(nèi)部保存native層PreparedStatement匿名描述符
statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
if (!skipCache && isCacheable(type)) {
mPreparedStatementCache.put(sql, statement);
statement.mInCache = true;
}
} catch (RuntimeException ex) {
// Finalize the statement if an exception occurred and we did not add
// it to the cache. If it is already in the cache, then leave it there.
if (statement == null || !statement.mInCache) {
nativeFinalizeStatement(mConnectionPtr, statementPtr);
}
throw ex;
}
statement.mInUse = true;
return statement;
}
mPreparedStatementCache.get(sql) 優(yōu)先在緩存中進(jìn)行查找,每個(gè) Java 層 PreparedStatement 對(duì)象對(duì)應(yīng)一個(gè) native 層 SQLite 庫(kù)的 Statement。Statement 可以將它理解為一個(gè)輕量但只能向前遍歷,沒有緩存的 Cursor。
重新回到 SQLiteConnection 的 prepare 方法,將計(jì)算得到的相關(guān)信息保存在 SQLiteStatementInfo 對(duì)象中。
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
//獲取當(dāng)前查詢的所有列長(zhǎng)度
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
//如果獲取到列長(zhǎng)度為0,此時(shí)為空數(shù)組
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
//根據(jù)長(zhǎng)度創(chuàng)建String[]
outStatementInfo.columnNames = new String[columnCount];
for (int i = 0; i < columnCount; i++) {
//按照順序獲取所有的列名,保存在SQLiteStatementInfo的String數(shù)組中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
}
}
SQLiteStatementInfo 是在 SQLiteProgram 構(gòu)造方法傳遞的出參入?yún)ⅲㄉ厦嬉呀?jīng)貼出),內(nèi)部保存信息實(shí)際賦值給了 SQLiteProgram 的成員:
//調(diào)用SQLiteSession的prepare方法
//實(shí)際調(diào)用到SQLiteConnection的prepare方法中,將相關(guān)信息保存在
//info(SQLiteStatementInfo)中返回
db.getThreadSession().prepare(mSql,
db.getThreadDefaultConnectionFlags(assumeReadOnly),
cancellationSignalForPrepare, info);
//是否只讀
mReadOnly = info.readOnly;
//列名稱數(shù)組
mColumnNames = info.columnNames;
//參數(shù)長(zhǎng)度
mNumParameters = info.numParameters;
實(shí)際上在查詢過程中,SQLiteQuery 的創(chuàng)建工作主要是完成了對(duì)查詢結(jié)果的列名獲?。ò错樞颍?。
重新回到 SQLiteDirectCursorDirver 的 query 方法,SQLiteQuery 執(zhí)行過程分析完成,再看下 SQLiteCursor:
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
if (query == null) {
throw new IllegalArgumentException("query object cannot be null");
}
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
} else {
mStackTrace = null;
}
//SQLiteDirectCursorDriver
mDriver = driver;
mEditTable = editTable;
//保存當(dāng)前列名和索引(index)位置
mColumnNameMap = null;
//SQLiteQuery
mQuery = query;
//獲取查詢結(jié)果所有的列名
mColumns = query.getColumnNames();
}
這里我們重點(diǎn)看下 SQLiteQuery 的 getColumnNames 方法,實(shí)際調(diào)用到其父類 SQLiteProgram 中:
final String[] getColumnNames() {
return mColumnNames;
}
還記得在分析 SQLiteProgram 構(gòu)造方法中獲取當(dāng)前查詢的所有列名稱:
for (int i = 0; i < columnCount; i++) {
//按照順序獲取所有的列名,保存在SQLiteStatementInfo的String數(shù)組中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
在 SQLiteProgram 中根據(jù)當(dāng)前查詢數(shù)據(jù)列的長(zhǎng)度,按照順序獲取對(duì)應(yīng)列的名稱保存在 String 數(shù)組中。
Cursor 數(shù)據(jù)獲取過程
查詢返回 Curosr 對(duì)象后,此時(shí)我們可以通過 Cursor 完成數(shù)據(jù)的獲取過程,下面就分析下它是如何完成數(shù)據(jù)獲取的。
//從Cursor中獲取查詢數(shù)據(jù)
final String name = cursor.getString(cursor.getColumnIndex("name"));
DbLog.e(TAG, "name: " + name);
根據(jù)字段名稱 cursor.getColumnIndex 獲取該字段對(duì)應(yīng)的 index 位置:
@Override
public int getColumnIndex(String columnName) {
// Create mColumnNameMap on demand
if (mColumnNameMap == null) {
String[] columns = mColumns;
//遍歷查詢到的列名數(shù)組
int columnCount = columns.length;
HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
for (int i = 0; i < columnCount; i++) {
//存放如Map中
//實(shí)際這里計(jì)算Column的index就是根據(jù)列的順序
map.put(columns[i], i);
}
mColumnNameMap = map;
}
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
//返回要查詢列名的index
Integer i = mColumnNameMap.get(columnName);
if (i != null) {
return i.intValue();
} else {
return -1;
}
}
按照列名數(shù)組的先后順序?qū)⒘忻Q與對(duì)應(yīng)的 index 保存在 Map 容器中。此時(shí)通過該 Map 容器獲取某個(gè)列名對(duì)應(yīng)查詢數(shù)據(jù)的 index 位置,并調(diào)用 getString 方法獲取查詢結(jié)果:
/**
* 根據(jù)列名Index
*/
@Override
public String getString(int columnIndex) {
checkPosition();
//直接從CursorWindow中g(shù)et
return mWindow.getString(mPos, columnIndex);
}
可以看到此時(shí)直接通過 mWindow 變量進(jìn)行獲取,那 mWindow 是什么?又是在哪里創(chuàng)建的?實(shí)際上我們從 Cursor 中讀取數(shù)據(jù)之前一般會(huì)通過如下檢查:
//遍歷Cursor
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
//獲取查詢數(shù)據(jù)
}
}
無論是 getCount 或 moveToNext 方法都會(huì)去填充 mWindow 的數(shù)據(jù)。
- getCount 方法
//getCount方法
public int getCount() {
if (mCount == NO_COUNT) {
//默認(rèn)mCount==NO_COUNT
fillWindow(0);
}
return mCount;
}
- moveToNext 方法
//moveToNext方法
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
//調(diào)用moveToPosition方法
public final boolean moveToPosition(int position) {
// 確保位置不超過光標(biāo)的末端
final int count = getCount();
if (position >= count) {
//所有查詢數(shù)據(jù)已經(jīng)獲取完畢
mPos = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
}
// Check for no-op moves, and skip the rest of the work for them
if (position == mPos) {
//還是當(dāng)前位置
return true;
}
//onMove方法在子類SQLiteCursor中重寫
//主要用于判斷當(dāng)前光標(biāo)位置是否超出CursorWindow
boolean result = onMove(mPos, position);
if (result == false) {
mPos = -1;
} else {
mPos = position;
}
return result;
}
在子類 SQLiteCursor 中重寫了 onMove 方法:
//newPosition是新的要讀取的位置
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null
|| newPosition < mWindow.getStartPosition()
|| newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
//mWindow == null,表示初次執(zhí)行
//newPosition < mWindow.getStartPosition() 讀取游標(biāo)位置 < 起始位置
//newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) 讀取游標(biāo)位置 >= 游標(biāo)起始位置 + 總長(zhǎng)度
fillWindow(newPosition);
}
return true;
}
如果當(dāng)前游標(biāo)遍歷到緩沖區(qū)以外的行此時(shí)就會(huì)調(diào)用 fillWindow 重新查詢:
private void fillWindow(int requiredPos) {
//創(chuàng)建或清空當(dāng)前CursorWindow
clearOrCreateWindow(getDatabase().getPath());
try {
Preconditions.checkArgumentNonnegative(requiredPos,
"requiredPos cannot be negative, but was " + requiredPos);
if (mCount == NO_COUNT) {
//調(diào)用SQLiteQuery的fillWindow方法
mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
.cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
} catch (RuntimeException ex) {
// Close the cursor window if the query failed and therefore will
// not produce any results. This helps to avoid accidentally leaking
// the cursor window if the client does not correctly handle exceptions
// and fails to close the cursor.
closeWindow();
throw ex;
}
}
mWindow 實(shí)際是在 SQLiteCursor 父類中聲明:
//在SQLiteCursor的父類AbstractWindowedCursor聲明。
protected CursorWindow mWindow;
fillWindow 方法的作用主要就是填充數(shù)據(jù)到該 Cursor Window 緩沖區(qū)。CursorWindow 創(chuàng)建過程 :
protected void clearOrCreateWindow(String name) {
if (mWindow == null) {
//創(chuàng)建CursorWindow
mWindow = new CursorWindow(name);
} else {
mWindow.clear();
}
}
CursorWindow 構(gòu)造方法:
public CursorWindow(String name) {
//創(chuàng)建一個(gè)定長(zhǎng)的CursorWindow
this(name, getCursorWindowSize());
}
//調(diào)用該構(gòu)造方法
public CursorWindow(String name, @BytesLong long windowSizeBytes) {
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
//定長(zhǎng)內(nèi)存區(qū)域,native層CursorWindow對(duì)象
mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
if (mWindowPtr == 0) {
//如果創(chuàng)建失敗則直接拋出異常。
throw new CursorWindowAllocationException("Cursor window allocation of " +
windowSizeBytes + " bytes failed. " + printStats());
}
mCloseGuard.open("close");
recordNewWindow(Binder.getCallingPid(), mWindowPtr);
}
創(chuàng)建一塊定長(zhǎng)的 native 層緩沖區(qū),用于存放查詢結(jié)果,該大小一般是固定的 2MB 內(nèi)存空間,也被稱作 Cursor Window。
fillWindow 方法就是填充數(shù)據(jù)到該內(nèi)存區(qū)域,直到 Cursor Window 空間被放滿或遍歷完結(jié)果集。每當(dāng)執(zhí)行 moveToNext 會(huì)檢查當(dāng)前游標(biāo)是否遍歷到緩沖區(qū)以外的行。如果超過,此時(shí)會(huì)回調(diào) onMove 重新查詢。CursorWindow 會(huì)先丟棄之間的數(shù)據(jù),重新選定一個(gè)開始位置,直到緩沖區(qū)被再次填滿或遍歷完結(jié)果集。
CursorWindow 定長(zhǎng)內(nèi)存大小獲取過程:
//獲取系統(tǒng)系統(tǒng)配置,? * 1KB =
private static int getCursorWindowSize() {
if (sCursorWindowSize < 0) {
// The cursor window size. resource xml file specifies the value in kB.
// convert it to bytes here by multiplying with 1024.
sCursorWindowSize = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_cursorWindowSize) * 1024; // ? * 1KB
}
return sCursorWindowSize;
}
SQLiteCursor 中 fillWindow 方法實(shí)際委托到 SQLiteQuery 中:
int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
acquireReference();
try {
window.acquireReference();
try {
//getSession返回SQLiteSession,通過ThreadLocal保證線程私有
//執(zhí)行SQLiteSession的executeForCursorWindow
//實(shí)際執(zhí)行到了SQLiteConnection中
int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
window, startPos, requiredPos, countAllRows, getConnectionFlags(),
mCancellationSignal);
return numRows;
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} catch (SQLiteException ex) {
Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
throw ex;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
}
}
getSession 前面已經(jīng)說明過,此時(shí)調(diào)用 SQLiteSession 的 executeForCursorWindow 方法:
public int executeForCursorWindow(String sql, Object[] bindArgs,
CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
int connectionFlags, CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (window == null) {
throw new IllegalArgumentException("window must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
window.clear();
return 0;
}
//獲取一個(gè)數(shù)據(jù)庫(kù)連接
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForCursorWindow(sql, bindArgs,
window, startPos, requiredPos, countAllRows,
cancellationSignal); // might throw
} finally {
//釋放連接,其中就有空閑超時(shí)機(jī)制
releaseConnection(); // might throw
}
}
直接調(diào)用 SQLiteConnection 的 executeForCursorWindow 方法:
public int executeForCursorWindow(String sql, Object[] bindArgs,
CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (window == null) {
throw new IllegalArgumentException("window must not be null.");
}
window.acquireReference();
try {
int actualPos = -1;
int countedRows = -1;
int filledRows = -1;
final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
sql, bindArgs);
try {
//從復(fù)用池中獲取一個(gè)PreparedStatement
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
//調(diào)用native方法,CursorWindow的文件描述符傳遞
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 {
if (mRecentOperations.endOperationDeferLog(cookie)) {
mRecentOperations.logOperation(cookie, "window='" + window
+ "', startPos=" + startPos
+ ", actualPos=" + actualPos
+ ", filledRows=" + filledRows
+ ", countedRows=" + countedRows);
}
}
} finally {
window.releaseReference();
}
}
最終還是調(diào)用 native 函數(shù)完成 Cursor Window 緩沖區(qū)的數(shù)據(jù)填充。實(shí)際上這個(gè)過程是先將查詢數(shù)據(jù)取出保存到 Cursor Window ,然后再?gòu)?Cursor Window 在取出(SQLite -> CursorWindow -> Java),數(shù)據(jù)經(jīng)歷了兩次內(nèi)存拷貝和轉(zhuǎn)換。
Cursor 中提供的直接獲取數(shù)據(jù)過程,都是委托給 CursorWidow 完成:
/**
* 獲取字節(jié)數(shù)組
*/
@Override
public byte[] getBlob(int columnIndex) {
checkPosition();
return mWindow.getBlob(mPos, columnIndex);
}
/**
* 獲取String
*/
@Override
public String getString(int columnIndex) {
checkPosition();
//直接從CursorWindow中g(shù)et
return mWindow.getString(mPos, columnIndex);
}
小結(jié)
Android 框架查詢數(shù)據(jù)庫(kù)使用的是 Cursor 接口,調(diào)用 SQLiteDatabase.query(...) 會(huì)返回一個(gè) Cursor,它的實(shí)際類型是 SQLiteCursor,Cursor 只是定義獲取數(shù)據(jù)的接口規(guī)范。之后就可以使用該 Cursor 遍歷結(jié)果集了。
Cursor 的實(shí)現(xiàn)是分配一個(gè)固定 2MB 大小的緩沖區(qū),稱作 Cursor Window,用于存放查詢結(jié)果集。
查詢時(shí),先分配 Cursor Window,然后執(zhí)行 SQL 獲取結(jié)果集填充該區(qū)域,直到 Cursor Window 放滿或者遍歷完結(jié)果集。
如果 Cursor 遍歷到緩沖區(qū)以外的行,Cursor 會(huì)丟棄之前緩沖區(qū)的所有內(nèi)容,跳過已查詢過的行重新查詢,重新選定一個(gè)開始位置填充 Cursor Window 直到緩沖區(qū)再次填滿或遍歷完結(jié)果集。
ps:這種場(chǎng)景下,先將數(shù)據(jù)保存到 Cursor Window 后再取出,中間經(jīng)歷了兩次內(nèi)存拷貝和轉(zhuǎn)換,這是沒有必要的,另外由于 Cursor Window 是定長(zhǎng)的,對(duì)于小結(jié)果集需要無故分配 2MB 內(nèi)存,對(duì)于大結(jié)果集 2MB 不足以放下,遍歷到圖中還會(huì)引發(fā) Cursor 重新查詢,這個(gè)消耗就相當(dāng)大了。
至此 SQLiteDatabase 提供的查詢操作就分析完了,數(shù)據(jù)庫(kù)的查詢操作整個(gè)過程還是比較繁瑣的。此時(shí)再去分析數(shù)據(jù)庫(kù)插入、刪除、更新等操作會(huì)變得非常輕松。
插入、刪除、更新等其他操作
下面我們分別以插入、刪除、更新操作做下分析
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
//調(diào)用自己的
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
//調(diào)用內(nèi)部的insertWithOnConflict方法
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
//引用計(jì)數(shù)++
acquireReference();
try {
//拼接SQL主語
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && !initialValues.isEmpty())
? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
//拼接字段
for (String colName : initialValues.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = initialValues.get(colName);
}
sql.append(')');
//拼接value
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
//創(chuàng)建SQLiteStatement
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
//執(zhí)行其內(nèi)部的executeInsert
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
//引用計(jì)數(shù)--
releaseReference();
}
}
從源碼可以看出首先根據(jù)傳遞參數(shù)拼接出完整的 SQL 語句,接著創(chuàng)建 SQLiteStatement 對(duì)象,執(zhí)行 executeInsert 方法。
在 SQLiteDatabase 中除了查詢操作,插入、刪除和更新操作都是通過 SQLiteStatement 完成的,構(gòu)造方法如下
SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
super(db, sql, bindArgs, null);
}
SQLiteStatement 也是繼承自 SQLiteProgram,關(guān)于 SQLiteProgram 在前面已經(jīng)做過分析,這里不再贅述。
看下 SQLiteStatement 中提供的插入、刪除、更新相關(guān)方法:
//數(shù)據(jù)庫(kù)插入方法
public long executeInsert() {
//引用計(jì)數(shù)++
acquireReference();
try {
//這里getSession 返回 SQLiteSession
//這還是執(zhí)行的SQLiteConnection 的 executeForLastInsertedRowId
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
//引用計(jì)數(shù)--
releaseReference();
}
}
//數(shù)據(jù)庫(kù)更新/刪除方法
public int executeUpdateDelete() {
//引用計(jì)數(shù)++
acquireReference();
try {
//getSession() 返回 SQLiteSession
//最終執(zhí)行 SQLiteConnection 的 executeForChangedRowCount 方法
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
//引用計(jì)數(shù)--
releaseReference();
}
}
SQLiteDatabase 的更新和刪除方法都是 executeUpdateDelete 方法完成,與 executeInsert 相比幾乎沒有差別(API 名稱不同,完成的業(yè)務(wù)邏輯不同)。
方法 getSession 整個(gè)過程,在分析數(shù)據(jù)庫(kù)查詢時(shí)已經(jīng)做了詳細(xì)的介紹,這里不再跟入,直接看 SQLiteConnection 的 executeForLastInsertedRowId 方法:
public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
sql, bindArgs);
try {
//一樣的套路,獲取復(fù)用的PreparedStatement
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
//綁定參數(shù)
bindArguments(statement, bindArgs);
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
//執(zhí)行native方法
return nativeExecuteForLastInsertedRowId(
mConnectionPtr, statement.mStatementPtr);
} finally {
detachCancellationSignal(cancellationSignal);
}
} finally {
//回收該P(yáng)reparedStatement
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
mRecentOperations.endOperation(cookie);
}
}
源碼中大部分內(nèi)容在 query 階段都已經(jīng)做過分析,其最終核心還是調(diào)用了 native 層 nativeExecuteForLastInsertedRowId 方法完成數(shù)據(jù)庫(kù)插入任務(wù)。
static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz,
469 jlong connectionPtr, jlong statementPtr) {
//轉(zhuǎn)成對(duì)應(yīng)的SQLiteConnection
470 SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
//轉(zhuǎn)換成對(duì)應(yīng)的PreparedStatement
471 sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
472
473 int err = executeNonQuery(env, connection, statement);
474 return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
475 ? sqlite3_last_insert_rowid(connection->db) : -1;
476}
static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
/最終根據(jù)PreparedStatement調(diào)用到SQLite中
441 int err = sqlite3_step(statement);
442 if (err == SQLITE_ROW) {
//這里表示不能使用該api完成查詢操作
443 throw_sqlite3_exception(env,
444 "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
445 } else if (err != SQLITE_DONE) {
//發(fā)生異常,數(shù)據(jù)庫(kù)沒有正確操作完成
446 throw_sqlite3_exception(env, connection->db);
447 }
448 return err;
449}
Cursor 注意事項(xiàng)
1. Cursor 的關(guān)閉邏輯
通過前面的分析,我們得知 Cursor 內(nèi)部持有一個(gè)定長(zhǎng)的 native 內(nèi)存區(qū)域 CursorWindow。
看下 Cursor 的 close 方法(本文依據(jù) API Level 28):
//實(shí)際SQLiteCursor的close方法
public void close() {
//調(diào)用父類的close方法
super.close();
synchronized (this) {
mQuery.close();
mDriver.cursorClosed();
}
}
SQLiteCursor 的繼承關(guān)系關(guān)系:SQLiteCursor —> AbstractWindowedCursor -> AbstractCursor -> ... -> Cursor。
此時(shí)調(diào)用其父類 AbstractCursor 的 close 方法:
//調(diào)用其父類AbstractCursor的close
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
//CursorWindow的關(guān)閉過程主要在這里
onDeactivateOrClose();
}
//onDeactivateOrClose在SQLiteCursor的直接父類
//AbstractWindowedCursor中重寫
protected void onDeactivateOrClose() {
super.onDeactivateOrClose();
//調(diào)用CursorWindow的close
closeWindow();
}
//在這里調(diào)用CursorWindow的close方法
protected void closeWindow() {
if (mWindow != null) {
//調(diào)用Cursor的close方法
mWindow.close();
mWindow = null;
}
}
2. CursorWindow 的關(guān)閉操作
在 Cursor 的 close 方法中調(diào)用了 CursorWindow 的 close,想必大家肯定猜得到,此時(shí)就是要去釋放那塊定長(zhǎng)的 Cursor Window 緩沖區(qū)。CursorWindow 繼承自 SQLiteCloseable,關(guān)于 SQLiteCloseable 的作用,在文章最開始就已經(jīng)做了詳細(xì)的分析。
//調(diào)用CursorWindow的close,實(shí)際調(diào)用到SQLiteCloseable中close方法
public void close() {
releaseReference();
}
public void releaseReference() {
boolean refCountIsZero = false;
synchronized (this) {
//釋放對(duì)該對(duì)象的引用
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
//當(dāng)前不存在任何引用
//通知關(guān)閉
onAllReferencesReleased();
}
}
//在 CursorWindow 中重寫了onAllReferencesReleased
protected void onAllReferencesReleased() {
dispose();
}
//釋放CursorWindow持有的native內(nèi)存在這里
private void dispose() {
if (mCloseGuard != null) {
mCloseGuard.close();
}
if (mWindowPtr != 0) {
recordClosingOfWindow(mWindowPtr);
//通知釋放CursorWindow的native內(nèi)存
nativeDispose(mWindowPtr);
//將指向置為0
mWindowPtr = 0;
}
}
正如我們猜想,重點(diǎn)是要釋放 Cursor Window 分配的定長(zhǎng)內(nèi)存空間。
3. Cursor 兜底回收策略
上面我們通過主動(dòng)調(diào)用 Cursor 的 close 方法能夠有效地避免 CursorWindow 發(fā)生內(nèi)存泄漏,如果我們忘記調(diào)用,那 Cursor Window 就一定會(huì)發(fā)生內(nèi)存泄漏嗎?
SQLiteCursor 的 finalize 方法:
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
//...日志信息,省略
//調(diào)用close方法
close();
}
} finally {
super.finalize();
}
}
從這里可以看出,當(dāng)垃圾收集器開始回收 SQLiteCursor 對(duì)象時(shí),在其 finalize 方法中會(huì)調(diào)用 close 方法,從而避免 CursorWindow 的內(nèi)存泄漏。
關(guān)于 finalize 再多說幾句,每當(dāng) GC 要回收某個(gè)對(duì)象時(shí)首先會(huì)調(diào)用它的 finalize 方法,決定是否要調(diào)用取決于當(dāng)前對(duì)象是否重寫了該方法。finalize 方法只會(huì)被調(diào)用一次。
總結(jié)
相比插入、刪除、更新等常用數(shù)據(jù)庫(kù)操作,查詢要相對(duì)繁瑣很多,查詢時(shí)先分配 Curosr Window,然后執(zhí)行 SQL 獲取結(jié)果集填充之,直到緩沖區(qū)再次填滿或遍歷完結(jié)果集。這樣雖然能保證正常工作,但在很多時(shí)候卻不是最優(yōu)實(shí)現(xiàn)。當(dāng)查詢數(shù)據(jù)較小是的時(shí)候,可能不一定劃算,而且數(shù)據(jù)還要經(jīng)過兩次內(nèi)存拷貝。這都是沒有必要的,因?yàn)榇蠖鄶?shù)數(shù)據(jù)庫(kù)操作都是獲取 Cursor 直接遍歷獲取數(shù)據(jù)后關(guān)閉。那該如何優(yōu)化它呢?你可以參考微信開源的 WCDB。
WCDB 完全集成自己的數(shù)據(jù)庫(kù)源碼,從實(shí)現(xiàn)上看提供了 SQLiteDircetCusor 對(duì) native 的 Statement 簡(jiǎn)單做下封裝,直接通過 Statement 完成數(shù)據(jù)的獲取。這樣能夠很好的解決 Cursor Window 額外的內(nèi)存消耗,特別是結(jié)果集大于 2 MB 的場(chǎng)合。
//直接通過底層的Statement完成數(shù)據(jù)獲取
public long getLong(int column) {
return nativeGetLong(mPreparedStatement.getPtr(), column);
}
public double getDouble(int column) {
return nativeGetDouble(mPreparedStatement.getPtr(), column);
}
public String getString(int column) {
return nativeGetString(mPreparedStatement.getPtr(), column);
}
public byte[] getBlob(int column) {
return nativeGetBlob(mPreparedStatement.getPtr(), column);
}
以上便是個(gè)人在學(xué)習(xí) SQLiteDatabase 時(shí)的心得和體會(huì),文中分析如有不妥或更好的分析結(jié)果,還請(qǐng)大家指出!
文章如果對(duì)你有幫助,就請(qǐng)留個(gè)贊吧!