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