您的位置:首页 > 移动开发 > Android开发

Android ContentProvider数据共享全解析

2016-11-26 11:35 429 查看
     前两天处理了那个beam分享的问题单之后,回头来想想,蓝牙进程为什么能读取到文件并分享给另一个手机呢?Android手机本身是LInux系统,每个文件都有相应的读、写、执行权限的,如果权限不符合是无法访问该文件的,而在那个问题单的处理过程当中,我们可以看到,就是使用了Uri标识文件,然后给蓝牙进程赋予读权限,最终蓝牙进程肯定就是通过ContentProvider来实现文件的读取并共享的。问题单虽然处理完了,但是对于ContentProvider还是只了解面貌,底层到底是如何实现的,我们还是不清楚,也就是考虑到这个,所以本节课我们来深究一下ContentProvider的实现原理。

     之前也读过老罗关于ContentProvider的研究的博客了,写的非常好,非常细致,大家也可以学习一下,但是还是要说明一下,只是读别人的博客,自己不研究,不跟踪代码,那么学习还是非常肤浅的,基本上就非常模糊。好了,我们这节课呢实现很简单,就是通过访问图库中的图片,来学习一下ContentProvider的实现原理。客户端很简单,就一个方法,其他的调用界面我就不写了,大家可以自己写一下:

public static void queryAllImages(Context context) {
ArrayList names = new ArrayList();
ArrayList descs = new ArrayList();
ArrayList datas = new ArrayList();
Uri uri = Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
Log.i(TAG, "query images, " + uri + ", " + uri.toString());
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(Media.DISPLAY_NAME));
String desc = cursor.getString(cursor.getColumnIndex(Media.DESCRIPTION));
byte[] data = cursor.getBlob(cursor.getColumnIndex(Media.DATA));
names.add(name);
descs.add(desc);
datas.add(Arrays.toString(data));
mDatas.add(data);
}
Log.i(TAG, "names = " + names);
Log.i(TAG, "datas = " + datas);
}

     我在这个方法和系统层framework当中都加了一些日志打印,从输出的日志当中,我们可以看到最后获取回来的blob数据是一个byte[]数组,存储的全部是十进制的整数,这些数组中的内容代码什么呢?



     
     我们可以把这些十进制的数据先转换为十六进制,然后再将十六进制的数据转换成字符串,就可以看到它到底是什么内容了。





     从转换成的字符串结果当中,很清楚的可以看到,它正是我们当前取回来的图片文件在手机上的存储路径。 

     这个方法当中的目的也非常明确,就是通过Resolver来查询图库中Media.EXTERNAL_CONTENT_URI匹配的所有的图片,然后在返回的结果中把数据读出来,看一下返回来的数据是什么,这样就完成了。那么根据这样的目的,我们也就把这节课分为三个步骤来讲:1、context.getContentResolver().query(uri, null, null, null, null)调用完成,返回一个Cursor对象;2、cursor.moveToNext()到底干了什么?;3、cursor.getBlob(cursor.getColumnIndex(Media.DATA))是如何把数据取回来的。后面两个步骤大家可能有疑问,这两步也要单独拿出来分析?先不着急,大家细心看完就知道了,其实这两步是非常重要的,数据的获取就是通过这两步来完成的。
     1、context.getContentResolver().query(uri, null, null, null, null)返回Cursor对象

     context.getContentResolver()是由ContextImpl实现的,它返回的就是成员变量mContentResolver,而mContentResolver是一个ApplicationContentResolver对象,是在ContextImpl定义的一个内部类,而query方法是由父类ContentResolver来实现的,注意query方法的修饰符为final,即此方法不允许子类自己实现,那么我们就来看一下query方法的实现:

public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}

/**
* Query the given URI, returning a {@link Cursor} over the result set
* with optional support for cancellation.
*

* For best performance, the caller should follow these guidelines:
*

*
Provide an explicit projection, to prevent
* reading data from storage that aren't going to be used.
*
Use question mark parameter markers such as 'phone=?' instead of
* explicit values in the {@code selection} parameter, so that queries
* that differ only by those values will be recognized as the same
* for caching purposes.
*

*
*
* @param uri The URI, using the content:// scheme, for the content to
* retrieve.
* @param projection A list of which columns to return. Passing null will
* return all columns, which is inefficient.
* @param selection A filter declaring which rows to return, formatted as an
* SQL WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URI.
* @param selectionArgs You may include ?s in selection, which will be
* replaced by the values from selectionArgs, in the order that they
* appear in the selection. The values will be bound as Strings.
* @param sortOrder How to order the rows, formatted as an SQL ORDER BY
* clause (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
* @return A Cursor object, which is positioned before the first entry, or null
* @see Cursor
*/
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();

ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let's try!!!!
// This is exciting!!1!!1!!!!1
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}

// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);

// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
if (qCursor != null) {
qCursor.close();
}
if (cancellationSignal != null) {
cancellationSignal.setRemote(null);
}
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
if (stableProvider != null) {
releaseProvider(stableProvider);
}
}
}
     1.1、acquireUnstableProvider(uri)获取一个IContentProvider对象

     调用acquireUnstableProvider(uri)来获取一个Provider实例,获取的时候会先检查uri.getScheme(),如果Scheme字段不是content,则直接返回空了,Scheme字段正确后,然后获取uri.getAuthority(),这也就是我们自己实现一个ContentProvider时定义的Authority了,注意,这个字段必须是唯一的,是在一个手机上唯一的,如果有定义了两个Authority相同的ContentProvider,那么,后面那个apk是无法安装到手机上的,大家可以试一下。acquireUnstableProvider方法最终也是根据Authority字段来查询我们的目标Provider的。这个过程我们就不展开了,大体的步骤就是先在ActivityThread当中调用acquireExistingProvider去缓存当中查,查到了就直接返回;如果缓存中没有查到,那么就继续去AMS当中查,AMS当中也是先在缓存中查,缓存有,则直接返回给当前调用进程;如果缓存也没有,那就说明提供目标ContentProvider的进程还未启动,那么就先启动目标进程,启动完成后,将目标进程提供的该ContentProvider缓存起来,同时返回给当前调用进程。这个过程,大家可以参考老罗的博客:

     Android应用程序组件Content Provider的启动过程源代码分析

     我们这里呢,从第一张图的日志输出看到,对应Media.EXTERNAL_CONTENT_URI的ContentProvider是com.android.providers.media/.MediaProvider对象,而在调用方拿到的只是一个代理对象,对应的服务端实际上就是定义在ContentProvider的内部类Transport了。

     1.2、unstableProvider.query执行查询
     unstableProvider是一个IContentProvider对象,它的query方法是由ContentProviderProxy来实现的,我们来看一下它的实现:

public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
throws RemoteException {
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);

data.writeString(callingPkg);
url.writeToParcel(data, 0);
int length = 0;
if (projection != null) {
length = projection.length;
}
data.writeInt(length);
for (int i = 0; i < length; i++) {
data.writeString(projection[i]);
}
data.writeString(selection);
if (selectionArgs != null) {
length = selectionArgs.length;
} else {
length = 0;
}
data.writeInt(length);
for (int i = 0; i < length; i++) {
data.writeString(selectionArgs[i]);
}
data.writeString(sortOrder);
data.writeStrongBinder(adaptor.getObserver().asBinder());
data.writeStrongBinder(cancellationSignal != null ? cancellationSignal.asBinder() : null);

mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);

DatabaseUtils.readExceptionFromParcel(reply);

if (reply.readInt() != 0) {
BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply);
adaptor.initialize(d);
} else {
adaptor.close();
adaptor = null;
}
return adaptor;
} catch (RemoteException ex) {
adaptor.close();
throw ex;
} catch (RuntimeException ex) {
adaptor.close();
throw ex;
} finally {
data.recycle();
reply.recycle();
}
}

     这个方法当中先创建了一个BulkCursorToCursorAdaptor对象,这个adaptor也正是我们要返回给上层调用者的。binder通信在执行QUERY_TRANSACTION传信时,如果查询成功,则会调用reply.writeInt(1)写入1,如果失败则reply.writeInt(0)写入0,用这个int值来区分查询是否成功。这里我们假设查询是成功的,那么就会执行BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply)、adaptor.initialize(d),完成后,将adapter返回给调用层。关于这块的详细执行,大家也可以看老罗的博客:
   
Android应用程序组件Content Provider在应用程序之间共享数据的原理分析
     我们还是按照代码执行流程先来看一下mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0)的执行过程,然后再回来看一下query方法中后面的逻辑。mRemote.transact()调用之后,就通过binder进程间通信到ContentProviderNative类的onTransact方法当中了,当前的code就是QUERY_TRANSACTION,在这个分支当中,主要的事情就是:1、将调用方的参数通过binder传递过来的data中取出来,然后用这些参数调用query方法,完成后会得到一个Cursor
cursor对象;2、以这个cursor对象为参数,构造一个CursorToBulkCursorAdaptor,然后再调用BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor(),将需要的数据都封装在d当中,最后通过binder把数据返回到调用方进程当中。这里需要提醒大家一下,执行到这里,已经是在ContentProvider的服务端了,相当于正在服务端查数据。我们先来看一下服务端query方法的实现,它的实现正是由我们1.1当中Transport来实现的,那我们就来看一下Transport类的query方法的实现:

@Override
public Cursor query(String callingPkg, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
validateIncomingUri(uri);
Log.w(TAG, "execute query, " + callingPkg + ", " + uri);
uri = getUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
// The caller has no access to the data, so return an empty cursor with
// the columns in the requested order. The caller may ask for an invalid
// column and we would not catch that but this is not a problem in practice.
// We do not call ContentProvider#query with a modified where clause since
// the implementation is not guaranteed to be backed by a SQL database, hence
// it may not handle properly the tautology where clause we would have created.
if (projection != null) {
return new MatrixCursor(projection, 0);
}

// Null projection means all columns but we have no idea which they are.
// However, the caller may be expecting to access them my index. Hence,
// we have to execute the query as if allowed to get a cursor with the
// columns. We then use the column names to return an empty cursor.
Cursor cursor = ContentProvider.this.query(uri, projection, selection,
selectionArgs, sortOrder, CancellationSignal.fromTransport(
cancellationSignal));
if (cursor == null) {
return null;
}

// Return an empty cursor for all columns.
return new MatrixCursor(cursor.getColumnNames(), 0);
}
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.query(
uri, projection, selection, selectionArgs, sortOrder,
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingPackage(original);
}
}

     在这里一般我们的调用方都是有read权限的,if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED)判断为false,那么就是直接调用最后的ContentProvider.this.query(),查询完成的结果就直接返回了,这里的this就是我们在1.1当中日志中看到的真正执行查询方法的MediaProvider了。那么接下来,我们就来看一下MediaProvider类的query方法是如何实现的。这个query方法非常长,我们这里只贴出关键代码,其他的就省略了:

public Cursor query(Uri uri, String[] projectionIn, String selection,
String[] selectionArgs, String sort) {

uri = safeUncanonicalize(uri);

int table = URI_MATCHER.match(uri);
List prependArgs = new ArrayList();

// MtkLog.v(TAG, "query: uri="+uri+", selection="+selection);
// handle MEDIA_SCANNER before calling getDatabaseForUri()
………………………………………………………………

String groupBy = null;
DatabaseHelper helper = getDatabaseForUri(uri);
if (helper == null) {
return null;
}
Log.i(TAG, "database name is " + helper.getDatabaseName());
helper.mNumQueries++;
SQLiteDatabase db = helper.getReadableDatabase();
if (db == null) return null;
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String limit = uri.getQueryParameter("limit");
String filter = uri.getQueryParameter("filter");
String [] keywords = null;
if (filter != null) {
filter = Uri.decode(filter).trim();
if (!TextUtils.isEmpty(filter)) {
String [] searchWords = filter.split(" ");
keywords = new String[searchWords.length];
for (int i = 0; i < searchWords.length; i++) {
String key = MediaStore.Audio.keyFor(searchWords[i]);
key = key.replace("\\", "\\\\");
key = key.replace("%", "\\%");
key = key.replace("_", "\\_");
keywords[i] = key;
}
}
}
if (uri.getQueryParameter("distinct") != null) {
qb.setDistinct(true);
}

boolean hasThumbnailId = false;
boolean permitedAccessDrm = DrmHelper.isPermitedAccessDrm(getContext(), Binder.getCallingPid());
switch (table) {
case IMAGES_MEDIA:
qb.setTables("images");
if (uri.getQueryParameter("distinct") != null)
qb.setDistinct(true);
if (!permitedAccessDrm) {
qb.appendWhere(NO_DRM_CLAUSE);
}
// set the project map so that data dir is prepended to _data.
//qb.setProjectionMap(mImagesProjectionMap, true);
break;
…………………………………………………………
}

// MtkLog.v(TAG, "query = "+ qb.buildQuery(projectionIn, selection,
//        combine(prependArgs, selectionArgs), groupBy, null, sort, limit));
Cursor c = null;
try {
c = qb.query(db, projectionIn, selection,
combine(prependArgs, selectionArgs), groupBy, null, sort, limit);
} catch (IllegalStateException e) {
MtkLog.e(TAG, "query: IllegalStateException! uri=" + uri, e);
}
if (true) {
MtkLog.d(TAG, "query: uri = " + uri + ", projection = " + Arrays.toString(projectionIn)
+ ", selection = " + selection + ", selectionArgs = " + Arrays.toString(selectionArgs)
+ ", sort = " + sort + ", caller pid = " + Binder.getCallingPid());
}

if (c != null) {
String nonotify = uri.getQueryParameter("nonotify");
if (nonotify == null || !nonotify.equals("1")) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
}

return c;
}

     在这个方法当中,我们可以看到好多匹配,有图库、音频、版本等等各种信息,在MediaProvider类的开始,我们也可以看到,定义了两个数据库:private static final String INTERNAL_DATABASE_NAME = "internal.db"、private static final String EXTERNAL_DATABASE_NAME = "external.db",我们的图片肯定就是保存在external.db数据库中了。不过保存的只是索引,可不是图片信息,一定要理解清楚哈。我们这里要查询的表就是images了,查询工作是调用SQLiteQueryBuilder类的query方法来完成的。SQLiteQueryBuilder类的query方法我们就不贴代码了,主要作了两件事:1、将外部传入的参数组合起来,构造好一条sql语句;2、调用SQLiteDatabase类的rawQueryWithFactory方法继续执行查询。SQLiteDatabase类的rawQueryWithFactory方法也是两件事:1、先构造一个SQLiteDirectCursorDriver对象;2、调用SQLiteDirectCursorDriver的query方法执行查询。SQLiteDirectCursorDriver类的整体代码如下:

public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
private final SQLiteDatabase mDatabase;
private final String mEditTable;
private final String mSql;
private final CancellationSignal mCancellationSignal;
private SQLiteQuery mQuery;

public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
CancellationSignal cancellationSignal) {
mDatabase = db;
mEditTable = editTable;
mSql = sql;
mCancellationSignal = cancellationSignal;
}

public Cursor query(CursorFactory factory, String[] selectionArgs) {
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs);

if (factory == null) {
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
}

mQuery = query;
return cursor;
}

public void cursorClosed() {
// Do nothing
}

public void setBindArguments(String[] bindArgs) {
mQuery.bindAllArgsAsStrings(bindArgs);
}

public void cursorDeactivated() {
// Do nothing
}

public void cursorRequeried(Cursor cursor) {
// Do nothing
}

@Override
public String toString() {
return "SQLiteDirectCursorDriver: " + mSql;
}
}

     可以看到它的构造方法中没有什么实质性的动作,只是给成员变量赋值,query方法中,构造了一个SQLiteQuery对象,然后以它为参数,构造一个SQLiteCursor,最终返回的就是这个SQLiteCursor了。注意这里的参数factory,它是SQLiteQueryBuilder类的成员变量,在整个过程中,我们没有指定factory,所以此处它为空。SQLiteCursor的构造函数中也没有什么实质性的工作,这里我们就不展开了。
     我们可以看到,系统中管理所有图片都是在MediaProvider当中,它的代码在packages/providers/MediaProvider目录下,查看manifest文件,我们就可以看到它的包名。



     有了包名,我们看一下它在手机当中的数据信息的位置,使用adb shell dumpsys package "com.android.providers.media"命令。



     这里会显示当前进程的好多信息,那么我们就是想看一下它用来存储数据的数据库到底是什么样的,我们进入它的data目录,把它的进程保存的数据全部pull出来看一下。



     这里呢,我把当前MediaProvider进程下位置data/data/com.android.providers.media目录下的所有文件夹全部pull出来了,可以看到它和我们普通进程的数据存储目录结构是相同的。



     好了,兴奋的时刻来了,我们使用SQLiteSpy来打开它的external.db数据库文件,我靠,无语了,数据库是加密的。



     从以上的代码分析,我们就可以得知,在调用Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)返回的结果cursor中其实还没有任何数据,它只是准备好了一个Cursor对象,我们可以加上代码来验证一下我们猜想,比如我们先不调用cursor.moveToNext(),就直接取它当中的blob数据,能不能取出来呢?



     可以从我们的验证看到,当前只是返回的cursor对象,它的mPos才刚初始化,当前值为-1,所以我们这时候取值,就直接异常崩溃了。
     回到我们的主题,那么到这,只是在MediaProvider对象的query方法执行完了,返回到上一层Transport当中,这里也是直接返回了,那么再往回一层,回到ContentProviderNative类的onTransact方法的case QUERY_TRANSACTION分支,返回回来的cursor不为空,而且它是一个SQLiteCursor对象,在这个方法当中,就用这个cursor对象构造一个CursorToBulkCursorAdaptor对象,它当中就是给一些成员变量赋值,然后调用createAndRegisterObserverProxyLocked将我们要监听的Observer对象注册进来,这些过程我们就不深究了,其中的注册Observer会在数据变化时通知我们。创建好CursorToBulkCursorAdaptor对象之后,然后调用adaptor.getBulkCursorDescriptor()获取一个BulkCursorDescriptor对象,最后把这个对象写入的binder返回的数据当中,再写入1表示cursor创建成功,到这里Provider服务端的工作就完成了。那么再往上返回一层,就到了ContentProviderProxy类的query方法的mRemote.transact()调用,这里的(reply.readInt()
!= 0)就为true了,那么就利用reply中的数据构造一个BulkCursorDescriptor,然后调用adaptor.initialize(d)就完成了,最后把这个adapter返回给上一层。再往上一层就是ContentResolve类的query方法中的unstableProvider.query()调用了。到这里呢,大家可以看一下系统层所作的工作,整个query过程,只是根据我们传入的参数在服务端构造了一个SQLiteCursor,但是还没有进行任何实质性的查询工作,同时通过binder进程间通信的服务端返回给Provider客户端的是一个CursorToBulkCursorAdaptor对象,Provider客户端在收到binder通信的返回数据后,构造了一个BulkCursorToCursorAdaptor,这两个对象是通过BulkCursorDescriptor联系起来的,在服务端调用adaptor.getBulkCursorDescriptor()把数据准备好,然后写入到binder的返回数据reply中;客户端就直接从reply中调用BulkCursorDescriptor.CREATOR.createFromParcel(reply)把数据取回来,然后进一步封装,这些细节一定要搞清楚。

     1.3、qCursor.getCount()
     从这句代码的备注“Force query execution.  Might fail and throw a runtime exception here.”上,我们先来理解一下,也就是系统强制先执行一次查询,其实大家看一下它的实现就知道了,这里相当于只是进行一下数据检查,它的实现非常简单,就是检查mBulkCursor成员变量是否为空,在1.2步骤的最后,已经调用adaptor.initialize(d)把binder返回给我们的数据保存下来了,所以这里的mBulkCursor也就不为空了。
     1.4、以1.2的结果为参数调用new CursorWrapperInner(),最终返回给调用者
     通过了1.3的关口检查,那么最后就是把数据封装成一个CursorWrapperInner最终返回给我们客户端了,到这句执行完成后,相当于我们在应用层中调用getContentResolver().query()方法才完成,作了这么多工作,才只完成了一句代码的逻辑,大家从这里也可以看到,ContentProvider系统是多么的复杂!!好了,继续我们的流程,CursorWrapperInner的构造方法也比较简单,就是把传入的参数保存在成员变量当中,就完成了。
     2、cursor.moveToNext()到底干了什么?
     现在我们该执行第二步了,这里的cursor就是应用层拿到的cursor了,是一个CursorWrapperInner对象,moveToNext()是由它的父类CursorWrapper来实现的。它当中是直接调用mCursor.moveToNext()来处理的,这里的成员变量cursor也就是在1.4步骤时构造CursorWrapperInner传入的第一个参数了,实际就是binder通信完成,在客户端自己构造的一个BulkCursorToCursorAdaptor对象,它的moveToNext()也是调用父类AbstractCursor来实现的,这里的实现非常简单,就是调用moveToPosition(mPos
+ 1),注意,mPos还进行任何赋值,所以此时它的值是-1,moveToPosition方法当中就作了两件事:1、计算mPos的位置,然后赋值;2、回调子类的onMove。这里的子类当然就是BulkCursorToCursorAdaptor了,我们来看一下它的onMove方法的实现:

@Override
public boolean onMove(int oldPosition, int newPosition) {
throwIfCursorIsClosed();

try {
// Make sure we have the proper window
if (mWindow == null
|| newPosition < mWindow.getStartPosition()
|| newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) {
setWindow(mBulkCursor.getWindow(newPosition));
} else if (mWantsAllOnMoveCalls) {
mBulkCursor.onMove(newPosition);
}
} catch (RemoteException ex) {
// We tried to get a window and failed
Log.e(TAG, "Unable to get window because the remote process is dead");
return false;
}

// Couldn't obtain a window, something is wrong
if (mWindow == null) {
return false;
}

return true;
}

     mWindow是从父类AbstractWindowedCursor继承下来的,它是一个重量级的对象,大家从后边的分析中就可以感觉到。在前面的query过程当中,因为还没有执行任何实质性的查询,所以此时第一次执行时,mWindow是为空的,那么就执行if分支,调用mBulkCursor.getWindow(newPosition)获取服务端的window对象,然后调用setWindow赋值给它的成员变量。mBulkCursor是从binder通信的服务端返回过来的一个CursorToBulkCursorAdaptor对象。在中间的查询过程,涉及到的各种对象太多了,大家如果没有自己分析源码,这时候肯定都已经乱了。
     在这里我们重点说一下,在Provider通信过程中,binder通信直接对应的两端的对象:客户端是BulkCursorToCursorAdaptor,服务端是CursorToBulkCursorAdaptor。
     往外退一层,应用层访问的Cursor实质是封装的一个CursorWrapperInner对象,服务端实际构造好的是一个SQLiteCursor对象。
     好了,稍微理一下,我们继续,那么就通过调用CursorToBulkCursorAdaptor对象的getWindow方法去获取一个Window,我们来看一下这个方法的实现:

@Override
public CursorWindow getWindow(int position) {
synchronized (mLock) {
throwIfCursorIsClosed();

if (!mCursor.moveToPosition(position)) {
closeFilledWindowLocked();
return null;
}

CursorWindow window = mCursor.getWindow();
if (window != null) {
closeFilledWindowLocked();
} else {
window = mFilledWindow;
if (window == null) {
mFilledWindow = new CursorWindow(mProviderName);
window = mFilledWindow;
} else if (position < window.getStartPosition()
|| position >= window.getStartPosition() + window.getNumRows()) {
window.clear();
}
mCursor.fillWindow(position, window);
}

if (window != null) {
// Acquire a reference to the window because its reference count will be
// decremented when it is returned as part of the binder call reply parcel.
window.acquireReference();
}
return window;
}
}

     这个方法当中的mCursor定义为CrossProcessCursor,是一个接口,本质就是我们上面说的服务端往外退一层的SQLiteCursor了,那么第一次调用时,获取到的window肯定是空的了,就构造一个CursorWindow对象,然后赋值给成员变量。在这个方法当中,非常重要的两步:1、调用mFilledWindow = new CursorWindow(mProviderName)构造一个window;2、mCursor.fillWindow(position,
window)。
     我们先来看一下CursorWindow对象的构造过程。它的构造方法中,给mStartPos赋值为0,表示起始位置,然后调用nativeCreate方法在native层创建一个CursorWindow,它是和Java层对应的,最后调用recordNewWindow将window在native层的对象缓存起来。在调用nativeCreate(mName, sCursorWindowSize)时,传入的第二个参数sCursorWindowSize就是指的我们要创建的匿名共享内存的大小了,它是定义如下:

public class CursorWindow extends SQLiteClosable implements Parcelable {
private static final String STATS_TAG = "CursorWindowStats";

/** The cursor window size. resource xml file specifies the value in kB.
* convert it to bytes here by multiplying with 1024.
*/
private static final int sCursorWindowSize =
Resources.getSystem().getInteger(
com.android.internal.R.integer.config_cursorWindowSize) * 1024;

     config_cursorWindowSize的定义是在frameworks/base/core/res/res/values/config.xml当中:

2048

     2048*1024,也就是说我们能创建的匿名共享内存的上限就是2M。我们来看一下native层的CursorWindow的构造方法:

status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
String8 ashmemName("CursorWindow: ");
ashmemName.append(name);

status_t result;
int ashmemFd = ashmem_create_region(ashmemName.string(), size);
if (ashmemFd < 0) {
result = -errno;
} else {
result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
if (result >= 0) {
void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
if (data == MAP_FAILED) {
result = -errno;
} else {
result = ashmem_set_prot_region(ashmemFd, PROT_READ);
if (result >= 0) {
CursorWindow* window = new CursorWindow(name, ashmemFd,
data, size, false /*readOnly*/);
result = window->clear();
if (!result) {
LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
"numRows=%d, numColumns=%d, mSize=%d, mData=%p",
window->mHeader->freeOffset,
window->mHeader->numRows,
window->mHeader->numColumns,
window->mSize, window->mData);
*outCursorWindow = window;
return OK;
}
delete window;
}
}
::munmap(data, size);
}
::close(ashmemFd);
}
*outCursorWindow = NULL;
return result;
}

     在这里的代码,大家就非常清楚的看到我们Provider跨进程传递数据的真谛了,就是ashmem_create_region系统调用创建了一个匿名共享内存,然后通过它来实现数据的跨进程传递的。如果大家想学习匿名共享内存的相关知识,可以参考老罗的博客:
     Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
     Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

     Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

     老罗的博客写的非常细致,强力给大家推荐!!

     创建好了native层的匿名共享内存后,然后调用mCursor.fillWindow(position, window),这里的mCursor就是SQLiteCursor对象,但是它没有重写带两个参数的fillWindow方法,是调用父类AbstractCursor,而父类的实现中也非常简单,就是直接调用DatabaseUtils.cursorFillWindow(this, position, window)来处理的,我们来看一下它的实现:

public static void cursorFillWindow(final Cursor cursor,
int position, final CursorWindow window) {
if (position < 0 || position >= cursor.getCount()) {
return;
}
final int oldPos = cursor.getPosition();
final int numColumns = cursor.getColumnCount();
window.clear();
window.setStartPosition(position);
window.setNumColumns(numColumns);
if (cursor.moveToPosition(position)) {
do {
if (!window.allocRow()) {
break;
}
for (int i = 0; i < numColumns; i++) {
final int type = cursor.getType(i);
final boolean success;
switch (type) {
case Cursor.FIELD_TYPE_NULL:
success = window.putNull(position, i);
break;

case Cursor.FIELD_TYPE_INTEGER:
success = window.putLong(cursor.getLong(i), position, i);
break;

case Cursor.FIELD_TYPE_FLOAT:
success = window.putDouble(cursor.getDouble(i), position, i);
break;

case Cursor.FIELD_TYPE_BLOB: {
final byte[] value = cursor.getBlob(i);
success = value != null ? window.putBlob(value, position, i)
: window.putNull(position, i);
break;
}

default: // assume value is convertible to String
case Cursor.FIELD_TYPE_STRING: {
final String value = cursor.getString(i);
success = value != null ? window.putString(value, position, i)
: window.putNull(position, i);
break;
}
}
if (!success) {
window.freeLastRow();
break;
}
}
position += 1;
} while (cursor.moveToNext());
}
cursor.moveToPosition(oldPos);
}

     分析这个方法之前,我们要先明白传进来的参数,第一个cursor就是SQLiteCursor,position是0,因为我们当前是第一次调用,window就是我们才刚刚创建好的CursorWindow对象了。接下来看一下这个方法的实现,也就是数据库操作中的本质的东西了,查到的数据都会通过调用window.putLong()、window.putDouble()、window.putBlob()、window.putString()填充到window对象上,其他剩下的逻辑我们就不展开了,大家自己有兴趣的,可以分析一下。

     上面的fillWindow方法执行完成后,最终就把window对象返回到客户端BulkCursorToCursorAdaptor对象当中了,moveToNext的逻辑也就执行完了。

     3、cursor.getBlob(cursor.getColumnIndex(Media.DATA))

     这一步就是直接调用服务端CursorWindow对象查询完成后填充好的数据了,中间的过程我们就不看了,它的最终实现是在android_database_CursorWindow.cpp当中的nativeGetBlob方法,我们来看一下它的代码:

static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast(windowPtr);
LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);

CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return NULL;
}

int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
size_t size;
const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
jbyteArray byteArray = env->NewByteArray(size);
if (!byteArray) {
env->ExceptionClear();
throw_sqlite3_exception(env, "Native could not create new byte[]");
return NULL;
}
env->SetByteArrayRegion(byteArray, 0, size, static_cast(value));
return byteArray;
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
// do nothing
} else {
throwUnknownTypeException(env, type);
}
return NULL;
}

     在这里,我想看一下它里边的数据,就自己加了一些日志输出,但是代码全部加在这个文件当中,又怕后边忘了不好找,我们就自己写个类,专门来完成我们自己的意图。为了方便查看C++中的内容,我们自己在系统的jni目录下加一个文件,专门添加我们自己的代码,防止和framework当中的代码有干扰,好,我们现在在frameworks/base/core/jni目录下加一个头文件和一个源文件,分别命名为a_leui.h、a_leui.cpp,先加一个打印jbyteArray的方法,代码非常简单:



     好,添加完成后,我们在jni目录下mm,然后打编译成功的so文件替换掉手机中当前的so库,然后再次运行并打印日志。



     这里呢需要说一下,我们搞应用层的,对C++了解非常少,当然也有很多同事经常用到JNI,那就非常熟悉了,这里的日志打印当中的%s、%d、%p等等是个格式化输出符,在网上找到了一个非常详细的总结的博客,大家如果对这个不是很了解,可以学习一下:

     printf 格式化输出符号详细说明

      好了,这里也只是简单的引入一下,只要有这个点,就可以加很多逻辑了。那么我们这里反过来,在2步骤的最后,是通过DatabaseUtils.cursorFillWindow方法来把数据填充进去的,那我们就来看一下它是怎么填充的,取出来的过程也就理解了。我们要取的blob数据,最终是调用android_database_CursorWindow.cpp当中的nativePutBlob方法来完成的,我们来看一下它的实现:

static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
jbyteArray valueObj, jint row, jint column) {
CursorWindow* window = reinterpret_cast(windowPtr);
jsize len = env->GetArrayLength(valueObj);

void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
status_t status = window->putBlob(row, column, value, len);
env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);

if (status) {
LOG_WINDOW("Failed to put blob. error=%d", status);
return false;
}

LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
return true;
}

     这里的重点就是调用native层中对应的CursorWindow类的putBlob方法来填充数据了,putBlob方法很简单,是直接调用putBlobOrString来完成的,putBlobOrString方法的实现如下:

status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
const void* value, size_t size, int32_t type) {
if (mReadOnly) {
return INVALID_OPERATION;
}

FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}

uint32_t offset = alloc(size);
if (!offset) {
return NO_MEMORY;
}

memcpy(offsetToPtr(offset), value, size);

fieldSlot->type = type;
fieldSlot->data.buffer.offset = offset;
fieldSlot->data.buffer.size = size;
return OK;
}

     在CursorWindow内部还使用FieldSlot、RowSlot来组织管理数据的,因为本人C++基础薄弱,而且Linux系统基本上不懂,所以再深的就不展开了。有哪位清楚的话,也请指点我一下。

    这节课也就到这里了,从整个过程当中,我们可以看到,底层的实现非常的复杂,尤其是在第一步的时候,一句query调用,底层为我们作了大量的工作。也正是这种庞大的机制才能最终保证了我们使用ContentProvider数据的简易和方便!!

     好了,同学们,下课!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息