上述文章介绍了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在此基础上再次优化,以应对结果集过大、跳跃式读取结果等问题。尚未分析。