Android SQLite的 select 操作分析

就像 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<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;
    ......
}
在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 : "<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在此基础上再次优化,以应对结果集过大、跳跃式读取结果等问题。尚未分析

没有更多推荐了,返回首页