Android(安卓)ContentProvider数据共享全解析
前两天处理了那个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. *
调用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数据的简易和方便!!
好了,同学们,下课!!
更多相关文章
- [置顶] Android中调用系统相机、系统相册来获取图片,并裁剪图片。
- # Android的按键消息分发机制
- Android中微信支付的调用方法
- android 浏览器 app层,framework层,webkit层消息的交互
- android导入外部数据库到项目中的使用方法
- 在移动开发中,关于发送消息及解析消息响应的一点方法
- Android消息循环机制分析
- android保存数据的四种方法
- Android判断设备网络连接状态及判断连接方式的方法