Concepts of Cursor and CursorWindow on android platform

来源:互联网 发布:js字符串数组方法 编辑:程序博客网 时间:2024/06/05 04:42

简记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.java303    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 execution317            qCursor.getCount();318            long durationMillis = SystemClock.uptimeMillis() - startTime;319            maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);320            // Wrap the cursor object into CursorWrapperInner object321            return new CursorWrapperInner(qCursor, provider);322        } catch (RemoteException e) {323            releaseProvider(provider);324325            // Arbitrary and not worth documenting, as Activity326            // Manager will kill this process shortly anyway.327            return null;328        } catch (RuntimeException e) {329            releaseProvider(provider);330            throw e;331        }332    }IContentProvider.javastatic final int QUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;ContentProviderNative.javafinal class ContentProviderProxy implements IContentProvider315{316    public ContentProviderProxy(IBinder remote)317    {318        mRemote = remote;319    }320321    public IBinder asBinder()322    {323        return mRemote;324    }325326    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);333334            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());355356            mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);357358            DatabaseUtils.readExceptionFromParcel(reply);359360            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.java77    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);8485                    Uri url = Uri.CREATOR.createFromParcel(data);8687                    // String[] projection88                    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                    }9697                    // 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                    }107108                    String sortOrder = data.readString();109                    IContentObserver observer = IContentObserver.Stub.asInterface(110                            data.readStrongBinder());111112                    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();121122                        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                    }131132                    return true;133                }     ……………..298            }299        } catch (Exception e) {300            DatabaseUtils.writeExceptionToParcel(reply, e);301            return true;302        }303304        return super.onTransact(code, data, reply, flags);305    }306SettingsProvider.java for example.641    @Override642    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();645646        // The favorites table was moved from this provider to a provider inside Home647        // Home still need to query this table to upgrade from pre-cupcake builds648        // However, a cupcake+ build with no data does not contain this table which will649        // cause an exception in the SQL stack. The following line is a special case to650        // let the caller of the query have a chance to recover and avoid the exception651        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        }664665        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();666        qb.setTables(args.table);667668        Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);669        ret.setNotificationUri(getContext().getContentResolver(), url);670        return ret;671    }SQLiteQueryBuilder.java327    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        }333334        if (mStrict && selection != null && selection.length() > 0) {335            // Validate the user-supplied selection to detect syntactic anomalies336            // 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 expression338            // by compiling it twice: once wrapped in parentheses and once as339            // originally specified. An attacker cannot create an expression that340            // would escape the SQL expression while maintaining balanced parentheses341            // 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 invalid345        }346347        String sql = buildQuery(348                projectionIn, selection, groupBy, having,349                sortOrder, limit);350351        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 invalid357    }SQLiteDatabase.java1568    public Cursor rawQueryWithFactory(1569            CursorFactory cursorFactory, String sql, String[] selectionArgs,1570            String editTable) {1571        verifyDbIsOpen();1572        BlockGuard.getThreadPolicy().onReadFromDisk();15731574        SQLiteDatabase db = getDbConnection(sql);1575        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable);15761577        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.java40    public Cursor query(CursorFactory factory, String[] selectionArgs) {41        // Compile the query42        SQLiteQuery query = null;4344        try {45            mDatabase.lock(mSql);46            mDatabase.closePendingStatements();47            query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);4849            // Create the cursor50            if (factory == null) {51                mCursor = new SQLiteCursor(this, mEditTable, query);52            } else {53                mCursor = factory.newCursor(mDatabase, this, mEditTable, query);54            }5556            mQuery = query;57            query = null;58            return mCursor;59        } finally {60            // Make sure this object is cleaned up if something happens61            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    @Override154    public int getCount() {155        if (mCount == NO_COUNT) {156            fillWindow(0);157        }158        return mCount;159    }160161    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 = 0166            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 " + elapsed92                                + " ms: window=\"" + window93                                + "\", startPos=" + startPos94                                + ", offset=" + mOffsetIndex95                                + ", filledRows=" + window.getNumRows()96                                + ", countedRows=" + numRows97                                + ", 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 it107                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    @Override80    public int getCount() {81        throwIfCursorIsClosed();82        return mCount;83    }8485    @Override86    public boolean onMove(int oldPosition, int newPosition) {87        throwIfCursorIsClosed();8889        try {90            // Make sure we have the proper window91            if (mWindow == null92                    || 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 failed100            Log.e(TAG, "Unable to get window because the remote process is dead");101            return false;102        }103104        // Couldn't obtain a window, something is wrong105        if (mWindow == null) {106            return false;107        }108109        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 BulkCursorProxy183    public CursorWindow getWindow(int startPos) throws RemoteException184    {185        Parcel data = Parcel.obtain();186        Parcel reply = Parcel.obtain();187        try {188            data.writeInterfaceToken(IBulkCursor.descriptor);189            data.writeInt(startPos);190191            mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);192            DatabaseUtils.readExceptionFromParcel(reply);193194            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    }204205    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);211212            mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0);213            DatabaseUtils.readExceptionFromParcel(reply);214        } finally {215            data.recycle();216            reply.recycle();217        }218    }219220    public int count() throws RemoteException221    {222        Parcel data = Parcel.obtain();223        Parcel reply = Parcel.obtain();224        try {225            data.writeInterfaceToken(IBulkCursor.descriptor);226227            boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);228            DatabaseUtils.readExceptionFromParcel(reply);229230            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 BulkCursorNative56    @Override57    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                }7475                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 CursorToBulkCursorAdaptor134    @Override135    public CursorWindow getWindow(int startPos) {136        synchronized (mLock) {137            throwIfCursorIsClosed();138139            if (!mCursor.moveToPosition(startPos)) {// will cause SQLiteCursor create CursorWindow140                closeFilledWindowLocked();// close Adaptor’s self mFilledWindow if exsiting141                return null;142            }143144            CursorWindow window = mCursor.getWindow();    // get SQLiteCursor’s mWindow145            if (window != null) { // when mCursor.moveToPosition  is called, clearOrCreateWindow-ed146                closeFilledWindowLocked();// close Adaptor’s self mFilledWindow if exsiting147            } else {// create the window as mFilledWindow if SQLiteCursor has not its Window148                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            }159160            // Acquire a reference before returning from this RPC.161            // The Binder proxy will decrement the reference count again as part of writing162            // 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    @Override192    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 it320        } finally {321            window.releaseReference();322        }323    }cursor.getCount() is called.


END

原创粉丝点击