简记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