[原始碼]Sqlite是怎麼通過CursorWindow讀DB的

weixin_34293059發表於2016-03-30

更多內容在這裡檢視
https://ahangchen.gitbooks.io/windy-afternoon/content/

執行QUERY

執行SQLiteDatabase類中query系列函式時,只會構造查詢資訊,不會執行查詢。

(query的原始碼追蹤路徑)

執行MOVE(裡面的FILLWINDOW是真正開啟檔案控制程式碼並分配記憶體的地方)

當執行Cursor的move系列函式時,第一次執行,會為查詢結果集建立一塊共享記憶體,即cursorwindow

moveToPosition原始碼路徑

FILLWINDOW----真正耗時的地方

然後會執行sql語句,向共享記憶體中填入資料,

fillWindow原始碼路徑

在SQLiteCursor.java中可以看到

@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;
}

如果請求查詢的位置在cursorWindow的範圍內,不會執行fillWindow,

而超出cursorwindow的範圍,會呼叫fillWindow,

而在nativeExecuteForCursorWindow中,

獲取記錄時,如果要請求的位置超出視窗範圍,會發生CursorWindow的清空:

CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);  
if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {  
// We filled the window before we got to the one row that we really wanted. 
// Clear the window and start filling it again from here.  
// TODO: Would be nicer if we could progressively replace earlier rows.  
window->clear();  
window->setNumColumns(numColumns);  
startPos += addedRows;  
addedRows = 0;  
cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);  
}

CursorWindow的清空機制會影響到多執行緒讀(通常認為不可以併發讀寫,sqlite的併發實際上是序列執行的,但可以併發讀,這裡要強調的是多執行緒讀也可能有問題),具體見稍後一篇文章“listview併發讀寫資料庫”。

上面說的這些直觀的感受是什麼樣的呢?大概是這樣,

執行query,讀10000條資料,很快就拿到了cursor,這裡不會卡,

執行moveToFirst,卡一下(fillwindow(0))

moveToPosition(7500),卡一下,因為已經超了cursorwindow的區域,又去fillwindow(7500),

關於fillwindow還有一些奇特的細節,比如4.0以後,fillwindow會填充position前後各一段資料,防止讀舊資料的時候又需要fill,感興趣的同學可以看看各個版本fillwidow的原始碼。

這裡還可以延伸一下,因為高版本的android sqlite對舊版有許多改進,

所以實際開發裡我們有時候會把sqlite的原始碼帶在自己的工程裡,使得低版本的android也可以使用高版本的特性,並且避開一部分相容性問題。

CURSOR關閉(顯式呼叫CLOSE()的理由)

追蹤原始碼看關閉

 //SQLiteCursor

super.close();
synchronized (this) {
    mQuery.close();
    mDriver.cursorClosed();
}


//AbstractCursor

public void close() {
    mClosed = true;
    mContentObservable.unregisterAll();
    onDeactivateOrClose();
}

protected void onDeactivateOrClose() {
    if (mSelfObserver != null) {
        mContentResolver.unregisterContentObserver(mSelfObserver);
        mSelfObserverRegistered = false;
    }
    mDataSetObservable.notifyInvalidated();
}


//AbstractWindowedCursor

/** @hide */
@Override
protected void onDeactivateOrClose() {
    super.onDeactivateOrClose();
    closeWindow();
}

protected void closeWindow() {
    if (mWindow != null) {
        mWindow.close();
        mWindow = null;
    }
}

 

//SQLiteClosable

public void close() {
    releaseReference();
}

public void releaseReference() {
    boolean refCountIsZero = false;
    synchronized(this) {
        refCountIsZero = --mReferenceCount == 0;
    }
    if (refCountIsZero) {
        onAllReferencesReleased();
    }
}

//CursorWindow

@Override
protected void onAllReferencesReleased() {
    dispose();
}

private void dispose() {
    if (mCloseGuard != null) {
        mCloseGuard.close();
    }
    if (mWindowPtr != 0) {
        recordClosingOfWindow(mWindowPtr);
        nativeDispose(mWindowPtr);
        mWindowPtr = 0;
    }
}

跟CursorWindow有關的路徑裡,最終呼叫nativeDispose()清空cursorWindow;

當Cursor被GC回收時,會呼叫finalize:

@Override
protected void finalize() {
    try {
        // if the cursor hasn't been closed yet, close it first
        if (mWindow != null) {
            if (mStackTrace != null) {
                String sql = mQuery.getSql();
                int len = sql.length();
                StrictMode.onSqliteObjectLeaked(
                    "Finalizing a Cursor that has not been deactivated or closed. " +
                    "database = " + mQuery.getDatabase().getLabel() +
                    ", table = " + mEditTable +
                    ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
                    mStackTrace);
            }
            close();
        }
    } finally {
        super.finalize();
    }
}

然而finalize()並沒有釋放CursorWindow,而super.finalize();裡也只是解綁了觀察者,沒有去釋放cursorwindow

所以不呼叫cursor.close(),最終會導致cursorWindow所在的共享記憶體(1M或2M)洩露。

相關文章