Concepts of Cursor and CursorWindow on android platform

简记android平台下ContenProvider使用SQLite数据库实现时的代码片段和逻辑结构,主要描述Cursor和CursorWindow.

Cursor can be treated as an iterater of the traditional ODBC record set and is the current position of the data record. It describes and reflects the logical result record set in android.

AbstractWindowedCursor both in client side and provider-implementing sideassociates a cursor window, which is an ashmem actually on android platform. Same name, same ashmem.

BulkCursorToCursorAdaptor and CursorToBulkCursorAdaptor are used for cursor transfer between processes, they are used to binderize the cursor.

 CursorWindow uses the /dev/ashmem to share the memory and avoids data block transfer to improve efficiency. CursorWindow is a cache window/buffer of the record set which is an obvious design to achieve efficiency. Using ashmem is avoiding record block transfer and is more efficiency in the requirement and circumstance.

IN ALL, CursorWindow is created with RW in service process, and is created with R in client process for with RW failed.

 

The relation of Cursors:
Cursor
  CrossProcessCursor
    AbstractCursor
      AbstractWindowedCursor       
         SQLiteCursor

For BuilkCursorToCursorAdapater's hierachy:
Cursor
  CrossProcessCursor
    AbstractCursor
      AbstractWindowedCursor       
        BulkCursorToCursorAdaptor

The top 4 base classes are same used in client and provider-implementing side.

 

------- Code of Control Flow------------

ContentResolver.java
303    public final Cursor query(Uri uri, String[] projection,
304            String selection, String[] selectionArgs, String sortOrder) {
305        IContentProvider provider = acquireProvider(uri);
306        if (provider == null) {
307            return null;
308        }
309        try {
310            long startTime = SystemClock.uptimeMillis();
311            Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
312            if (qCursor == null) {
313                releaseProvider(provider);
314                return null;
315            }
316            // force query execution
317            qCursor.getCount();
318            long durationMillis = SystemClock.uptimeMillis() - startTime;
319            maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
320            // Wrap the cursor object into CursorWrapperInner object
321            return new CursorWrapperInner(qCursor, provider);
322        } catch (RemoteException e) {
323            releaseProvider(provider);
324
325            // Arbitrary and not worth documenting, as Activity
326            // Manager will kill this process shortly anyway.
327            return null;
328        } catch (RuntimeException e) {
329            releaseProvider(provider);
330            throw e;
331        }
332    }

IContentProvider.java
static final int QUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;

ContentProviderNative.java
final class ContentProviderProxy implements IContentProvider
315{
316    public ContentProviderProxy(IBinder remote)
317    {
318        mRemote = remote;
319    }
320
321    public IBinder asBinder()
322    {
323        return mRemote;
324    }
325
326    public Cursor query(Uri url, String[] projection, String selection,
327            String[] selectionArgs, String sortOrder) throws RemoteException {
328        BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
329        Parcel data = Parcel.obtain();
330        Parcel reply = Parcel.obtain();
331        try {
332            data.writeInterfaceToken(IContentProvider.descriptor);
333
334            url.writeToParcel(data, 0);
335            int length = 0;
336            if (projection != null) {
337                length = projection.length;
338            }
339            data.writeInt(length);
340            for (int i = 0; i < length; i++) {
341                data.writeString(projection[i]);
342            }
343            data.writeString(selection);
344            if (selectionArgs != null) {
345                length = selectionArgs.length;
346            } else {
347                length = 0;
348            }
349            data.writeInt(length);
350            for (int i = 0; i < length; i++) {
351                data.writeString(selectionArgs[i]);
352            }
353            data.writeString(sortOrder);
354            data.writeStrongBinder(adaptor.getObserver().asBinder());
355
356            mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
357
358            DatabaseUtils.readExceptionFromParcel(reply);
359
360            IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder());
361            if (bulkCursor != null) {
362                int rowCount = reply.readInt();
363                int idColumnPosition = reply.readInt();
364                boolean wantsAllOnMoveCalls = reply.readInt() != 0;
365                adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls);
366            } else {
367                adaptor.close();
368                adaptor = null;
369            }
370            return adaptor;
371        } catch (RemoteException ex) {
372            adaptor.close();
373            throw ex;
374        } catch (RuntimeException ex) {
375            adaptor.close();
376            throw ex;
377        } finally {
378            data.recycle();
379            reply.recycle();
380        }
381    }

ContentProviderNative.java
77    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
78            throws RemoteException {
79        try {
80            switch (code) {
81                case QUERY_TRANSACTION:
82                {
83                    data.enforceInterface(IContentProvider.descriptor);
84
85                    Uri url = Uri.CREATOR.createFromParcel(data);
86
87                    // String[] projection
88                    int num = data.readInt();
89                    String[] projection = null;
90                    if (num > 0) {
91                        projection = new String[num];
92                        for (int i = 0; i < num; i++) {
93                            projection[i] = data.readString();
94                        }
95                    }
96
97                    // String selection, String[] selectionArgs...
98                    String selection = data.readString();
99                    num = data.readInt();
100                    String[] selectionArgs = null;
101                    if (num > 0) {
102                        selectionArgs = new String[num];
103                        for (int i = 0; i < num; i++) {
104                            selectionArgs[i] = data.readString();
105                        }
106                    }
107
108                    String sortOrder = data.readString();
109                    IContentObserver observer = IContentObserver.Stub.asInterface(
110                            data.readStrongBinder());
111
112                    Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder);
113                    if (cursor != null) {
114                        CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
115                                cursor, observer, getProviderName());
116                        final IBinder binder = adaptor.asBinder();
117                        final int count = adaptor.count();
118                        final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex(
119                                adaptor.getColumnNames());
120                        final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls();
121
122                        reply.writeNoException();
123                        reply.writeStrongBinder(binder);
124                        reply.writeInt(count);
125                        reply.writeInt(index);
126                        reply.writeInt(wantsAllOnMoveCalls ? 1 : 0);
127                    } else {
128                        reply.writeNoException();
129                        reply.writeStrongBinder(null);
130                    }
131
132                    return true;
133                }
     ……………..
298            }
299        } catch (Exception e) {
300            DatabaseUtils.writeExceptionToParcel(reply, e);
301            return true;
302        }
303
304        return super.onTransact(code, data, reply, flags);
305    }
306


SettingsProvider.java for example.
641    @Override
642    public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
643        SqlArguments args = new SqlArguments(url, where, whereArgs);
644        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
645
646        // The favorites table was moved from this provider to a provider inside Home
647        // Home still need to query this table to upgrade from pre-cupcake builds
648        // However, a cupcake+ build with no data does not contain this table which will
649        // cause an exception in the SQL stack. The following line is a special case to
650        // let the caller of the query have a chance to recover and avoid the exception
651        if (TABLE_FAVORITES.equals(args.table)) {
652            return null;
653        } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
654            args.table = TABLE_FAVORITES;
655            Cursor cursor = db.rawQuery("PRAGMA table_info(favorites);", null);
656            if (cursor != null) {
657                boolean exists = cursor.getCount() > 0;
658                cursor.close();
659                if (!exists) return null;
660            } else {
661                return null;
662            }
663        }
664
665        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
666        qb.setTables(args.table);
667
668        Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
669        ret.setNotificationUri(getContext().getContentResolver(), url);
670        return ret;
671    }

SQLiteQueryBuilder.java
327    public Cursor query(SQLiteDatabase db, String[] projectionIn,
328            String selection, String[] selectionArgs, String groupBy,
329            String having, String sortOrder, String limit) {
330        if (mTables == null) {
331            return null;
332        }
333
334        if (mStrict && selection != null && selection.length() > 0) {
335            // Validate the user-supplied selection to detect syntactic anomalies
336            // in the selection string that could indicate a SQL injection attempt.
337            // The idea is to ensure that the selection clause is a valid SQL expression
338            // by compiling it twice: once wrapped in parentheses and once as
339            // originally specified. An attacker cannot create an expression that
340            // would escape the SQL expression while maintaining balanced parentheses
341            // in both the wrapped and original forms.
342            String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
343                    having, sortOrder, limit);
344            validateSql(db, sqlForValidation); // will throw if query is invalid
345        }
346
347        String sql = buildQuery(
348                projectionIn, selection, groupBy, having,
349                sortOrder, limit);
350
351        if (Log.isLoggable(TAG, Log.DEBUG)) {
352            Log.d(TAG, "Performing query: " + sql);
353        }
354        return db.rawQueryWithFactory(
355                mFactory, sql, selectionArgs,
356                SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid
357    }

SQLiteDatabase.java
1568    public Cursor rawQueryWithFactory(
1569            CursorFactory cursorFactory, String sql, String[] selectionArgs,
1570            String editTable) {
1571        verifyDbIsOpen();
1572        BlockGuard.getThreadPolicy().onReadFromDisk();
1573
1574        SQLiteDatabase db = getDbConnection(sql);
1575        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable);
1576
1577        Cursor cursor = null;
1578        try {
1579            cursor = driver.query(
1580                    cursorFactory != null ? cursorFactory : mFactory,
1581                    selectionArgs);
1582        } finally {
1583            releaseDbConnection(db);
1584        }
1585        return cursor;
1586    }

SQLiteDirectCursorDriver.java
40    public Cursor query(CursorFactory factory, String[] selectionArgs) {
41        // Compile the query
42        SQLiteQuery query = null;
43
44        try {
45            mDatabase.lock(mSql);
46            mDatabase.closePendingStatements();
47            query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
48
49            // Create the cursor
50            if (factory == null) {
51                mCursor = new SQLiteCursor(this, mEditTable, query);
52            } else {
53                mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
54            }
55
56            mQuery = query;
57            query = null;
58            return mCursor;
59        } finally {
60            // Make sure this object is cleaned up if something happens
61            if (query != null) query.close();
62            mDatabase.unlock();
63        }
64    }

It can be seen that only allocate the SQLiteCursor and save the query statement.

 

If ContentResolver user and ContentProvider in the same process, for example

06-26 16:30:11.439 E/AndroidRuntime(13356):    at android.database.CursorWindow.<init>(CursorWindow.java:104)

06-26 16:30:11.439 E/AndroidRuntime(13356):    at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)

06-26 16:30:11.439 E/AndroidRuntime(13356):    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:162)

06-26 16:30:11.439 E/AndroidRuntime(13356):    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156)

06-26 16:30:11.439 E/AndroidRuntime(13356):    at android.content.ContentResolver.query(ContentResolver.java:317)

06-26 16:30:11.439 E/AndroidRuntime(13356):    at com.android.providers.downloads.DownloadService$UpdateThread.run(DownloadService.java:307)

 

SQLiteCursor.getCount is called directly

153    @Override
154    public int getCount() {
155        if (mCount == NO_COUNT) {
156            fillWindow(0);
157        }
158        return mCount;
159    }
160
161    private void fillWindow(int startPos) {
162        clearOrCreateWindow(getDatabase().getPath());
163        mWindow.setStartPosition(startPos);
164        int count = getQuery().fillWindow(mWindow);
165        if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
166            if (Log.isLoggable(TAG, Log.DEBUG)) {
167                Log.d(TAG, "received count(*) from native_fill_window: " + count);
168            }
169            mCount = count;
170        } else if (mCount <= 0) {
171            throw new IllegalStateException("Row count should never be zero or negative "
172                    + "when the start position is non-zero");
173        }
174    }
Using the sqlite query to fillWindow.
78    /* package */ int fillWindow(CursorWindow window) {
79        mDatabase.lock(mSql);
80        long timeStart = SystemClock.uptimeMillis();
81        try {
82            acquireReference();
83            try {
84                window.acquireReference();
85                int startPos = window.getStartPosition();
86                int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
87                        startPos, mOffsetIndex);
88                if (SQLiteDebug.DEBUG_LOG_SLOW_QUERIES) {
89                    long elapsed = SystemClock.uptimeMillis() - timeStart;
90                    if (SQLiteDebug.shouldLogSlowQuery(elapsed)) {
91                        Log.d(TAG, "fillWindow took " + elapsed
92                                + " ms: window=\"" + window
93                                + "\", startPos=" + startPos
94                                + ", offset=" + mOffsetIndex
95                                + ", filledRows=" + window.getNumRows()
96                                + ", countedRows=" + numRows
97                                + ", query=\"" + mSql + "\""
98                                + ", args=[" + (mBindArgs != null ?
99                                        TextUtils.join(", ", mBindArgs.values()) : "")
100                                + "]");
101                    }
102                }
103                mDatabase.logTimeStat(mSql, timeStart);
104                return numRows;
105            } catch (IllegalStateException e){
106                // simply ignore it
107                return 0;
108            } catch (SQLiteDatabaseCorruptException e) {
109                mDatabase.onCorruption();
110                throw e;
111            } catch (SQLiteException e) {
112                Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql);
113                throw e;
114            } finally {
115                window.releaseReference();
116            }
117        } finally {
118            releaseReference();
119            mDatabase.unlock();
120        }
121    }
Use 
38static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
39        jint statementPtr, jint windowPtr, jint startPos, jint offsetParam) querying sqlite3 to fill window. 

If ContentResolver user and ContentProvider in the different process, qCursur.getCount() returns directly.
79    @Override
80    public int getCount() {
81        throwIfCursorIsClosed();
82        return mCount;
83    }
84
85    @Override
86    public boolean onMove(int oldPosition, int newPosition) {
87        throwIfCursorIsClosed();
88
89        try {
90            // Make sure we have the proper window
91            if (mWindow == null
92                    || newPosition < mWindow.getStartPosition()
93                    || newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) {
94                setWindow(mBulkCursor.getWindow(newPosition));
95            } else if (mWantsAllOnMoveCalls) {
96                mBulkCursor.onMove(newPosition);
97            }
98        } catch (RemoteException ex) {
99            // We tried to get a window and failed
100            Log.e(TAG, "Unable to get window because the remote process is dead");
101            return false;
102        }
103
104        // Couldn't obtain a window, something is wrong
105        if (mWindow == null) {
106            return false;
107        }
108
109        return true;
110    }

When the qCursor is moved, onMove is called, and setWindow(mBulkCursor.getWindow()) branch is taken for the first time or cursor is out of the current range. And mBulkCursor.onMove branch is called for maintaining cursor position consistent.

In BulkCursorProxy
183    public CursorWindow getWindow(int startPos) throws RemoteException
184    {
185        Parcel data = Parcel.obtain();
186        Parcel reply = Parcel.obtain();
187        try {
188            data.writeInterfaceToken(IBulkCursor.descriptor);
189            data.writeInt(startPos);
190
191            mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);
192            DatabaseUtils.readExceptionFromParcel(reply);
193
194            CursorWindow window = null;
195            if (reply.readInt() == 1) {
196                window = CursorWindow.newFromParcel(reply);
197            }
198            return window;
199        } finally {
200            data.recycle();
201            reply.recycle();
202        }
203    }
204
205    public void onMove(int position) throws RemoteException {
206        Parcel data = Parcel.obtain();
207        Parcel reply = Parcel.obtain();
208        try {
209            data.writeInterfaceToken(IBulkCursor.descriptor);
210            data.writeInt(position);
211
212            mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0);
213            DatabaseUtils.readExceptionFromParcel(reply);
214        } finally {
215            data.recycle();
216            reply.recycle();
217        }
218    }
219
220    public int count() throws RemoteException
221    {
222        Parcel data = Parcel.obtain();
223        Parcel reply = Parcel.obtain();
224        try {
225            data.writeInterfaceToken(IBulkCursor.descriptor);
226
227            boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
228            DatabaseUtils.readExceptionFromParcel(reply);
229
230            int count;
231            if (result == false) {
232                count = -1;
233            } else {
234                count = reply.readInt();
235            }
236            return count;
237        } finally {
238            data.recycle();
239            reply.recycle();
240        }
241    } 

In BulkCursorNative
56    @Override
57    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
58            throws RemoteException {
59        try {
60            switch (code) {
61                case GET_CURSOR_WINDOW_TRANSACTION: {
62                    data.enforceInterface(IBulkCursor.descriptor);
63                    int startPos = data.readInt();
64                    CursorWindow window = getWindow(startPos);
65                    reply.writeNoException();
66                    if (window == null) {
67                        reply.writeInt(0);
68                    } else {
69                        reply.writeInt(1);
70                        window.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
71                    }
72                    return true;
73                }
74
75                case COUNT_TRANSACTION: {
76                    data.enforceInterface(IBulkCursor.descriptor);
77                    int count = count();
78                    reply.writeNoException();
79                    reply.writeInt(count);
80                    return true;
81                }
	}
        }

public abstract class BulkCursorNative extends Binder implements IBulkCursor, while public final class CursorToBulkCursorAdaptor extends BulkCursorNative

In CursorToBulkCursorAdaptor
134    @Override
135    public CursorWindow getWindow(int startPos) {
136        synchronized (mLock) {
137            throwIfCursorIsClosed();
138
139            if (!mCursor.moveToPosition(startPos)) {	// will cause SQLiteCursor create CursorWindow
140                closeFilledWindowLocked();	// close Adaptor’s self mFilledWindow if exsiting
141                return null;
142            }
143
144            CursorWindow window = mCursor.getWindow();    // get SQLiteCursor’s mWindow
145            if (window != null) { // when mCursor.moveToPosition  is called, clearOrCreateWindow-ed
146                closeFilledWindowLocked();	// close Adaptor’s self mFilledWindow if exsiting
147            } else {	// create the window as mFilledWindow if SQLiteCursor has not its Window
148                window = mFilledWindow;
149                if (window == null) {
150                    mFilledWindow = new CursorWindow(mProviderName);
151                    window = mFilledWindow;
152                    mCursor.fillWindow(startPos, window);	// SQLiteCursor fill passed-in window??
153                } else if (startPos < window.getStartPosition()
154                        || startPos >= window.getStartPosition() + window.getNumRows()) {
155                    window.clear();
156                    mCursor.fillWindow(startPos, window);
157                }
158            }
159
160            // Acquire a reference before returning from this RPC.
161            // The Binder proxy will decrement the reference count again as part of writing
162            // the CursorWindow to the reply parcel as a return value.
163            if (window != null) {
164                window.acquireReference();
165            }
166            return window;
167        }
168    }

 

Note the comment in CursorToBulkCursorAdaptor,

26 * Wraps a BulkCursor around an existing Cursor making it remotable.

27 * <p>

28 * If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow}

29 * then it is assumed to own the window. Otherwise, the adaptor provides a

30 * window to be filled and ensures it gets closed as needed during deactivation

31 * and requeries.

32 * </p>

mCursor is instance of SQLiteCursor .

In most cases, SQLiteCursor’s CursorWindow is used, so SQLiteCursor::fileWindow is called, same flow with cs same process case.

If SQLiteCursor’s CursorWindow is null, why? Maybe some othrer database rather than SQLite.

A new CursorWindow will be created as CursorToBulkCursorAdaptor::mFilledWindow and is passed to database to fill the window.

191    @Override
192    public void AbstractCursor::fillWindow(int position, CursorWindow window) {
193        DatabaseUtils.cursorFillWindow(this, position, window);
194    }

261    public static void cursorFillWindow(final Cursor cursor,
262            int position, final CursorWindow window) {
263        if (position < 0 || position >= cursor.getCount()) {
264            return;
265        }
266        window.acquireReference();
267        try {
268            final int oldPos = cursor.getPosition();
269            final int numColumns = cursor.getColumnCount();
270            window.clear();
271            window.setStartPosition(position);
272            window.setNumColumns(numColumns);
273            if (cursor.moveToPosition(position)) {
274                do {
275                    if (!window.allocRow()) {
276                        break;
277                    }
…………………
309                        if (!success) {
310                            window.freeLastRow();
311                            break;
312                        }
313                    }
314                    position += 1;
315                } while (cursor.moveToNext());
316            }
317            cursor.moveToPosition(oldPos);
318        } catch (IllegalStateException e){
319            // simply ignore it
320        } finally {
321            window.releaseReference();
322        }
323    }
cursor.getCount() is called.


END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值