上述文章介紹了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;
- }
在④中可以看到,如果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<String, Integer> 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;
- ......
- }
- // 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);
- ……
- }
通過繼承,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();
- }
- }
- // CursorWindow.java
- public CursorWindow(String name) {
- mStartPos = 0;
- mName = name != null && name.length() != 0 ? name : "<unnamed>";
- 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在此基礎上再次優化,以應對結果集過大、跳躍式讀取結果等問題。尚未分析。