Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android SQLite的 select 操作分析

Android SQLite的 select 操作分析

編輯:關於Android編程

就像 Android SQLiteStatement 編譯、執行 分析 中所說的,SQLite中所有SQL語句都需要先編譯為stmt,然後執行。
上述文章介紹了SQLiteStatement在android層面的編譯執行。然而,類SQLiteStatement只能用以執行無返回值或者只有一行一列(1X1)的sql語句,例如INSERT ALERT 等,而像SELECT這種返回結果可能多行多列的則不適用。
android對select提供了專門的執行方法rawQuery(),對其也有特殊的SQLiteQuery類,以及相關的Cursor類。這篇文章我們可以看到,其實SQLiteQuery與SQLiteStatement本質是相同的,android針對select的特殊性做了特殊的執行流程。

1、使用query的方式

SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor cr;
cr = db.rawQuery("select * from person where age=20", null);
if (cr.moveToFirst()) {
    for (int i = 0; i < cr.getCount(); i++) {
        cr.getString();
        cr.moveToNext();
    }
}

2、query的操作

//SQLiteDatabase.java
    public Cursor rawQuery(String sql, String[] selectionArgs) {
        return rawQueryWithFactory(null, sql, selectionArgs, null, null);
    }
    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();
        }
    }

這裡出現了兩步操作,構建一個driver,通過driver執行。那麼 SQLiteDirectCursorDriver 是什麼呢?

// SQLiteDirectCursorDriver.java
public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
    private final SQLiteDatabase mDatabase;
    private final String mEditTable; 
    private final String mSql;
    private final CancellationSignal mCancellationSignal;
    private SQLiteQuery mQuery;

    public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
            CancellationSignal cancellationSignal) { 
        mDatabase = db;
        mEditTable = editTable;
        mSql = sql;
        mCancellationSignal = cancellationSignal;
    }

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

可以看到,driver的一個陌生成員變量為SQLiteQuery,並且它在構造函數中並未出現,在執行driver.query時才出現並被賦值③。接著又構造了我們熟悉的cursor並將其返回④。

首先看下SQLiteQuery:

// SQLiteQuery.java
public final class SQLiteQuery extends SQLiteProgram {
    private static final String TAG = "SQLiteQuery";
    private final CancellationSignal mCancellationSignal;
    SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {
        super(db, query, null, cancellationSignal);
        mCancellationSignal = cancellationSignal;
    }
正如文章開頭所說,SQLiteQuery繼承自SQLiteProgram,和SQLiteStatement相同。由(Android SQLiteStatement 編譯、執行 分析)可以知道,在其構造函數中,經歷了sql語句的prepare過程,在某個連接池的某個connection中已經含有了相應的stmt。
在④中可以看到,如果factory == null,即沒有自定義的cursor工廠類(我們一般不會自定義的),會直接構造一個SQLiteCursor。具體看下SQLiteCursor類。
// SQLiteCursor.java
public class SQLiteCursor extends AbstractWindowedCursor {
    static final String TAG = "SQLiteCursor";
    static final int NO_COUNT = -1;
    private final String mEditTable;
    private final String[] mColumns;
    private final SQLiteQuery mQuery;
    private final SQLiteCursorDriver mDriver;
    private int mCount = NO_COUNT;
    private int mCursorWindowCapacity;
    private Map mColumnNameMap;
    private final Throwable mStackTrace;

    public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
        ……
        mDriver = driver;
        mEditTable = editTable;
        mColumnNameMap = null;
        mQuery = query;
        mColumns = query.getColumnNames();
        mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
    }

可以看到,SQLiteCursor維護的也是一些元信息,但其繼承自 AbstractWindowedCursor,後者又繼承自AbstractCursor。

// AbstractWindowedCursor.java
public abstract class AbstractWindowedCursor extends AbstractCursor {
    protected CursorWindow mWindow;
}
// AbstractCursor.java
public abstract class AbstractCursor implements CrossProcessCursor {
    protected int mPos;
    ......
}
在AbstractWindowedCursor中,我們看到了CursorWindow,在數據庫中cursor window是很重要的概念。

// CursorWindow.java
public class CursorWindow extends SQLiteClosable implements Parcelable {
    public int mWindowPtr;  // !!!
    private int mStartPos;
    private final String mName;
    private final CloseGuard mCloseGuard = CloseGuard.get();

    private static native int nativeCreate(String name, int cursorWindowSize);
    private static native void nativeClear(int windowPtr);
    private static native int nativeGetNumRows(int windowPtr);
    private static native double nativeGetDouble(int windowPtr, int row, int column);
    ……
}
mWindowPtr 目測是指向native層sqlite相應window的指針。並且該類含有不少native方法,部分對sqlite中window的操作,應該是通過這個類實現的。
通過繼承,SQLiteCursor有了指向cursor window的能力,但是在構造函數中並未體現,並且driver.query時,直接將new處的cursor返回了。此時,尚未通過native實際執行過select語句。

3、Cursor的操作

//AbstractCursor.java

    public final boolean moveToFirst() {
        return moveToPosition(0);
    }
    public final boolean moveToNext() {
        return moveToPosition(mPos + 1);
    }

    public final boolean moveToPosition(int position) {
        final int count = getCount(); // ⑤
        if (position >= count) {
            mPos = count;
            return false;
        }
        if (position < 0) {
            mPos = -1;
            return false;
        }
        if (position == mPos) {
            return true;
        }
        boolean result = onMove(mPos, position); /// ⑨
        if (result == false) {
            mPos = -1;
        } else {
            mPos = position;
            if (mRowIdColumnIndex != -1) {
                mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
            }
        }

        return result;
    }

// SQLiteCursor.java
    @Override
    public int getCount() {
        if (mCount == NO_COUNT) {
            fillWindow(0); 
        }
        return mCount;
    }

    @Override
    public boolean onMove(int oldPosition, int newPosition) {
        if (mWindow == null || newPosition < mWindow.getStartPosition() ||
                newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
            fillWindow(newPosition);
        }
        return true;
    }

到這裡可以看到,第一次moveToPosition時,因為此時mCount為-1,fillWindow(0)。
// SQLiteCursor.java
    private void fillWindow(int requiredPos) {
        clearOrCreateWindow(getDatabase().getPath());  // ⑥

        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);
        }
    }
    protected void clearOrCreateWindow(String name) {
        if (mWindow == null) { // 建立CursorWindow
            mWindow = new CursorWindow(name);
        } else {
            mWindow.clear();
        }
    }
在第⑥中,new出CursorWindow,將其賦值給mWindow,此時,由SQLiteCursor掌管。如下,new CursorWindow的過程,調用了nativeCreate,並使mWindowPtr指向native層的window。

// CursorWindow.java
    public CursorWindow(String name) {
        mStartPos = 0;
        mName = name != null && name.length() != 0 ? name : "";
        mWindowPtr = nativeCreate(mName, sCursorWindowSize); // !!!
        if (mWindowPtr == 0) {
            throw new CursorWindowAllocationException("Cursor window allocation of " +
                    (sCursorWindowSize / 1024) + " kb failed. " + printStats());
        }
        mCloseGuard.open("close");
        recordNewWindow(Binder.getCallingPid(), mWindowPtr);
    }

先看第⑧中,fillWindow()

// SQLiteQuery.java
    int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
         ....
         int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
                        window, startPos, requiredPos, countAllRows, getConnectionFlags(),
                        mCancellationSignal);
         return numRows;
     }
// SQLiteSeesion.java
    public int executeForCursorWindow(String sql, Object[] bindArgs,
            CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
            int connectionFlags, CancellationSignal cancellationSignal) {
        acquireConnection(sql, connectionFlags, cancellationSignal); 
        try {
            return mConnection.executeForCursorWindow(sql, bindArgs,
                    window, startPos, requiredPos, countAllRows,
                    cancellationSignal); 
        } finally {
            releaseConnection();
        }
    }
// SQLiteConnection.java
    public int executeForCursorWindow(String sql, Object[] bindArgs,
        CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
        CancellationSignal cancellationSignal) {
        final PreparedStatement statement = acquirePreparedStatement(sql);
        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;
        .....
    }

可以看到,最終仍是通過SQLiteConnection連接到native來執行。

剩下的getString就比較簡單了,一直會調用到到mWindow的getString

    public String getString(int row, int column) {
        acquireReference();
        try {
            return nativeGetString(mWindowPtr, row - mStartPos, column);
        } finally {
            releaseReference();
        }
    }


最後看下第⑦,即window fill 的控制。

這裡有涉及fill策略,一般無需考慮。如果結果集大於window怎麼辦?如果所需某個元素不在window中怎麼辦?尚未詳細分析了,貼下代碼。

若是第一次fill,required row 為0,即從第一條記錄開始fill滿window。

window將會包含所需的row及其周圍的一些row。例如,想要結果集的第120個元素,window大小為90,則將結果集第90-180的元素填充至window,120之前30個,之後60個。 如果window中沒有,將其放置在window的第10個位置。

// DatabaseUtils.java
    public static int cursorPickFillWindowStartPosition(
            int cursorPosition, int cursorWindowCapacity) {
        return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
    }

static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,
        jint connectionPtr, jint statementPtr, jint windowPtr,
        jint startPos, jint requiredPos, jboolean countAllRows) {
    ......

    int retryCount = 0;
    int totalRows = 0;
    int addedRows = 0;
    bool windowFull = false;
    bool gotException = false;
    while (!gotException && (!windowFull || countAllRows)) {
        int err = sqlite3_step(statement);
        if (err == SQLITE_ROW) {
            LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
            retryCount = 0;
            totalRows += 1;

            // Skip the row if the window is full or we haven't reached the start position yet.
            if (startPos >= totalRows || windowFull) {
                continue;
            }

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

            if (cpr == CPR_OK) {
                addedRows += 1;
            } else if (cpr == CPR_FULL) {
                windowFull = true;
            } else {
                gotException = true;
            }
        } else if (err == SQLITE_DONE) {
            // All rows processed, bail
            LOG_WINDOW("Processed all rows");
            break;
        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
            // The table is locked, retry
            LOG_WINDOW("Database locked, retrying");
            if (retryCount > 50) {
                ALOGE("Bailing on database busy retry");
                throw_sqlite3_exception(env, connection->db, "retrycount exceeded");
                gotException = true;
            } else {
                // Sleep to give the thread holding the lock a chance to finish
                usleep(1000);
                retryCount++;
            }
        } else {
            throw_sqlite3_exception(env, connection->db);
            gotException = true;
        }
    }

    LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
            "to the window in %d bytes",
            statement, totalRows, addedRows, window->size() - window->freeSpace());
    sqlite3_reset(statement);

    // Report the total number of rows on request.
    if (startPos > totalRows) {
        ALOGE("startPos %d > actual rows %d", startPos, totalRows);
    }
    jlong result = jlong(startPos) << 32 | jlong(totalRows);
    return result;
}

4、總結

① query的執行同普通sql語句相同,都需經過sql語句的編譯及執行。

② 編譯後為SQLiteQuery,執行後返回SQLiteCursor,SQLiteCursor的mWindow指向native層的cursor window。

③ 通過SQLiteCursor對返回結果進行控制。

④ 執行的過程,是構建SQLiteCursor的過程,並未將結果集寫入相應window。

⑤ 結果集寫入window,發生在第一次類似cursor.moveToFirst()操作中。這是android中處處體現的惰性策略

⑥ sqlite本身對結果集與window的關系做了優化,android在此基礎上再次優化,以應對結果集過大、跳躍式讀取結果等問題。尚未分析

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved