前两天处理了那个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数据的简易和方便!!

     好了,同学们,下课!!

更多相关文章

  1. [置顶] Android中调用系统相机、系统相册来获取图片,并裁剪图片。
  2. # Android的按键消息分发机制
  3. Android中微信支付的调用方法
  4. android 浏览器 app层,framework层,webkit层消息的交互
  5. android导入外部数据库到项目中的使用方法
  6. 在移动开发中,关于发送消息及解析消息响应的一点方法
  7. Android消息循环机制分析
  8. android保存数据的四种方法
  9. Android判断设备网络连接状态及判断连接方式的方法

随机推荐

  1. Android几种消息推送方案总结
  2. Android中滑屏初探 - scrollTo 以及 scro
  3. Android 调用第三方so中方法记录
  4. android 的Android Media Scanner多媒体
  5. [Android] Android自定义对话框(Dialog)
  6. Android socket 编程 实现消息推送(二)
  7. 【实习周记】Android中ProtoBuf的使用与
  8. android之ImageView
  9. 【Android FFMPEG 开发】Android Studio
  10. Android Afianl框架(序)