这篇文章会分成两部分进行讲述以帮助大家更好地理解Cursor的使用方法。
- IPC获取Cursor原理、Cursor读取原理
了解通过ContentProvider获取Cursor的原理以及过程中一些关键点;了解Client在拿到Cursor资源对象后获取其中内容、移动游标位置的原理。
- 自定义Cursor
如何通过ContentProvider返回一个自定义Cursor。
IPC获取Cursor原理
从通过ContentResolver拿到 ContentProvider.query() 返回的查询结果Cursor,到操作Cursor的位置指针获取其中的内容,再到最后通过 close() 方法释放Cursor资源。这一系列过程中涵盖了大量的Binder IPC操作,本小节通过解析源码和IPC通信的过程来揭示Client APP是如何拿到可使用Cursor对象的。
一、IBulkCursor
IBulkCursor Binder接口为Cursor IPC操作提供控制支持,是除去Cursor本身最重要的辅助类。例如在Client APP中使用 Cursor.moveToNext() 移动指示行的标识位置时,正是通过 IBulkCursor.onMove() 映射到Server端的Cursor对象。
IBulkCursor Binder IPC接口及实现类:

- CursorToBulkCursorAdaptor 定义为final类,是IBulkCursor中所定义方法的具体实现所在。作用是接收Client对Cursor操作的IPC请求,完成远端操作对原始Cursor对象的映射。
- CursorToBulkCursorAdaptor 通过使用 监视器锁 来保证IPC调用时,IBulkCursor操作的原子性。
1 2 3 4 5 6
| @Override public void close() { synchronized (mLock) { disposeLocked(); } }
|
举个例子:
当在Client APP中使用Cursor的moveToNext()方法时,会调用到IBulkCursor接口的onMove()方法,通过Binder IPC到CursorToBulkCursorAdaptor的onMove()实现中。
CursorToBulkCursorAdaptor.java
1 2 3 4 5 6 7 8
| @Override public void onMove(int position) { synchronized (mLock) { throwIfCursorIsClosed();
mCursor.onMove(mCursor.getPosition(), position); } }
|
由此完成Client APP在移动Cursor的游标位置后,真正Cursor对象的游标位置也能随之移动。
NOTE: 需要注意的是在当前Android SDK的实现里面,客户端对Cursor的读取操作和游标移动都是代理给了其中的CursorWindow。IBulkCursor及其Native、Proxy也都是隐藏的API。对于应用工程师来说,了解到在执行Cursor的requery()、close()等方法时会调用IBulkCursor的相关接口通知Server端就够了。
二、如何拿到Cursor
Note: 这里不涉及Server如何查询生成Cursor对象,以及Client如何通过ContentResolver调用ContentProvider接口(这些都是ContentProvider的内容)。
1.在ContentProviderNative中,可以看到是如何将Cursor对象的操作句柄传送给Client APP的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ... Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder, cancellationSignal); if (cursor != null) { CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( cursor, observer, getProviderName()); BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
reply.writeNoException(); reply.writeInt(1); d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeNoException(); reply.writeInt(0); } ...
|
步骤:
- 将query()生成的Cursor包装在控制辅助类CursorToBulkCursorAdaptor中。
- 生成操作句柄BulkCursorDescriptor。
- 将 成功标识符(1) 以及 BulkCursorDescriptor 放入Parcel中,写回给Client APP。
2.在ContentProviderProxy中,则可以看到是如何通过句柄生成一个可以被使用的Cursor对象并返回给调用者的。
1 2 3 4 5 6 7 8 9 10 11 12
| ... mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); if (reply.readInt() != 0) { BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply); adaptor.initialize(d); //adaptor的类型为BulkCursorToCursorAdaptor } else { adaptor.close(); adaptor = null; } return adaptor; ...
|
调用者拿到的是类型为BulkCursorToCursorAdaptor的Cursor对象。BulkCursorToCursorAdaptor适配器将IBulkCursor适配成Cursor,让Client APP在执行很多操作时完全感受不到IBulkCursor的IPC调用。
3.BulkCursorDescriptor

- 通过句柄拿到了Cursor对象Binder IPC的操作接口IBulkCursor。Server端的IBulkCursor实现类对象CursorToBulkCursorAdaptor持有真正Cursor资源对象的引用
- 列名数组以及row数量等重要信息。
- CursorWindow。携带着多条Cursor row的数据结构,实现了Parcelable接口,可以通过Binder IPC在进程间传递。在Client APP中使用Cursor接口读取的内容便来自CursorWindow。
Cursor操作原理
经过上面一个小节了解了Cursor及其关联对象是如何通过Binder IPC在Server/Client两端传递的。本小节就看看系统是如何在Client中使用通过ContentResolver拿到的Cursor对象。
Cursor接口及其实现类图:

先对其中几个重要的类做一下说明:
- AbstractCursor: 主要目标在于实现跟Cursor游标位置、Observer有关的一些操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ... public final boolean move(int offset) { return moveToPosition(mPos + offset); }
public final boolean moveToFirst() { return moveToPosition(0); }
public final boolean moveToLast() { return moveToPosition(getCount() - 1); }
public final boolean moveToNext() { return moveToPosition(mPos + 1); }
public final boolean moveToPrevious() { return moveToPosition(mPos - 1); }
public final boolean isFirst() { return mPos == 0 && getCount() != 0; }
public final boolean isLast() { int cnt = getCount(); return mPos == (cnt - 1) && cnt != 0; }
...
|
- AbstractWindowedCursor:
首先,作为AbstractCursor的直接子类,应该完成的最主要任务是实现从每一个Cursor row中获取相关信息的逻辑。如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ... @Override public int getInt(int columnIndex) { checkPosition(); return mWindow.getInt(mPos, columnIndex); }
@Override public long getLong(int columnIndex) { checkPosition(); return mWindow.getLong(mPos, columnIndex); }
@Override public float getFloat(int columnIndex) { checkPosition(); return mWindow.getFloat(mPos, columnIndex); }
@Override public double getDouble(int columnIndex) { checkPosition(); return mWindow.getDouble(mPos, columnIndex); } ...
|
与MaxtrixCursor、SortCursor等不同的是,在这里所有的Cursor数据装载到CursorWindow中。
一、Client中Cursor对象调用关系
在Client中通过ContentResolver的query()方法拿到Cursor对象并不是前面看到通过Binder IPC返回的BulkCursorDescriptor对象生成的Cursor操作具体实现类BulkCursorToCursorAdaptor。而是外面包了一层装饰者CursorWrapperInner:
ContentResolver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // query() 方法节选 Cursor qCursor; try { qCursor = unstableProvider.query(uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } catch (DeadObjectException e) { ... qCursor = stableProvider.query(uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } if (qCursor == null) { return null; } ... CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, stableProvider != null ? stableProvider : acquireProvider(uri)); stableProvider = null; return wrapper;
|
CursorWrapperInner的作用是在释放Cursor资源时顺带减少Cursor关联的ContentProvider引用计数。稍微了解装饰者设计模式就可以一眼看出来真正执行Cursor接口相关方法的是传给CursorWrapperInner构造器中的 qCursor对象。
CursorWrapperInner的父类CursorWrapper也印证了这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ... public int getInt(int columnIndex) { return mCursor.getInt(columnIndex); }
public long getLong(int columnIndex) { return mCursor.getLong(columnIndex); }
public short getShort(int columnIndex) { return mCursor.getShort(columnIndex); }
public String getString(int columnIndex) { return mCursor.getString(columnIndex); } ...
|
跟上一小节 Binder IPC原理 串联起来,可以得出以下小结:
Client在平时操作从ContentResolver拿到的Cursor对象时,本质就是访问通过ContentProvider IPC拿到的BulkCursorToCursorAdaptor。
通过前面的介绍,我们知道BulkCursorToCursorAdaptor是AbstractWindowedCursor的子类,则Client在访问Cursor中内容时,便是转换给CursorWindow对象来执行。该CursorWindow对象由ContentProvider的Server生成并返回给调用Client。
二、CursorWindow读取原理
1.一个包含了多条Cursor row的内存缓冲区。
2.在Server端创建时处于可读写的状态,如果一直在该进程内使用也会一直是可读写;但是如果经过Parcel的IPC传送之后,在远端进程中只能是只读的状态。
3.典型的生产者-消费者结构,在生产者Server端分配内存空间,填充数据然后发回给Client端,而Client APP仅仅读取其中的内容。
4.无论读还是写都是native方法,Java只是包了一层Java API。
以putLong()方法为例,看看是如何使用这片缓存区的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| //Java 方法 public boolean putLong(long value, int row, int column) { acquireReference(); try { return nativePutLong(mWindowPtr, value, row - mStartPos, column); } finally { releaseReference(); } }
//native 方法 status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) { if (mReadOnly) { return INVALID_OPERATION; }
FieldSlot* fieldSlot = getFieldSlot(row, column); if (!fieldSlot) { return BAD_VALUE; }
fieldSlot->type = FIELD_TYPE_INTEGER; fieldSlot->data.l = value; return OK; }
|
- mReadOnly为true时则不允许写入。mReadOnly在调用create()方法构造CursorWindow时会被赋值为false;在通过createFromParcel反序列化回CursorWindow时会被赋值为true。
- 空间不足时则不允许写入,返回false。
- 缓存区不是直接保存数值,而是保存一种数据结构FieldSlot。FieldSlot的数据结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct FieldSlot { private: int32_t type; //类型描述 union { double d; //float、double使用 int64_t l; //int、long使用 struct { //blot、String、Null使用... uint32_t offset; uint32_t size; } buffer; } data;
friend class CursorWindow; } __attribute((packed));
|
以上可得出结论:CursorWindow拥有一块共享的内存区域,只要能正确调整指向不同内存区域的位置指针,就能够拿到对应的Cursor值。平时所理解的行、列索引,其实代表着指向着特定内存区域的指
自定义Cursor
对于Cursor的使用,按照使用区域的不同可以分成 生成进程内使用 和 跨进程使用。在生成Cursor进程内使用Cursor的场景很少,同时Android SDK本身也提供了很多API为这个场景提供支持(如上面提到的MatrixCursor、MergeCursor)。
因此这里我们只聚焦在Cursor最主要使用的场景 —— 跨进程使用。
虽然根据获取的方式不同,跨进程使用还可以分为:
- 通过ContentProvider获取
- 通过自定义Binder IPC接口获取
但是由于Cursor IPC传递的本质还是传递CursorWindow,从数据流动的角度来说都是一样的。这里我想通过第一种方式就足以证明如何自定义一个在IPC过程中传送的Cursor。
通过ContentProvider获取
通过ContentProvider获取Cursor流程一般都是这样的:通过ContentResolver查询到ContentProvider Binder IPC的接口,然后获取query() 调用返回的Cursor。第一小节的 IPC获取Cursor原理 亦是基于这一套流程进行分析。
1.实现一个继承自AbstractWindowedCursor的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyWindowedCursor(window: CursorWindow) : AbstractWindowedCursor() { init { setWindow(window) } //定义Client拿到的Cursor的行数 override fun getCount(): Int { return window.numRows } //定义Cursor的每一列的列名 override fun getColumnNames(): Array<String> { return arrayOf("key", "value") } }
|
2.实现ContentProvider,并将填充好内容的CursorWindow装进Cursor中并返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? { val window = CursorWindow("CursorProvider") // 必须先要设置每一行有多少列才能往里面添加数据 window.setNumColumns(2) // 分配每一行的内存地址,建议每次需要新添加一行时新分配一行的内存。 window.allocRow() // 行、列索引都是从零开始 window.putString("key1", 0, 0) window.putLong(1, 0, 1) window.allocRow() window.putString("key2", 1, 0) window.putLong(2, 1, 1) Log.d("Test", "current rows: ${window.numRows}") return MyWindowedCursor(window) }
|
ContentProvider的定义:
1 2 3 4 5 6 7
| <provider android:name=".CursorProvider" android:authorities="com.wusp.cursor" android:enabled="true" android:exported="true" android:multiprocess="false" android:process=":cursor_provider" />
|
至此,就因为完成了自定义Cursor最基本的实现。只要Client调用到该ContentProvider的query()接口,就能够拿到往自定义Cursor类MyWindowedCursor中填充的CursorWindow,并将其包装在CursorWrapperInner中。
3.测试
在APP中运行下面的代码测试一下是否能正常拿到自定义Cursor类型中的内容:
1 2 3 4 5 6 7
| val uri: Uri = Uri.parse("content://com.wusp.cursor/test") val cursor = contentResolver.query(uri, null, null, null, null) Log.d("Test", "Activity cursor count: ${cursor.count}, ${cursor.columnCount}") while (cursor.moveToNext()) { Log.d("Test", "Cursor.Content: ${cursor.getString(0)} - ${cursor.getInt(1)}") } cursor.close()
|
测试结果:
1 2 3
| D/Test: Activity cursor count: 2, 2 D/Test: Cursor.Content: key1 - 1 D/Test: Cursor.Content: key2 - 2
|