天天看點

android sqlite 源碼分析,Android資料庫源碼分析(2)-SQLiteDatabase的實作以及多線程行為...

本篇主要關注SQLiteDatabase的線程同步實作與架構實作。

1 SQLiteClosable的acquireReference與releaseReference方法

SQLiteClosable是SQLiteDatabase的父類,也同時是資料庫下其他幾個類的父類。其中實作了引用計數邏輯來控制資源釋放的時機。

private int mReferenceCount = 1;

public void acquireReference() {

synchronized(this) {

if (mReferenceCount <= 0) {

throw new IllegalStateException(

"attempt to re-open an already-closed object: " + this);

}

mReferenceCount++;

}

}

public void releaseReference() {

boolean refCountIsZero = false;

synchronized(this) {

refCountIsZero = --mReferenceCount == 0;

}

if (refCountIsZero) {

onAllReferencesReleased();

}

}

可以看到這裡用mReferenceCount簡單地實作了一個引用計數。而引用計數的初始值是1。SQLiteDatabase會在每次操作前調用一次acquireReference,而在結束後調用一次releaseReference。為了友善,下文中把這樣的被acquireReference和releaseReference包裹的過程稱為一次“操作”。

那麼如果這兩個方法保持成對調用的話,是不是就不可能觸發onAllReferenceReleased方法?事實上,SQLiteClosable還有一個方法close調用了releaseReference。由于鎖的存在,隻要不在其它“操作”中調用close,調用close之後mReferenceCount的值可以斷定是0。

到這裡為止,感覺上是可以用一個boolean值來标記引用狀态的。因為由于鎖的存在,隻要各個“操作”是序列進行的(沒有一個“操作”調用了另一個“操作”的情況),mReferenceCount隻可能是0和1。推測引用計數就是為了應付“操作”之間存在調用這種情況。這就像同一個線程裡的嵌套鎖需要進行計數一樣。

2 SQLiteDatabase的打開與關閉

2.1 關閉

上文中提到的onAllReferenceReleased是一個抽象方法。其在SQLiteDatabase中的實作為

@Override

protected void onAllReferencesReleased() {

dispose(false);

}

在finalize中同樣調用了dispose方法

protected void finalize() throws Throwable {

try {

dispose(true);

} finally {

super.finalize();

}

}

而dispose的實作為

private void dispose(boolean finalized) {

final SQLiteConnectionPool pool;

synchronized (mLock) {

if (mCloseGuardLocked != null) {

if (finalized) {

//CloseGuard是一個監測是否及時調用close方法的類,一般來說除了輸出日志并不會做别的什麼

//這裡事實上就是在finalize的時候如果沒有close過,就輸出一條日志

mCloseGuardLocked.warnIfOpen();

}

mCloseGuardLocked.close();

}

pool = mConnectionPoolLocked;//這個mConnectionPool是連接配接池。此方法裡将其置空并關閉。後文詳細讨論其作用。

mConnectionPoolLocked = null;

}

if (!finalized) {

//sActiveDatabases是一個靜态的WeakHashMap,用key來放置所有活動資料庫,而value并沒有作用。dispose的時候自然要移除this。

//跟蹤代碼分析下來,用這個map隻是為了bug report

synchronized (sActiveDatabases) {

sActiveDatabases.remove(this);

}

if (pool != null) {

pool.close();

}

}

}

2.2 打開

在本系列第一篇中我們曾看到過,最終的打開資料庫的是一個靜态方法,SQLiteDatabase.openDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler)。

public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,

DatabaseErrorHandler errorHandler) {

SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);

db.open();

return db;

}

這裡很簡單,就是建立一個對象,然後調用open。構造器裡隻有一些初始化,略過。着重看open方法:

private void open() {

try {

try {

openInner();//嘗試一次

} catch (SQLiteDatabaseCorruptException ex) {

onCorruption();//失敗了,再次嘗試前調用另一個方法。

openInner();

}

} catch (SQLiteException ex) {

Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);

close();

throw ex;

}

}

private void openInner() {

synchronized (mLock) {

assert mConnectionPoolLocked == null;

mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);

mCloseGuardLocked.open("close");

}

synchronized (sActiveDatabases) {//這是之前那個WeakHashMap

sActiveDatabases.put(this, null);

}

}

void onCorruption() {

EventLog.writeEvent(EVENT_DB_CORRUPT, getLabel());

mErrorHandler.onCorruption(this);

}

open中會嘗試調用openInner。如果失敗一次,則調用onCorruption,随後再嘗試一次。mErrorHandler是構造器傳入的,構造器參數由靜态方法openDatabase傳入,而這個參數又最終從SQLiteOpenHelper傳入。

openInner中做的事情,從命名上看,是開啟一個SQLiteConnectionPool即資料庫連接配接池。簡單地說,資料庫連接配接池維持了對資料庫的多個連接配接。資料庫連接配接的類是SQLiteConnection。

3 線程内單例的SQLiteSession

private final ThreadLocal mThreadSession = new ThreadLocal() {

@Override

protected SQLiteSession initialValue() {

return createSession();

}

};

SQLiteSession getThreadSession() {

return mThreadSession.get(); // initialValue() throws if database closed

}

SQLiteSession createSession() {

final SQLiteConnectionPool pool;

synchronized (mLock) {

throwIfNotOpenLocked();

pool = mConnectionPoolLocked;

}

return new SQLiteSession(pool);

}

ThreadLocal會在每個線程内維護一個對象,而線上程結束時解除對對象的引用。initialValue方法會線上程中不存在已有對象時建立一個,不Override的話會給出一個null。除此之外也可以通過ThreadLocal.set來給本線程配置一個對象。

可以看到mThreadSession是一個ThreadLocal。調用getThreadSession會擷取一個線程内單例的SQLiteSession對象。

SQLiteSession是提供資料庫操作能力(增删改查以及事務)的一個單元。它會從SQLiteConnectionPool即連接配接池中擷取連接配接,最終對資料庫進行操作。

到這兒類已經有點多了。整理一下邏輯:

(1)SQLiteDatabase持有一個ThreadLocal,用于對每個線程生成一個SQLiteSession;

(2)SQLiteSession持有SQLiteConnectionPool(雖然SQLiteDatabase也持有連接配接池對象,但它隻用來傳遞給SQLiteSession),但是同一個SQLiteDatabase下的SQLiteSession是共用一個SQLiteConnectionPool的;

(3)SQLiteConnectionPool管理SQLiteConnection并适時向SQLiteSession提供之;

(4)SQLiteConnection直接對底層資料庫進行操作(這個類裡面才有大量的native方法)。

接下來分析一下SQLiteSession。

擷取與釋放連接配接,還是一個引用計數實作:

private final SQLiteConnectionPool mConnectionPool;//構造器中初始化,值從SQLiteDatabase對象中傳入

private SQLiteConnection mConnection;

private int mConnectionFlags;

private int mConnectionUseCount;//無處不在的引用計數

private void acquireConnection(String sql, int connectionFlags,

CancellationSignal cancellationSignal) {

if (mConnection == null) {

assert mConnectionUseCount == 0;

mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,

cancellationSignal); // might throw

mConnectionFlags = connectionFlags;

}

mConnectionUseCount += 1;

}

private void releaseConnection() {

assert mConnection != null;

assert mConnectionUseCount > 0;

if (--mConnectionUseCount == 0) {

try {

mConnectionPool.releaseConnection(mConnection); // might throw

} finally {

mConnection = null;

}

}

}

具體的資料庫操作有很多executeXXX形式的方法,邏輯大同小異。挑一個看看:

public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,

CancellationSignal cancellationSignal) {

if (sql == null) {

throw new IllegalArgumentException("sql must not be null.");

}

if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {//排除特殊操作

return 0;

}

//擷取連接配接

acquireConnection(sql, connectionFlags, cancellationSignal); // might throw

try {

//底層資料庫操作。本文不關心。

return mConnection.executeForChangedRowCount(sql, bindArgs,

cancellationSignal); // might throw

} finally {

//釋放連接配接

releaseConnection(); // might throw

}

}

//用來支援'BEGIN','COMMIT','ROLLBACK'的操作。就是與Transaction相關的操作。

private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags,

CancellationSignal cancellationSignal) {

if (cancellationSignal != null) {

cancellationSignal.throwIfCanceled();

}

final int type = DatabaseUtils.getSqlStatementType(sql);

switch (type) {

case DatabaseUtils.STATEMENT_BEGIN:

beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags,

cancellationSignal);

return true;

case DatabaseUtils.STATEMENT_COMMIT:

setTransactionSuccessful();

endTransaction(cancellationSignal);

return true;

case DatabaseUtils.STATEMENT_ABORT:

endTransaction(cancellationSignal);

return true;

}

return false;

}

4 單次完整的SQLite操作

4.1 SQLiteStatement

以最簡單的delete方法為例。其它方法的流程均大同小異。

public int delete(String table, String whereClause, String[] whereArgs) {

acquireReference();

try {

SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +

(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);

try {

return statement.executeUpdateDelete();

} finally {

statement.close();

}

} finally {

releaseReference();

}

}

先用SQLiteStatement做一些sql轉義和拼接,然後調用statement.executeUpdateDelete()。

具體看一下executeUpdateDelete:

//以下來自SQLiteStatement

public int executeUpdateDelete() {

acquireReference();//注意這裡是SQLiteStatement内的引用計數,不是SQLiteDatabase了。

try {

return getSession().executeForChangedRowCount(

getSql(), getBindArgs(), getConnectionFlags(), null);//上一節分析過了,執行SQL。

} catch (SQLiteDatabaseCorruptException ex) {

onCorruption();

throw ex;

} finally {

releaseReference();

}

}

//這個方法在父類SQLiteProgram中。又回到了上一小節的getThreadSession。擷取線程内的單例。

protected final SQLiteSession getSession() {

return mDatabase.getThreadSession();

}

4.2 SQLiteDirectCursorDriver與SQLiteQuery

與4.1不同的是,在進行query操作時,最終沒有使用SQLiteStatement類,而是通過SQLiteDirectCursorDriver間接使用了SQLiteQuery。而SQLiteQuery與SQLiteStatement同為SQLiteProgram的子類,完成類似的功能。

所有的query操作最終均調用這樣一個方法:

public Cursor rawQueryWithFactory(

CursorFactory cursorFactory, String sql, String[] selectionArgs,

String editTable, CancellationSignal cancellationSignal) {

acquireReference();

try {

SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,

cancellationSignal);

return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,

selectionArgs);

} finally {

releaseReference();

}

}

而SQLiteDirectCursorDriver的query方法如下:

public Cursor query(CursorFactory factory, String[] selectionArgs) {

final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);

final Cursor cursor;

try {

query.bindAllArgsAsStrings(selectionArgs);

if (factory == null) {

cursor = new SQLiteCursor(this, mEditTable, query);

} else {

cursor = factory.newCursor(mDatabase, this, mEditTable, query);

}

} catch (RuntimeException ex) {

query.close();

throw ex;

}

mQuery = query;

return cursor;

}

其中建立了一個SQLiteQuery,并綁定參數。随後建立一個Cursor,這就是最終傳回的Cursor對象。接下來考察無CursorFactory情況下預設傳回的SQLiteCursor。

AbstractCursor中各種move方法均會調用moveToPosition,而moveToPosition會調用onMove,SQliteCursor中onMove的實作為:

@Override

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())) {

fillWindow(newPosition);

}

return true;

}

private void fillWindow(int requiredPos) {

clearOrCreateWindow(getDatabase().getPath());

try {

if (mCount == NO_COUNT) {

int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);

mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);

mCursorWindowCapacity = mWindow.getNumRows();

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "received count(*) from native_fill_window: " + mCount);

}

} else {

int startPos = 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;

}

}

核心邏輯在mQuery.fillWindow(mWindow, startPos, requiredPos, false);這裡。mQuery就是之前傳入的SQLiteQuery對象。檢視其fillWindow方法:

int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {

acquireReference();

try {

window.acquireReference();

try {

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

}

}

可以看到,最終回到了SQLiteSession.executeXXX方法邏輯之下。其餘即與上一節類似。

而從Cursor中取出資料的過程,則最終是由CursorWindow下的一系列native方法來完成,我認為屬于Cursor的代碼體系了,這裡不重點展開。

5 Transaction

5.1 beginTransaction

//一群差不多的beginTransaction方法最終調用到了這裡

private void beginTransaction(SQLiteTransactionListener transactionListener,

boolean exclusive) {

acquireReference();//怎麼老是你

try {

getThreadSession().beginTransaction(

exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :

SQLiteSession.TRANSACTION_MODE_IMMEDIATE,

transactionListener,

getThreadDefaultConnectionFlags(false ), null);

} finally {

releaseReference();

}

}

//上面的方法調用了這個方法。這套flags做了兩件小事:1.确定隻讀還是可寫 2.如果是主線程,就要提高連接配接的優先級

int getThreadDefaultConnectionFlags(boolean readOnly) {

int flags = readOnly ? SQLiteConnectionPool.CONNECTION_FLAG_READ_ONLY :

SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY;

if (isMainThread()) {

flags |= SQLiteConnectionPool.CONNECTION_FLAG_INTERACTIVE;

}

return flags;

}

還是要看SQLiteSession内部:

public void beginTransaction(int transactionMode,

SQLiteTransactionListener transactionListener, int connectionFlags,

CancellationSignal cancellationSignal) {

throwIfTransactionMarkedSuccessful();//一點合法性檢查,不貼了

beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,

cancellationSignal);

}

private void beginTransactionUnchecked(int transactionMode,

SQLiteTransactionListener transactionListener, int connectionFlags,

CancellationSignal cancellationSignal) {

if (cancellationSignal != null) {

//cancellationSignal從beginTransaction以及SQLiteStatement諸方法傳入的均為null,調查發現僅query時可以傳入此參數。

cancellationSignal.throwIfCanceled();

}

if (mTransactionStack == null) {//Transaction棧為空時才擷取連接配接。

acquireConnection(null, connectionFlags, cancellationSignal); // might throw

}

try {

// Set up the transaction such that we can back out safely

// in case we fail part way.

if (mTransactionStack == null) {//如果沒有進行中的Transaction,建立一個并BEGIN

// Execute SQL might throw a runtime exception.

switch (transactionMode) {

case TRANSACTION_MODE_IMMEDIATE:

mConnection.execute("BEGIN IMMEDIATE;", null,

cancellationSignal); // might throw

break;

case TRANSACTION_MODE_EXCLUSIVE:

mConnection.execute("BEGIN EXCLUSIVE;", null,

cancellationSignal); // might throw

break;

default:

mConnection.execute("BEGIN;", null, cancellationSignal); // might throw

break;

}

}

// Listener might throw a runtime exception.

if (transactionListener != null) {

try {

transactionListener.onBegin(); // might throw

} catch (RuntimeException ex) {

if (mTransactionStack == null) {

mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw

}

throw ex;

}

}

// Bookkeeping can't throw, except an OOM, which is just too bad...

Transaction transaction = obtainTransaction(transactionMode, transactionListener);//建立事務

transaction.mParent = mTransactionStack;

mTransactionStack = transaction;//入棧

} finally {

if (mTransactionStack == null) {//這裡要棧為空時才釋放連接配接。不為空時永遠持有一個連接配接。

releaseConnection(); // might throw

}

}

}

private static final class Transaction {

public Transaction mParent;//這個是個連結清單,或者說在這裡充當了一個棧

public int mMode;

public SQLiteTransactionListener mListener;

public boolean mMarkedSuccessful;

public boolean mChildFailed;

}

5.2 setTransactionSuccessful與endTransaction

直接看SQLiteSession吧:

public void setTransactionSuccessful() {

throwIfNoTransaction();

throwIfTransactionMarkedSuccessful();

mTransactionStack.mMarkedSuccessful = true;//僅僅是個标記

}

public void endTransaction(CancellationSignal cancellationSignal) {

throwIfNoTransaction();

assert mConnection != null;

endTransactionUnchecked(cancellationSignal, false);

}

private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {

if (cancellationSignal != null) {

cancellationSignal.throwIfCanceled();

}

final Transaction top = mTransactionStack;

boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;//如果有子Transaction失敗,也是失敗的

RuntimeException listenerException = null;

final SQLiteTransactionListener listener = top.mListener;

if (listener != null) {

try {

if (successful) {

listener.onCommit(); // might throw

} else {

listener.onRollback(); // might throw

}

} catch (RuntimeException ex) {

listenerException = ex;

successful = false;

}

}

mTransactionStack = top.mParent;//退棧

recycleTransaction(top);//回收

if (mTransactionStack != null) {//還沒到最外層事務,隻做個标記

if (!successful) {

mTransactionStack.mChildFailed = true;

}

} else {//到了最外層事務了,送出或復原

try {

if (successful) {

mConnection.execute("COMMIT;", null, cancellationSignal); // might throw

} else {

mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw

}

} finally {

releaseConnection(); // might throw

}

}

if (listenerException != null) {

throw listenerException;

}

}

6 總結

(1)總的來說,SQLiteDatabase是線程安全且高效的。它并沒有簡單地對每次操作加鎖,而是使用引用計數和ThreadLocal來保證連接配接複用的線程安全性,資料一緻性則交由SQLite自身去保證,以達到最優性能。

而很多時候我們在業務層封裝時反而處處加鎖,其實是沒有必要的。

(2)SQLiteDatabase的内部實作會讓每個線程單獨持有一個資料庫連接配接(不一定是建立,因為有連接配接池優化),而不是每個SQLiteDatabase對象對應一個連接配接。

(3)資料庫會給主線程持有的連接配接提高優先級。如果執行的是讀操作或者小量資料的寫入操作的話,可能可以滿足主線程低延遲的需要。但是還沒有具體的資料來支撐這一結論,希望有大牛補充。

(4)多線程下的事務行為本文中未作分析,下一篇會就此問題單獨進行讨論。