7.4 Cursor的query函数的实现分析
本节将分析CP中另一个比较复杂的知识点,即query函数的实现。下面从CR的query函数开始介绍,其代码如下:
[—>ContentResolver.java:query]
/*
注意query函数中的参数,它们组合后得到的SQL语句如下(方括号中的语句为笔者添加的注释)
SELECT projection指定的列名[如果projection为null,则使用“*”]
FROM表名[由目标CP根据uri参数设置]WHERE
(selection)[如果selection中有通配符,则具体参数由selectionArgs指定]
ORDERYED BY sortOrder
*/
public final Cursor query(Uri uri, String[]projection,
String selection, String[]selectionArgs, String sortOrder){
/*
根据前面的分析,下面这个函数返回的provider的真实类型是ContentProviderProxy,
对应的Bn端对象的真实类型是CP的内部类Transport。本次执行query的
目标CP是MediaProvider
*/
IContentProvider provider=acquireProvider(uri);
//来看下面的代码
try{
long startTime=SystemClock.uptimeMillis();
//①调用远端进程的query函数
Cursor qCursor=provider.query(uri, projection,
selection, selectionArgs, sortOrder);
if(qCursor==null){
//如果返回的结果为空,则释放provider,这个函数的作用在7.2.3节介绍过
releaseProvider(provider);
return null;
}
//②计算查询结果包含的数据项条数,结果保存在qCursor的内部变量中
qCursor.getCount();
long durationMillis=SystemClock.uptimeMillis()-startTime;
//③最终返回给客户端一个游标对象,其真实数据类型为CursorWrapperInner
return new CursorWrapperInner(qCursor, provider);
}
上面这段代码揭示了CR的query函数的工作流程:
调用远程CP的query函数,返回一个Cursor类型的对象qCursor。
该函数最终返给客户端的是一个CursorWrapperInner类型的对象。
Cursor和CursorWrappperInner这两个新出现的数据类型严重干扰了我们的思考。暂且不管它们,先来分析以上代码中列出的几个关键点函数。首先要分析的是第一个关键点函数query。
7.4.1 提取query关键点
1.客户端query关键点
按以前的分析习惯,碰到Binder调用时会马上转入服务端(即Bn端)去分析,但是这个思路在query函数中行不通。为什么?来看IContentProvider Bp端的query函数,它定义在ContentProviderProxy中,代码如下:
[—>ContentProviderNative.java:ContentProviderProxy.query]
public Cursor query(Uri url, String[]projection, String selection,
String[]selectionArgs, String sortOrder)throws RemoteException{
//①构造一个BulkCursorToCursorAdaptor对象
BulkCursorToCursorAdaptor adaptor=new BulkCursorToCursorAdaptor();
Parcel data=Parcel.obtain();
Parcel reply=Parcel.obtain();
try{
data.writeInterfaceToken(IContentProvider.descriptor);
……//将参数打包到data请求包中
//②adaptor.getObserver()返回一个IContentObserver类型的对象,把它
//也打包到data请求包中。ContentObserver相关的知识留到第8章再分析
data.writeStrongBinder(adaptor.getObserver().asBinder());
//发送请求给远端的Bn端
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply,0);
DatabaseUtils.readExceptionFromParcel(reply);
//③从回复包中得到一个IBulkCursor类型的对象
IBulkCursor bulkCursor=
BulkCursorNative.asInterface(reply.readStrongBinder());
if(bulkCursor!=null){
int rowCount=reply.readInt();
int idColumnPosition=reply.readInt();
boolean wantsAllOnMoveCalls=reply.readInt()!=0;
//④调用adaptor的initialize函数
adaptor.initialize(bulkCursor, rowCount,
idColumnPosition, wantsAllOnMoveCalls);
}……
return adaptor;
}……
finally{
data.recycle();
reply.recycle();
}
}
从以上代码中可发现,ContentProviderProxy query函数中还大有文章,其中一共列出了4个关键点。最令人头疼的是其中新出现的两个类BulkCursorToCursorAdaptor和IBulkCursor。此处不必急于分析它们。
我们再到服务端去提取query函数的关键点。
2.服务端query关键点
根据第2章对Binder的介绍,客户端发来的请求先在Bn端的onTransact中得到处理,代码如下:
[—>ContentProviderNative.java:onTransact]
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException{
try{
switch(code){
case QUERY_TRANSACTION://处理query请求
{
data.enforceInterface(IContentProvider.descriptor);
Uri url=Uri.CREATOR.createFromParcel(data);
……//从请求包中提取参数
//⑤创建ContentObserver Binder通信的Bp端
IContentObserver observer=IContentObserver.Stub.asInterface(
data.readStrongBinder());
//⑥调用MediaProvider实现的query函数。Cursor是一个接口类,那么变量
//cursor的真实类型是什么?
Cursor cursor=query(url, projection, selection, selectionArgs,
sortOrder);
if(cursor!=null){
//⑦创建一个CursorToBulkCursorAdaptor类型的对象
CursorToBulkCursorAdaptor adaptor=new
CursorToBulkCursorAdaptor(
cursor, observer, getProviderName());
final IBinder binder=adaptor.asBinder();
//⑧返回结果集所含记录项的条数,这个函数看起来极不起眼,但却非常关键
final int count=adaptor.count();
//返回名为"_id"的列在结果集中的索引位置,该列由数据库在创建表时自动添加
final int index=BulkCursorToCursorAdaptor.findRowIdColumnIndex(
adaptor.getColumnNames());
//wantsAllOnMoveCalls一般为false,读者阅读完本章后再自行分析它
final boolean wantsAllOnMoveCalls=
adaptor.getWantsAllOnMoveCalls();
reply.writeNoException();
//将binder的信息写到reply回复包中
reply.writeStrongBinder(binder);
reply.writeInt(count);//将结果集包含的记录项行数返回给客户端
reply.writeInt(index);
reply.writeInt(wantsAllOnMoveCalls?1:0);
}……
return true;
}……//其他情况处理
……
}
和客户端对应,服务端的query处理也比较复杂,其中的“拦路虎”仍是新出现的几种数据类型。
在扫清这些“拦路虎”之前,应先把客户端和服务端query调用的关键点总结一下。
3.提取query关键点总结
我们提取query两端的调用关键点,如图7-5所示。
图 7-5 query调用关键点示意
再来总结一下前面提到的几个“拦路虎”,它们分别是:
客户端创建的BulkCursorToCursorAdaptor,以及从服务端query后得到的IBulkCursor。
服务端创建的CursorToCursorAdaptor,以及从子类query函数返回的Cursor。
从名字上看,这几个类都和Cursor有关,所以有必要先搞清MediaProvider query返回的Cursor到底是什么。
注意 图7-5借用了UML的序列图来展示query调用顺序,其中ContentProvider(CP)框和MediaProvider框代表同一个对象。另外,图7-5中的调用函数编号并不完全对应代码中的关键函数调用编号。