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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. // DatabaseUtils.java  
  2.     public static int cursorPickFillWindowStartPosition(  
  3.             int cursorPosition, int cursorWindowCapacity) {  
  4.         return Math.max(cursorPosition - cursorWindowCapacity / 30);  
  5.     }  

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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在此基础上再次优化,以应对结果集过大、跳跃式读取结果等问题。尚未分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值