Android 存儲優(yōu)化系列專題
- SharedPreferences 系列
《Android 之不要濫用 SharedPreferences》
《Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失》
- ContentProvider 系列(待更)
《Android 存儲選項之 ContentProvider 啟動過程源碼分析》
《Android 存儲選項之 ContentProvider 深入分析》
- 對象序列化系列
《Android 對象序列化之你不知道的 Serializable》
《Android 對象序列化之 Parcelable 深入分析》
《Android 對象序列化之追求完美的 Serial》
- 數(shù)據(jù)序列化系列(待更)
《Android 數(shù)據(jù)序列化之 JSON》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 使用》
《Android 數(shù)據(jù)序列化之 Protocol Buffer 源碼分析》
- SQLite 存儲系列
《Android 存儲選項之 SQLiteDatabase 創(chuàng)建過程源碼分析》
《Android 存儲選項之 SQLiteDatabase 源碼分析》
《數(shù)據(jù)庫連接池 SQLiteConnectionPool 源碼分析》
《SQLiteDatabase 啟用事務(wù)源碼分析》
《SQLite 數(shù)據(jù)庫 WAL 模式工作原理簡介》
《SQLite 數(shù)據(jù)庫鎖機制與事務(wù)簡介》
《SQLite 數(shù)據(jù)庫優(yōu)化那些事兒》
在上一篇《Android 存儲選項之 SQLiteDatabase 創(chuàng)建過程源碼分析》一文户秤,為大家分析了 Android 平臺提供的數(shù)據(jù)庫操作對象 SQLiteDatabase 創(chuàng)建過程复颈,以及關(guān)于 SQLiteDatabase嘹承、SQLiteConnectionPool单匣、SQLiteConnection 三者之間的緊密關(guān)系瑰妄。
如果說 SQLiteOpenHelper 可能是我們在 Android 平臺使用 SQLite 數(shù)據(jù)庫接觸到的第一個類,那 SQLiteDatabase 可能就是那個最關(guān)鍵的類了错览,實際上它內(nèi)部的絕大多數(shù)函數(shù)也是圍繞數(shù)據(jù)庫操作進行封裝的插入、刪除澳盐、更新、查詢和關(guān)閉等核心任務(wù)令宿。該篇文章也是圍繞這些內(nèi)容對 SQLiteDatabase 源碼做進一步的分析叼耙。
SQLiteDatabase 介紹
SQLiteDatabase 對象在概念上比較容易理解,與 SQLite C API 的底層數(shù)據(jù)庫對象相似粒没,但是實際情況要復雜得多筛婉。
SQLiteDatabase 類中包含 50 多個方法,每個方法都有自己的用途癞松,而且每個方法相對獨立于其它方法倾贰,它們大部分都是為完成一個簡單的數(shù)據(jù)庫任務(wù),比如表的選擇拦惋、插入、更新安寺、刪除語句厕妖。這里只是列舉了一些重要的方法介紹。詳細你可以參照這里挑庶。
1. 使用 SQLiteDatabase 類執(zhí)行普通查詢操作
如同有很多創(chuàng)建 SQL 語句的方法言秸,SQLiteDatabase 也提供了大量的方法在 SQLite 數(shù)據(jù)庫中運行 SQL 語句软能。事實上 Android 提供了多達 16 種方法在 SQLite 數(shù)據(jù)庫中運行一般或特殊的查詢。
這些函數(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é)為三個核心函數(shù):execSQL、query 和 rawQuery叛买,它們各自的使用場景相信大家也一定分的出砂代。
2. 增加、刪除率挣、修改等操作
上面是一系列數(shù)據(jù)庫查詢操作刻伊,另外對數(shù)據(jù)庫的插入、更新椒功、刪除操作方法如下:
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ù)庫操作 API 進行貼出捶箱,不過即使通過方法名我們也很容易理解每個方法的作用。這些方法看上去很吸引人动漾,其實它們的設(shè)計主要也是為了面向?qū)ο蟮某绦騿T設(shè)計的丁屎。
3. compileStatement 函數(shù)
SQLiteDatabase 中還有一個方法比較重要,它就是 compileStatement:
publi SQLiteStatement compileStatement(String sql)
將一個 SQL 語句作為參數(shù)谦炬,返回一個 SQLiteStatement 對象悦屏,事實上,正如函數(shù)名表示的键思,這個函數(shù)將編譯 SQL 語句并允許將其重用础爬。關(guān)于它的使用將在后面一篇文章《Android 存儲選項之 SQLiteDatabase 優(yōu)化那些事兒》進行介紹。
4. SQLiteDatabase 類的事務(wù)管理
Android 很看重 SQLite 事務(wù)管理吼鳞。在 SQLiteDatabase 中很多方法用來啟動看蚜、結(jié)束和管理事務(wù)的。
void beginTransaction()
void beginTransactionWithListener(SQLiteTransactionListener transactionListener)
void endTransaction()
boolean inTransacetion()
void setTransactionSuccessful()
這些方法不用太多解釋赔桌。beginTransaction() 啟動 SQLite 事務(wù)供炎,endTransaction() 結(jié)束當前的事務(wù)。重要的是疾党,決定事務(wù)是否被提交或者回滾取決于事務(wù)是否被標注了 “clean”音诫。setTrancactionSuccessful() 函數(shù)用來設(shè)置這個標志位,這個額外的步驟剛開始讓人反感雪位,但事實上它保證了在事務(wù)提交前對所做更改的檢查竭钝。如果不存在事務(wù)或者事務(wù)已經(jīng)是成功狀態(tài),那么 setTransactionSuccessful() 函數(shù)就會拋出 IllegalStateException 異常。
isTransaction() 函數(shù)測試是否存在活動事務(wù)香罐,如果有卧波,則返回 true。
beginTransactionWithListener 函數(shù)接收 SQLiteTransaction-Listener 對象庇茫,SQLiteTransactionListener 負責監(jiān)聽與事務(wù)相關(guān)的一切操作港粱,比如提交、回滾或者嵌套地開始一個新事物旦签。
關(guān)于 SQLiteDatabase 事務(wù)源碼分析可以參考后續(xù)文章《SQLiteDatabase 啟用事務(wù)源碼分析》
5. SQLiteDatabase 中其它函數(shù)
SQLiteDatabase 中還涉及到一些其它方法:
public long getMaximumSize() 返回數(shù)據(jù)庫允許的最大值
public int getVersion() 返回應用創(chuàng)建數(shù)據(jù)庫的版本號
public boolean isDbLockedByCurrentThread() 測試當前線程是否鎖住了數(shù)據(jù)庫查坪。
public boolean isDbLockedByOtherThreads() 測試是否有其他線程鎖住了數(shù)據(jù)庫。
public static int releaseMemory() 釋放書庫不再需要的資源顷霹,比如內(nèi)存等咪惠。返回釋放資源的字節(jié)數(shù)。
經(jīng)過上面的一系列介紹淋淀,相信你對 SQLiteDatabase 的工作內(nèi)容有了進一步認識遥昧,接下來我們通過源碼的角度具體分析下數(shù)據(jù)庫的相關(guān)操作。
SQLiteCloseable
在分析 SQLiteDatabase 源碼之前我想先來說下 SQLiteCloseable 類朵纷。因為 SQLiteDatabase 中會出現(xiàn)大量該邏輯的操作炭臭,這里先對其進行說明。
其實關(guān)于 SQLiteCloseable 完全可以理解成 Java 為 I/O 提供的 Closeable 標準接口袍辞,SQLiteCloseable 就是專門為數(shù)據(jù)庫釋放提供的標準 API(SQLiteCloseable 也實現(xiàn)了 Closeable)鞋仍。
在 android/database 包下類似 SQLiteDatabase、CursorWindow搅吁、SQLiteQuery 等都實現(xiàn)了該接口威创。
通過源碼分析它的工作過程,申請操作:
public void acquireReference() {
synchronized (this) {
if (mReferenceCount <= 0) {
//表示數(shù)據(jù)庫已經(jīng)關(guān)閉
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + this);
}
//引用計數(shù)++
mReferenceCount++;
}
}
對應釋放操作:
public void releaseReference() {
boolean refCountIsZero = false;
synchronized (this) {
//釋放對該對象的引用
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
//當前不存在任何引用
//通知關(guān)閉
onAllReferencesReleased();
}
}
從源碼我們可以看出谎懦,其內(nèi)部通過引用計數(shù)的方式確定當前對象是否還被外部引用肚豺。
當引用計數(shù) mReferenceCount == 0 時,此時會調(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();
這其實也比較好理解吸申,當涉及到具體釋放過程時就需要具體實現(xiàn)類自行實現(xiàn)該過程。
以 SQLiteDatabase 中的實現(xiàn)為例:
//在SQLiteDatabase中重寫SQLiteCloseable中的onAllReferencesReleased
protected void onAllReferencesReleased() {
dispose(false);
}
看下引用計數(shù)在具體實現(xiàn)類 SQLiteDatabase 中的工作過程享甸,我們以查詢?yōu)槔?/p>
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
//引用計數(shù)++
acquireReference();
try {
//... 省略
} finally {
//釋放
releaseReference();
}
}
每次對 SQLiteDatabase 的訪問操作截碴,首先會通過 acquireReference 使引用計數(shù)++,訪問結(jié)束通過 releaseReference 引用計數(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();
}
//當前SQLiteDatabase對象的連接池
pool = mConnectionPoolLocked;
mConnectionPoolLocked = null;
}
if (!finalized) {
synchronized (sActiveDatabases) {
//移除在WeakHashMap中
sActiveDatabases.remove(this);
}
if (pool != null) {
//關(guān)閉連接池
pool.close();
}
}
}
關(guān)于 dispose 它接受一個 boolean 類型的字段蚯嫌,表示當前是主動(close)關(guān)閉還是被動(finalize 回調(diào))關(guān)閉聚凹。在 SQLiteDatabase割坠、 SQLiteConnectionPool、SQLiteConnection 等都提供了該套路妒牙,以 SQLiteDatabase 的 finalize 為例:
@Override
protected void finalize() throws Throwable {
try {
dispose(true);
} finally {
super.finalize();
}
}
這也是 finalize 典型使用場景,雖然 finalize 方法 的調(diào)用時機不確定对妄,但是我們可以利用該方法實現(xiàn)某個對象的兜底回收策略湘今,減小或減少發(fā)生內(nèi)存泄漏的影響或可能。
在 SQLiteDatabase dispose 方法中剪菱,我們重點關(guān)注下數(shù)據(jù)庫連接池的關(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)閉的連接池將會拋出異常。
throwIfClosedLocked();
//標志是否已經(jīng)關(guān)閉
mIsOpen = false;
//關(guān)閉所有數(shù)據(jù)庫連接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 方法中包含一個方法名非常長的 closeAvailableConnectionsAndLogExceptionsLocked 方法如下:
private void closeAvailableConnectionsAndLogExceptionsLocked() {
//關(guān)閉所有連接池中緩存的非主連接
closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
if (mAvailablePrimaryConnection != null) {
//關(guān)閉主連接
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
mAvailablePrimaryConnection = null;
}
}
在數(shù)據(jù)庫連接池 SQLiteConnectionPool 中通過一個 List 對象緩存所有非數(shù)據(jù)庫主連接孝常,關(guān)閉該些連接方法如下:
private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
//當前SQLiteDatabase對象的所有非主連連接都被mAvailableNonPrimaryConnections緩存
final int count = mAvailableNonPrimaryConnections.size();
for (int i = 0; i < count; i++) {
//關(guān)閉連接池中每個非主連接 SQLiteConnection
closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
}
//清空連接池緩存
mAvailableNonPrimaryConnections.clear();
}
直接遍歷該集合關(guān)閉所有連接(非主連接)并清空緩存旗们。注意數(shù)據(jù)庫主連接并沒有被緩存在該 List 對象中,如上也是直接關(guān)閉它构灸。
private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
try {
//直接關(guān)閉該連接SQLiteConnection
connection.close(); // might throw
if (mIdleConnectionHandler != null) {
//移除延時關(guān)閉連接機制
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)閉對應native層SQLiteConnection
nativeClose(mConnectionPtr);
//將對native持有句柄置為0
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
}
}
}
每個 Java 層 SQLiteConnection 都會對應一個 native 層 SQLiteConnection稠氮,此時調(diào)用 nativeClose 方法關(guān)閉對應的 native 對象,并將指向的匿名內(nèi)存描述符 mConnectionPtr 置為 0:
static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
//轉(zhuǎn)換成native層的SQLiteConnection對象
179 SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
180
181 if (connection) {
182 ALOGV("Closing connection %p", connection->db);
//關(guān)閉該數(shù)據(jù)庫操作句柄
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ù)庫相關(guān)核心類的申請和釋放操作隔披,然而它只是 SQLiteDatabase 操作涉及到的其中一個細節(jié),這樣的細節(jié)功能在 SQLiteDatabae 中還非常的多寂拆。感興趣的朋友可以更深入源碼進行分析奢米。
查詢操作
相比增加、刪除和更新等操作纠永,數(shù)據(jù)庫的查詢可能是最復雜的一個過程鬓长,下面就我們就先從 SQLiteDatabase 的查詢操作開始分析,然后再去分析其它的數(shù)據(jù)庫操作就會變得非常輕松渺蒿。示例如下:
public String callInBackground(@NonNull SQLiteDatabase db) {
db.beginTransactionNonExclusive();
Cursor cursor = null;
try {
//這里直接使用SQL語句進行查詢
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 中所有的查詢操作最終都會從 rawQueryWithFactory 方法開始:
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
//引用計數(shù) ++
acquireReference();
try {
//創(chuàng)建SQLiteCursorDriver對象
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
cancellationSignal);
//使用SQLiteDirectCursorDriver完成查詢
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
//釋放引用計數(shù) --
releaseReference();
}
}
這里直接創(chuàng)建 SQLiteDirectCursorDriver,然后調(diào)用它的 query 方法:
public Cursor query(CursorFactory factory, String[] selectionArgs) {
//查詢使用的是SQLiteQuery
//實際SQLiteCursor內(nèi)部使用SQLiteQuery開始查詢數(shù)據(jù)
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs);
if (factory == null) {
//默認返回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ì)只是一個接口怠蹂,約束了對查詢數(shù)據(jù)獲取的接口規(guī)則,實際查詢返回的 Cursor 類型是 SQLiteCursor少态〕遣啵可以看到創(chuàng)建 SQLiteQuery 對象作為參數(shù)傳入 SQLiteCurosr。先看下 SQLiteQuery 的創(chuàng)建過程:
SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {
super(db, query, null, cancellationSignal);
mCancellationSignal = cancellationSignal;
}
SQLiteQuery 實際僅包含一個非常重要的方法 fillWindow,在遍歷 Cursor 時會分析到它;SQLiteQuery 大部分邏輯操作都在其父類 SQLiteProgram 中:
SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
CancellationSignal cancellationSignalForPrepare) {
mDatabase = db;
mSql = sql.trim();
//這里根據(jù)SQL語句判斷當前的操作類型
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:
//查詢會走這里
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ù)賦值給當前成員
mReadOnly = info.readOnly;
mColumnNames = info.columnNames;
mNumParameters = info.numParameters;
break;
}
// ... 省略
}
根據(jù) getSqlStatementType 判斷當前操作類型乾颁,這里直接告訴大家:內(nèi)部根據(jù) SQL 語句前綴判斷其操作類型金矛,查詢語句返回的是 STATEMENT_SELECT刃唐,此時會走 default 流程简烤。
然后創(chuàng)建 SQLiteStatementInfo 對象熟吏,實際它內(nèi)部僅包含三個成員:
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 起到一個出參入?yún)⒈4鏀?shù)據(jù)的角色拐揭,后面會分析到炮温;接著看 SQLiteDatabase 的 getThreadSession 方法火脉,它返回一個 SQLiteSession 對象:
SQLiteSession getThreadSession() {
//從當前線程的ThreadLocal中嘗試獲取
return mThreadSession.get(); // initialValue() throws if database closed
}
在上篇《Android 存儲選項之 SQLiteDatabase 的創(chuàng)建過程源碼分析》中有給大家提到,SQLite 數(shù)據(jù)庫操作的同一個句柄同一時間只有一個線程在操作柒啤。 Android 中每個 Java 層 SQLiteConnection 對應一個 native 層的 SQLiteConnection倦挂,每個 SQLiteConnection 中會包含一個 SQLite 數(shù)據(jù)庫操作句柄。
那它是如何保證多線程句柄操作的呢担巩?實際就是通過 ThreadLocal(線程局部變量)方援,看下它在 SQLiteDatabase 中的定義:
private final ThreadLocal<SQLiteSession> mThreadSession = ThreadLocal
.withInitial(this::createSession);
//如果獲取不到則會調(diào)用這里,為當前線程創(chuàng)建
//一個新的SQLiteSession
SQLiteSession createSession() {
final SQLiteConnectionPool pool;
synchronized (mLock) {
throwIfNotOpenLocked();
pool = mConnectionPoolLocked;
}
return new SQLiteSession(pool);
}
ThreadLocal 能夠保證線程私有涛癌,簡單來說犯戏,ThreadLocal 為每個使用該變量的線程提供私有的變量副本,所以每個線程都可以獨立地改變自己的部分祖很,而不會影響到其它線程所對應的副本笛丙。也就是數(shù)據(jù)庫操作的每個線程都會擁有自己獨立的 SQLiteSession,構(gòu)造方法如下:
public SQLiteSession(SQLiteConnectionPool connectionPool) {
if (connectionPool == null) {
throw new IllegalArgumentException("connectionPool must not be null");
}
//持有當前SQLiteDatabase的數(shù)據(jù)庫連接池
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ù)庫連接池中獲取一個有效的連接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ù)庫連接池獲取一個有效的數(shù)據(jù)庫連接
private void acquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
//從數(shù)據(jù)庫連接池中獲取一個有效的連接
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // might throw
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
由于獲取數(shù)據(jù)庫連接的內(nèi)容相對比較多假颇,主要涉及到 SQLiteConnectionPool 中的工作胚鸯,你可以參考《Android 數(shù)據(jù)庫之 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 {
//獲取一個PreparedStatement對象
final PreparedStatement statement = acquirePreparedStatement(sql);
try {
//outStatementInfo為在SQLiteProgram中傳入的
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
//獲取當前查詢的所有列長度
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
//如果獲取到列長度為0,此時為空數(shù)組
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
//根據(jù)長度創(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 方法嘗試獲取一個復用的 PreparedStatement 對象哥桥,PreparedStatement 表示預編譯后的 SQL 語句對象,SQL 語句被預編譯并存儲在 PreparedStatement 對象中激涤,然后可以使用此對象多次高效地執(zhí)行該語句拟糕。看下 PreparedStatement 的聲明:
private static final class PreparedStatement {
// Next item in pool.
//鏈表結(jié)構(gòu)
public PreparedStatement mPoolNext;
//對應執(zhí)行的SQL語句
public String mSql;
//對應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;
}
每個數(shù)據(jù)庫連接池都會緩存執(zhí)行過的 PreparedStatement倦踢,其內(nèi)部通過 LRU 緩存完成送滞,在 SQLiteConnection 中定義如下:
//緩存預編譯后的SQL語句,內(nèi)部采用LRU算法
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
還記得在創(chuàng)建 SQLiteDatabase 過程中執(zhí)行的相關(guān)配置項辱挥,其中就包含了每個 SQLiteConnection 緩存 PreparedStatement 的最大數(shù)量 maxSqlCacheSize犁嗅,它默認大小是 25。
看下 PreparedStatement 的創(chuàng)建過程:
private PreparedStatement acquirePreparedStatement(String sql) {
//根據(jù)SQL在緩存獲取PreparedStatement
//mPreparedStatementCache是個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ù)長度
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
//操作類型
final int type = DatabaseUtils.getSqlStatementType(sql);
//是否只讀
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
//創(chuàng)建Java層PreparedStatement對象晤碘,內(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)先在緩存中進行查找褂微,每個 Java 層 PreparedStatement 對象對應一個 native 層 SQLite 庫的 Statement功蜓。Statement 可以將它理解為一個輕量但只能向前遍歷,沒有緩存的 Cursor宠蚂。
重新回到 SQLiteConnection 的 prepare 方法式撼,將計算得到的相關(guān)信息保存在 SQLiteStatementInfo 對象中。
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
outStatementInfo.readOnly = statement.mReadOnly;
//獲取當前查詢的所有列長度
final int columnCount = nativeGetColumnCount(
mConnectionPtr, statement.mStatementPtr);
if (columnCount == 0) {
//如果獲取到列長度為0肥矢,此時為空數(shù)組
outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
} else {
//根據(jù)長度創(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)部保存信息實際賦值給了 SQLiteProgram 的成員:
//調(diào)用SQLiteSession的prepare方法
//實際調(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ù)長度
mNumParameters = info.numParameters;
實際上在查詢過程中,SQLiteQuery 的創(chuàng)建工作主要是完成了對查詢結(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;
//保存當前列名和索引(index)位置
mColumnNameMap = null;
//SQLiteQuery
mQuery = query;
//獲取查詢結(jié)果所有的列名
mColumns = query.getColumnNames();
}
這里我們重點看下 SQLiteQuery 的 getColumnNames 方法忘嫉,實際調(diào)用到其父類 SQLiteProgram 中:
final String[] getColumnNames() {
return mColumnNames;
}
還記得在分析 SQLiteProgram 構(gòu)造方法中獲取當前查詢的所有列名稱:
for (int i = 0; i < columnCount; i++) {
//按照順序獲取所有的列名,保存在SQLiteStatementInfo的String數(shù)組中
outStatementInfo.columnNames[i] = nativeGetColumnName(
mConnectionPtr, statement.mStatementPtr, i);
}
在 SQLiteProgram 中根據(jù)當前查詢數(shù)據(jù)列的長度案腺,按照順序獲取對應列的名稱保存在 String 數(shù)組中庆冕。
Cursor 數(shù)據(jù)獲取過程
查詢返回 Curosr 對象后,此時我們可以通過 Cursor 完成數(shù)據(jù)的獲取過程劈榨,下面就分析下它是如何完成數(shù)據(jù)獲取的访递。
//從Cursor中獲取查詢數(shù)據(jù)
final String name = cursor.getString(cursor.getColumnIndex("name"));
DbLog.e(TAG, "name: " + name);
根據(jù)字段名稱 cursor.getColumnIndex 獲取該字段對應的 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中
//實際這里計算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與對應的 index 保存在 Map 容器中。此時通過該 Map 容器獲取某個列名對應查詢數(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);
}
可以看到此時直接通過 mWindow 變量進行獲取拷姿,那 mWindow 是什么?又是在哪里創(chuàng)建的旱函?實際上我們從 Cursor 中讀取數(shù)據(jù)之前一般會通過如下檢查:
//遍歷Cursor
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
//獲取查詢數(shù)據(jù)
}
}
無論是 getCount 或 moveToNext 方法都會去填充 mWindow 的數(shù)據(jù)响巢。
- getCount 方法
//getCount方法
public int getCount() {
if (mCount == NO_COUNT) {
//默認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) {
// 確保位置不超過光標的末端
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) {
//還是當前位置
return true;
}
//onMove方法在子類SQLiteCursor中重寫
//主要用于判斷當前光標位置是否超出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() 讀取游標位置 < 起始位置
//newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) 讀取游標位置 >= 游標起始位置 + 總長度
fillWindow(newPosition);
}
return true;
}
如果當前游標遍歷到緩沖區(qū)以外的行此時就會調(diào)用 fillWindow 重新查詢:
private void fillWindow(int requiredPos) {
//創(chuà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 實際是在 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)建一個定長的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>";
//定長內(nèi)存區(qū)域,native層CursorWindow對象
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)建一塊定長的 native 層緩沖區(qū)伏穆,用于存放查詢結(jié)果,該大小一般是固定的 2MB 內(nèi)存空間颅眶,也被稱作 Cursor Window蜈出。
fillWindow 方法就是填充數(shù)據(jù)到該內(nèi)存區(qū)域,直到 Cursor Window 空間被放滿或遍歷完結(jié)果集涛酗。每當執(zhí)行 moveToNext 會檢查當前游標是否遍歷到緩沖區(qū)以外的行铡原。如果超過偷厦,此時會回調(diào) onMove 重新查詢。CursorWindow 會先丟棄之間的數(shù)據(jù)燕刻,重新選定一個開始位置只泼,直到緩沖區(qū)被再次填滿或遍歷完結(jié)果集。
CursorWindow 定長內(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 方法實際委托到 SQLiteQuery 中:
int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
acquireReference();
try {
window.acquireReference();
try {
//getSession返回SQLiteSession,通過ThreadLocal保證線程私有
//執(zhí)行SQLiteSession的executeForCursorWindow
//實際執(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)說明過过蹂,此時調(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;
}
//獲取一個數(shù)據(jù)庫連接
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForCursorWindow(sql, bindArgs,
window, startPos, requiredPos, countAllRows,
cancellationSignal); // might throw
} finally {
//釋放連接十绑,其中就有空閑超時機制
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 {
//從復用池中獲取一個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ù)據(jù)取出保存到 Cursor Window 本橙,然后再從 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ù)庫使用的是 Cursor 接口,調(diào)用 SQLiteDatabase.query(...) 會返回一個 Cursor击胜,它的實際類型是 SQLiteCursor亏狰,Cursor 只是定義獲取數(shù)據(jù)的接口規(guī)范。之后就可以使用該 Cursor 遍歷結(jié)果集了偶摔。
Cursor 的實現(xiàn)是分配一個固定 2MB 大小的緩沖區(qū)暇唾,稱作 Cursor Window,用于存放查詢結(jié)果集啰挪。
查詢時信不,先分配 Cursor Window,然后執(zhí)行 SQL 獲取結(jié)果集填充該區(qū)域亡呵,直到 Cursor Window 放滿或者遍歷完結(jié)果集抽活。
如果 Cursor 遍歷到緩沖區(qū)以外的行,Cursor 會丟棄之前緩沖區(qū)的所有內(nèi)容锰什,跳過已查詢過的行重新查詢下硕,重新選定一個開始位置填充 Cursor Window 直到緩沖區(qū)再次填滿或遍歷完結(jié)果集。
ps:這種場景下汁胆,先將數(shù)據(jù)保存到 Cursor Window 后再取出梭姓,中間經(jīng)歷了兩次內(nèi)存拷貝和轉(zhuǎn)換,這是沒有必要的嫩码,另外由于 Cursor Window 是定長的誉尖,對于小結(jié)果集需要無故分配 2MB 內(nèi)存,對于大結(jié)果集 2MB 不足以放下铸题,遍歷到圖中還會引發(fā) Cursor 重新查詢铡恕,這個消耗就相當大了琢感。
至此 SQLiteDatabase 提供的查詢操作就分析完了,數(shù)據(jù)庫的查詢操作整個過程還是比較繁瑣的探熔。此時再去分析數(shù)據(jù)庫插入驹针、刪除、更新等操作會變得非常輕松诀艰。
插入柬甥、刪除、更新等其他操作
下面我們分別以插入其垄、刪除苛蒲、更新操作做下分析
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) {
//引用計數(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 {
//引用計數(shù)--
releaseReference();
}
}
從源碼可以看出首先根據(jù)傳遞參數(shù)拼接出完整的 SQL 語句,接著創(chuàng)建 SQLiteStatement 對象绿满,執(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ù)庫插入方法
public long executeInsert() {
//引用計數(shù)++
acquireReference();
try {
//這里getSession 返回 SQLiteSession
//這還是執(zhí)行的SQLiteConnection 的 executeForLastInsertedRowId
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
//引用計數(shù)--
releaseReference();
}
}
//數(shù)據(jù)庫更新/刪除方法
public int executeUpdateDelete() {
//引用計數(shù)++
acquireReference();
try {
//getSession() 返回 SQLiteSession
//最終執(zhí)行 SQLiteConnection 的 executeForChangedRowCount 方法
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
//引用計數(shù)--
releaseReference();
}
}
SQLiteDatabase 的更新和刪除方法都是 executeUpdateDelete 方法完成,與 executeInsert 相比幾乎沒有差別(API 名稱不同忱辅,完成的業(yè)務(wù)邏輯不同)七蜘。
方法 getSession 整個過程,在分析數(shù)據(jù)庫查詢時已經(jīng)做了詳細的介紹墙懂,這里不再跟入橡卤,直接看 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 {
//一樣的套路,獲取復用的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 {
//回收該PreparedStatement
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ù)庫插入任務(wù)碧库。
static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz,
469 jlong connectionPtr, jlong statementPtr) {
//轉(zhuǎn)成對應的SQLiteConnection
470 SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
//轉(zhuǎn)換成對應的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ù)庫沒有正確操作完成
446 throw_sqlite3_exception(env, connection->db);
447 }
448 return err;
449}
Cursor 注意事項
1. Cursor 的關(guān)閉邏輯
通過前面的分析巧勤,我們得知 Cursor 內(nèi)部持有一個定長的 native 內(nèi)存區(qū)域 CursorWindow嵌灰。
看下 Cursor 的 close 方法(本文依據(jù) API Level 28):
//實際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。
此時調(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颅悉,想必大家肯定猜得到沽瞭,此時就是要去釋放那塊定長的 Cursor Window 緩沖區(qū)。CursorWindow 繼承自 SQLiteCloseable剩瓶,關(guān)于 SQLiteCloseable 的作用驹溃,在文章最開始就已經(jīng)做了詳細的分析城丧。
//調(diào)用CursorWindow的close,實際調(diào)用到SQLiteCloseable中close方法
public void close() {
releaseReference();
}
public void releaseReference() {
boolean refCountIsZero = false;
synchronized (this) {
//釋放對該對象的引用
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
//當前不存在任何引用
//通知關(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;
}
}
正如我們猜想吠架,重點是要釋放 Cursor Window 分配的定長內(nèi)存空間芙贫。
3. Cursor 兜底回收策略
上面我們通過主動調(diào)用 Cursor 的 close 方法能夠有效地避免 CursorWindow 發(fā)生內(nèi)存泄漏,如果我們忘記調(diào)用傍药,那 Cursor Window 就一定會發(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();
}
}
從這里可以看出拐辽,當垃圾收集器開始回收 SQLiteCursor 對象時拣挪,在其 finalize 方法中會調(diào)用 close 方法,從而避免 CursorWindow 的內(nèi)存泄漏俱诸。
關(guān)于 finalize 再多說幾句菠劝,每當 GC 要回收某個對象時首先會調(diào)用它的 finalize 方法,決定是否要調(diào)用取決于當前對象是否重寫了該方法睁搭。finalize 方法只會被調(diào)用一次赶诊。
總結(jié)
相比插入、刪除园骆、更新等常用數(shù)據(jù)庫操作舔痪,查詢要相對繁瑣很多,查詢時先分配 Curosr Window锌唾,然后執(zhí)行 SQL 獲取結(jié)果集填充之锄码,直到緩沖區(qū)再次填滿或遍歷完結(jié)果集。這樣雖然能保證正常工作晌涕,但在很多時候卻不是最優(yōu)實現(xiàn)滋捶。當查詢數(shù)據(jù)較小是的時候,可能不一定劃算余黎,而且數(shù)據(jù)還要經(jīng)過兩次內(nèi)存拷貝重窟。這都是沒有必要的,因為大多數(shù)數(shù)據(jù)庫操作都是獲取 Cursor 直接遍歷獲取數(shù)據(jù)后關(guān)閉驯耻。那該如何優(yōu)化它呢亲族?你可以參考微信開源的 WCDB。
WCDB 完全集成自己的數(shù)據(jù)庫源碼可缚,從實現(xiàn)上看提供了 SQLiteDircetCusor 對 native 的 Statement 簡單做下封裝霎迫,直接通過 Statement 完成數(shù)據(jù)的獲取。這樣能夠很好的解決 Cursor Window 額外的內(nèi)存消耗帘靡,特別是結(jié)果集大于 2 MB 的場合知给。
//直接通過底層的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);
}
以上便是個人在學習 SQLiteDatabase 時的心得和體會,文中分析如有不妥或更好的分析結(jié)果,還請大家指出涩赢!
文章如果對你有幫助戈次,就請留個贊吧!