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(); } }
//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(); } }
// 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; }
首先看下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。
// 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); }
// 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的操作,应该是通过这个类实现的。
//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; }
// 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 : "<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); }
// 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; ..... }
剩下的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; }
① query的执行同普通sql语句相同,都需经过sql语句的编译及执行。
② 编译后为SQLiteQuery,执行后返回SQLiteCursor,SQLiteCursor的mWindow指向native层的cursor window。
③ 通过SQLiteCursor对返回结果进行控制。
④ 执行的过程,是构建SQLiteCursor的过程,并未将结果集写入相应window。
⑤ 结果集写入window,发生在第一次类似cursor.moveToFirst()操作中。这是android中处处体现的惰性策略。
⑥ sqlite本身对结果集与window的关系做了优化,android在此基础上再次优化,以应对结果集过大、跳跃式读取结果等问题。尚未分析。