Android中的sqlite Cursor操作詳解

 2408人閱讀 評論(0) 收藏 舉報
就像 Android SQLiteStatement 編譯、執行 分析 中所說的,SQLite中所有SQL語句都需要先編譯爲stmt,然後執行。
上述文章介紹了SQLiteStatement在android層面的編譯執行。然而,類SQLiteStatement只能用以執行無返回值或者只有一行一列(1X1)的sql語句,例如INSERT ALERT 等,而像SELECT這種返回結果可能多行多列的則不適用。
android對select提供了專門的執行方法rawQuery(),對其也有特殊的SQLiteQuery類,以及相關的Cursor類。這篇文章我們可以看到,其實SQLiteQuery與SQLiteStatement本質是相同的,android針對select的特殊性做了特殊的執行流程。

1、使用query的方式

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. SQLiteDatabase db = mOpenHelper.getWritableDatabase();  
  2. Cursor cr;  
  3. cr = db.rawQuery("select * from person where age=20"null);  
  4. if (cr.moveToFirst()) {  
  5.     for (int i = 0; i < cr.getCount(); i++) {  
  6.         cr.getString();  
  7.         cr.moveToNext();  
  8.     }  
  9. }  

2、query的操作

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. //SQLiteDatabase.java  
  2.     public Cursor rawQuery(String sql, String[] selectionArgs) {  
  3.         return rawQueryWithFactory(null, sql, selectionArgs, nullnull);  
  4.     }  
  5.     public Cursor rawQueryWithFactory(  
  6.             CursorFactory cursorFactory, String sql, String[] selectionArgs,  
  7.             String editTable, CancellationSignal cancellationSignal) {  
  8.         acquireReference();  
  9.         try {  
  10.             SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,  
  11.                     cancellationSignal);  // ①  
  12.             return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,  
  13.                     selectionArgs);       // ②  
  14.         } finally {  
  15.             releaseReference();  
  16.         }  
  17.     }  

這裏出現了兩步操作,構建一個driver,通過driver執行。那麼 SQLiteDirectCursorDriver 是什麼呢?

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // SQLiteDirectCursorDriver.java  
  2. public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {  
  3.     private final SQLiteDatabase mDatabase;  
  4.     private final String mEditTable;   
  5.     private final String mSql;  
  6.     private final CancellationSignal mCancellationSignal;  
  7.     private SQLiteQuery mQuery;  
  8.   
  9.     public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,  
  10.             CancellationSignal cancellationSignal) {   
  11.         mDatabase = db;  
  12.         mEditTable = editTable;  
  13.         mSql = sql;  
  14.         mCancellationSignal = cancellationSignal;  
  15.     }  
  16.   
  17.     public Cursor query(CursorFactory factory, String[] selectionArgs) {  
  18.         final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal); //③  
  19.         final Cursor cursor;  
  20.         try {  
  21.             query.bindAllArgsAsStrings(selectionArgs);  
  22.             if (factory == null) {   
  23.                 cursor = new SQLiteCursor(this, mEditTable, query); // ④  
  24.             } else {  
  25.                 cursor = factory.newCursor(mDatabase, this, mEditTable, query);  
  26.             }  
  27.         } catch (RuntimeException ex) {  
  28.             query.close();  
  29.             throw ex;  
  30.         }  
  31.   
  32.         mQuery = query;    
  33.         return cursor;  
  34.     }  

可以看到,driver的一個陌生成員變量爲SQLiteQuery,並且它在構造函數中並未出現,在執行driver.query時纔出現並被賦值③。接着又構造了我們熟悉的cursor並將其返回④。

首先看下SQLiteQuery:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // SQLiteQuery.java  
  2. public final class SQLiteQuery extends SQLiteProgram {  
  3.     private static final String TAG = "SQLiteQuery";  
  4.     private final CancellationSignal mCancellationSignal;  
  5.     SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {  
  6.         super(db, query, null, cancellationSignal);  
  7.         mCancellationSignal = cancellationSignal;  
  8.     }  
正如文章開頭所說,SQLiteQuery繼承自SQLiteProgram,和SQLiteStatement相同。由(Android SQLiteStatement 編譯、執行 分析)可以知道,在其構造函數中,經歷了sql語句的prepare過程,在某個連接池的某個connection中已經含有了相應的stmt。
在④中可以看到,如果factory == null,即沒有自定義的cursor工廠類(我們一般不會自定義的),會直接構造一個SQLiteCursor。具體看下SQLiteCursor類。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // SQLiteCursor.java  
  2. public class SQLiteCursor extends AbstractWindowedCursor {  
  3.     static final String TAG = "SQLiteCursor";  
  4.     static final int NO_COUNT = -1;  
  5.     private final String mEditTable;  
  6.     private final String[] mColumns;  
  7.     private final SQLiteQuery mQuery;  
  8.     private final SQLiteCursorDriver mDriver;  
  9.     private int mCount = NO_COUNT;  
  10.     private int mCursorWindowCapacity;  
  11.     private Map<String, Integer> mColumnNameMap;  
  12.     private final Throwable mStackTrace;  
  13.   
  14.     public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {  
  15.         ……  
  16.         mDriver = driver;  
  17.         mEditTable = editTable;  
  18.         mColumnNameMap = null;  
  19.         mQuery = query;  
  20.         mColumns = query.getColumnNames();  
  21.         mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);  
  22.     }  

可以看到,SQLiteCursor維護的也是一些元信息,但其繼承自 AbstractWindowedCursor,後者又繼承自AbstractCursor。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // AbstractWindowedCursor.java  
  2. public abstract class AbstractWindowedCursor extends AbstractCursor {  
  3.     protected CursorWindow mWindow;  
  4. }  
  5. // AbstractCursor.java  
  6. public abstract class AbstractCursor implements CrossProcessCursor {  
  7.     protected int mPos;  
  8.     ......  
  9. }  
在AbstractWindowedCursor中,我們看到了CursorWindow,在數據庫中cursor window是很重要的概念。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // CursorWindow.java  
  2. public class CursorWindow extends SQLiteClosable implements Parcelable {  
  3.     public int mWindowPtr;  // !!!  
  4.     private int mStartPos;  
  5.     private final String mName;  
  6.     private final CloseGuard mCloseGuard = CloseGuard.get();  
  7.   
  8.     private static native int nativeCreate(String name, int cursorWindowSize);  
  9.     private static native void nativeClear(int windowPtr);  
  10.     private static native int nativeGetNumRows(int windowPtr);  
  11.     private static native double nativeGetDouble(int windowPtr, int row, int column);  
  12.     ……  
  13. }  
mWindowPtr 目測是指向native層sqlite相應window的指針。並且該類含有不少native方法,部分對sqlite中window的操作,應該是通過這個類實現的。
通過繼承,SQLiteCursor有了指向cursor window的能力,但是在構造函數中並未體現,並且driver.query時,直接將new處的cursor返回了。此時,尚未通過native實際執行過select語句。

3、Cursor的操作

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. //AbstractCursor.java  
  2.   
  3.     public final boolean moveToFirst() {  
  4.         return moveToPosition(0);  
  5.     }  
  6.     public final boolean moveToNext() {  
  7.         return moveToPosition(mPos + 1);  
  8.     }  
  9.   
  10.     public final boolean moveToPosition(int position) {  
  11.         final int count = getCount(); // ⑤  
  12.         if (position >= count) {  
  13.             mPos = count;  
  14.             return false;  
  15.         }  
  16.         if (position < 0) {  
  17.             mPos = -1;  
  18.             return false;  
  19.         }  
  20.         if (position == mPos) {  
  21.             return true;  
  22.         }  
  23.         boolean result = onMove(mPos, position); /// ⑨  
  24.         if (result == false) {  
  25.             mPos = -1;  
  26.         } else {  
  27.             mPos = position;  
  28.             if (mRowIdColumnIndex != -1) {  
  29.                 mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));  
  30.             }  
  31.         }  
  32.   
  33.         return result;  
  34.     }  
  35.   
  36. // SQLiteCursor.java  
  37.     @Override  
  38.     public int getCount() {  
  39.         if (mCount == NO_COUNT) {  
  40.             fillWindow(0);   
  41.         }  
  42.         return mCount;  
  43.     }  
  44.   
  45.     @Override  
  46.     public boolean onMove(int oldPosition, int newPosition) {  
  47.         if (mWindow == null || newPosition < mWindow.getStartPosition() ||  
  48.                 newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {  
  49.             fillWindow(newPosition);  
  50.         }  
  51.         return true;  
  52.     }  

到這裏可以看到,第一次moveToPosition時,因爲此時mCount爲-1,fillWindow(0)。
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // SQLiteCursor.java  
  2.     private void fillWindow(int requiredPos) {  
  3.         clearOrCreateWindow(getDatabase().getPath());  // ⑥  
  4.   
  5.         if (mCount == NO_COUNT) {  
  6.             int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0); // ⑦  
  7.             mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true); // ⑧  
  8.             mCursorWindowCapacity = mWindow.getNumRows();  
  9.             if (Log.isLoggable(TAG, Log.DEBUG)) {  
  10.                 Log.d(TAG, "received count(*) from native_fill_window: " + mCount);  
  11.             }  
  12.         } else {  
  13.             int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,  
  14.                     mCursorWindowCapacity);    
  15.             mQuery.fillWindow(mWindow, startPos, requiredPos, false);  
  16.         }  
  17.     }  
  18.     protected void clearOrCreateWindow(String name) {  
  19.         if (mWindow == null) { // 建立CursorWindow  
  20.             mWindow = new CursorWindow(name);  
  21.         } else {  
  22.             mWindow.clear();  
  23.         }  
  24.     }  
在第⑥中,new出CursorWindow,將其賦值給mWindow,此時,由SQLiteCursor掌管。如下,new CursorWindow的過程,調用了nativeCreate,並使mWindowPtr指向native層的window。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // CursorWindow.java  
  2.     public CursorWindow(String name) {  
  3.         mStartPos = 0;  
  4.         mName = name != null && name.length() != 0 ? name : "<unnamed>";  
  5.         mWindowPtr = nativeCreate(mName, sCursorWindowSize); // !!!  
  6.         if (mWindowPtr == 0) {  
  7.             throw new CursorWindowAllocationException("Cursor window allocation of " +  
  8.                     (sCursorWindowSize / 1024) + " kb failed. " + printStats());  
  9.         }  
  10.         mCloseGuard.open("close");  
  11.         recordNewWindow(Binder.getCallingPid(), mWindowPtr);  
  12.     }  

先看第⑧中,fillWindow()

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // SQLiteQuery.java  
  2.     int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {  
  3.          ....  
  4.          int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),  
  5.                         window, startPos, requiredPos, countAllRows, getConnectionFlags(),  
  6.                         mCancellationSignal);  
  7.          return numRows;  
  8.      }  
  9. // SQLiteSeesion.java  
  10.     public int executeForCursorWindow(String sql, Object[] bindArgs,  
  11.             CursorWindow window, int startPos, int requiredPos, boolean countAllRows,  
  12.             int connectionFlags, CancellationSignal cancellationSignal) {  
  13.         acquireConnection(sql, connectionFlags, cancellationSignal);   
  14.         try {  
  15.             return mConnection.executeForCursorWindow(sql, bindArgs,  
  16.                     window, startPos, requiredPos, countAllRows,  
  17.                     cancellationSignal);   
  18.         } finally {  
  19.             releaseConnection();  
  20.         }  
  21.     }  
  22. // SQLiteConnection.java  
  23.     public int executeForCursorWindow(String sql, Object[] bindArgs,  
  24.         CursorWindow window, int startPos, int requiredPos, boolean countAllRows,  
  25.         CancellationSignal cancellationSignal) {  
  26.         final PreparedStatement statement = acquirePreparedStatement(sql);  
  27.         final long result = nativeExecuteForCursorWindow(   // !!!  
  28.                 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,  
  29.                 startPos, requiredPos, countAllRows);  
  30.         actualPos = (int)(result >> 32);  
  31.         countedRows = (int)result;  
  32.         filledRows = window.getNumRows();  
  33.         window.setStartPosition(actualPos);  
  34.         return countedRows;  
  35.         .....  
  36.     }  

可以看到,最終仍是通過SQLiteConnection連接到native來執行。

剩下的getString就比較簡單了,一直會調用到到mWindow的getString

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public String getString(int row, int column) {  
  2.     acquireReference();  
  3.     try {  
  4.         return nativeGetString(mWindowPtr, row - mStartPos, column);  
  5.     } finally {  
  6.         releaseReference();  
  7.     }  
  8. }  


最後看下第⑦,即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個位置。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // DatabaseUtils.java  
  2.     public static int cursorPickFillWindowStartPosition(  
  3.             int cursorPosition, int cursorWindowCapacity) {  
  4.         return Math.max(cursorPosition - cursorWindowCapacity / 30);  
  5.     }  

  1. static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,  
  2.         jint connectionPtr, jint statementPtr, jint windowPtr,  
  3.         jint startPos, jint requiredPos, jboolean countAllRows) {  
  4.     ......  
  5.   
  6.     int retryCount = 0;  
  7.     int totalRows = 0;  
  8.     int addedRows = 0;  
  9.     bool windowFull = false;  
  10.     bool gotException = false;  
  11.     while (!gotException && (!windowFull || countAllRows)) {  
  12.         int err = sqlite3_step(statement);  
  13.         if (err == SQLITE_ROW) {  
  14.             LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);  
  15.             retryCount = 0;  
  16.             totalRows += 1;  
  17.   
  18.             // Skip the row if the window is full or we haven't reached the start position yet.  
  19.             if (startPos >= totalRows || windowFull) {  
  20.                 continue;  
  21.             }  
  22.   
  23.             CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);  
  24.             if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {  
  25.                 // We filled the window before we got to the one row that we really wanted.  
  26.                 // Clear the window and start filling it again from here.  
  27.                 // TODO: Would be nicer if we could progressively replace earlier rows.  
  28.                 window->clear();  
  29.                 window->setNumColumns(numColumns);  
  30.                 startPos += addedRows;  
  31.                 addedRows = 0;  
  32.                 cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);  
  33.             }  
  34.   
  35.             if (cpr == CPR_OK) {  
  36.                 addedRows += 1;  
  37.             } else if (cpr == CPR_FULL) {  
  38.                 windowFull = true;  
  39.             } else {  
  40.                 gotException = true;  
  41.             }  
  42.         } else if (err == SQLITE_DONE) {  
  43.             // All rows processed, bail  
  44.             LOG_WINDOW("Processed all rows");  
  45.             break;  
  46.         } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {  
  47.             // The table is locked, retry  
  48.             LOG_WINDOW("Database locked, retrying");  
  49.             if (retryCount > 50) {  
  50.                 ALOGE("Bailing on database busy retry");  
  51.                 throw_sqlite3_exception(env, connection->db, "retrycount exceeded");  
  52.                 gotException = true;  
  53.             } else {  
  54.                 // Sleep to give the thread holding the lock a chance to finish  
  55.                 usleep(1000);  
  56.                 retryCount++;  
  57.             }  
  58.         } else {  
  59.             throw_sqlite3_exception(env, connection->db);  
  60.             gotException = true;  
  61.         }  
  62.     }  
  63.   
  64.     LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"  
  65.             "to the window in %d bytes",  
  66.             statement, totalRows, addedRows, window->size() - window->freeSpace());  
  67.     sqlite3_reset(statement);  
  68.   
  69.     // Report the total number of rows on request.  
  70.     if (startPos > totalRows) {  
  71.         ALOGE("startPos %d > actual rows %d", startPos, totalRows);  
  72.     }  
  73.     jlong result = jlong(startPos) << 32 | jlong(totalRows);  
  74.     return result;  
  75. }  

4、總結

① query的執行同普通sql語句相同,都需經過sql語句的編譯及執行。

② 編譯後爲SQLiteQuery,執行後返回SQLiteCursor,SQLiteCursor的mWindow指向native層的cursor window。

③ 通過SQLiteCursor對返回結果進行控制。

④ 執行的過程,是構建SQLiteCursor的過程,並未將結果集寫入相應window。

⑤ 結果集寫入window,發生在第一次類似cursor.moveToFirst()操作中。這是android中處處體現的惰性策略

⑥ sqlite本身對結果集與window的關係做了優化,android在此基礎上再次優化,以應對結果集過大、跳躍式讀取結果等問題。尚未分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章