深入分析ContentProvider

原文地址在博客圆,已经不用了,迁移过来。

ContentProvider是Android四大组件之一,承担着跨进程数据访问的重要职责。本文就从一次ContentProvider访问入手,分析下它是怎么完成跨进程数据访问的。

既然是跨进程,那就必须有一个客户端进程和一个ContentProvider进程,我们先从客户端进程分析,看它如何访问ContentProvider进程。以Query操作为例,一般情况下,当我们需要访问ContentProvider的时候一般都会执行这么一句:

getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);

ContentResolver是什么?Google的解释是“This class provides applications access to the content model”。实际上我们正是通过ContentResolver来获取一个IContentProvider对象,通过IContentProvider对象我们尽可以进行IPC通讯了。getContentResolver()方法定义Context类中,实际上Context是一个抽象类,在客户端应用程序中getContext()实际上返回的是一个ContextImp对象,getContentResolver()方法就定义在ContextImp.java中,并且最终返回ContextImp的内部类ApplicationContentResolver,从名字上看这是一个Application级别的对象。
ApplicationContentResolver我们稍后再说,先看下query方法都干了什么:

public final Cursor query(final Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder,
            CancellationSignal cancellationSignal) {
        IContentProvider unstableProvider = acquireUnstableProvider(uri);
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        try {
            long startTime = SystemClock.uptimeMillis();

            ICancellationSignal remoteCancellationSignal = null;
            if (cancellationSignal != null) {
                cancellationSignal.throwIfCanceled();
                remoteCancellationSignal = unstableProvider.createCancellationSignal();
                cancellationSignal.setRemote(remoteCancellationSignal);
            }
            Cursor qCursor;
            try {
                qCursor = unstableProvider.query(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(uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            }
            if (qCursor == null) {
                return null;
            }
            // force query execution
            qCursor.getCount();
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
            // Wrap the cursor object into CursorWrapperInner object
            CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
                    stableProvider != null ? stableProvider : acquireProvider(uri));
            stableProvider = null;
            return wrapper;
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        } finally {
            if (unstableProvider != null) {
                releaseUnstableProvider(unstableProvider);
            }
            if (stableProvider != null) {
                releaseProvider(stableProvider);
            }
        }
    }

上面的代码有四个关键步骤:

1.acquireUnstableProvider
2.unstableProvider.query(......)
3.qCursor.getCount();
4.return new CursorWrapperInner(......)

接下来我们一步一步分析这四个关键步骤。

第一步:acquireUnstableProvider

乍看上去感觉怪怪的,好端端的为什么加上了一个Unstable的标签?难道还有stable的不成?事实确实如此,我们知道此时的ContentResolver实际上是一个ApplicationContentResovler对象,来看下ApplicationContentResovler

private static final class ApplicationContentResolver extends ContentResolver {
        private final ActivityThread mMainThread;
        private final UserHandle mUser;

        public ApplicationContentResolver(
                Context context, ActivityThread mainThread, UserHandle user) {
            super(context);
            mMainThread = Preconditions.checkNotNull(mainThread);
            mUser = Preconditions.checkNotNull(user);
        }

        @Override
        protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context, auth, mUser.getIdentifier(), true);
        }

        @Override
        protected IContentProvider acquireExistingProvider(Context context, String auth) {
            return mMainThread.acquireExistingProvider(context, auth, mUser.getIdentifier(), true);
        }

        @Override
        public boolean releaseProvider(IContentProvider provider) {
            return mMainThread.releaseProvider(provider, true);
        }

        @Override
        protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c, auth, mUser.getIdentifier(), false);
        }

        @Override
        public boolean releaseUnstableProvider(IContentProvider icp) {
            return mMainThread.releaseProvider(icp, false);
        }

        @Override
        public void unstableProviderDied(IContentProvider icp) {
            mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
        }
    }

实际上,是否是stable的,都将调用ActivityThread的acquireProvider方法,区别就是最后的一个参数boolean stable。这个机制是API 16引入的,文章的最后会对此进行说明。现在我们只要知道它最终走到了ActivityThread的acquireProvider就可以了。在ActivityThread的acquireProvider方法中,我们首先会去acquireExistingProvider,从字面上就可以看出这是从一个类缓存的地方读取已经保存的ContentProvider对象,如果不存在,就会调用ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);从这儿开始,ActivityManagerService就要登场了,上面的代码最终会通过binder通信调用ActivityManagerService的getContentProviderImpl,这块的逻辑比较复杂,涉及到了Android组件启动的过程,我们只需知道客户端调用会阻塞在ActivityManagerNative.getDefault().getContentProvider,ActivityManagerService启动目标ContentProvider进程后(如果ContentProvider进程已经存在则不必重启),返回一个目标ContentProvider的实例。在这儿需要说明的是,ContentProvider可以再Manifest中配置一个叫做android:multiprocess的属性,默认值是false,表示ContentProvider是单例的,无论哪个客户端应用的访问都将是一个ContentProvider对象(当然,必须是同一个ContentProvider,即Uri或者Authority name是一个),如果设为true,系统会为每一个访问该ContentProvider的进程创建一个实例。因为android:multiprocess的默认值是false,所以我们在写自己的ContentProvider的时候还是要注意并发的情况。

扯得有点远了,回到我们之前步骤,我们已经得到了ContentProvider的实例,这个时候第一步就完成了,接下来看第二步。

第二步:unstableProvider.query(......)

unstableProvider实际上是IContentProvider实例,IContentProvider是进行IPC通讯的接口,这个query实际上调用的是目标ContentProvider中的query方法,当然,在真正调用目标ContentProvider的query方法之前,还需要经过enforceReadPermission方法,这一步主要是看下该ContentProvier有没有export,读写权限等等(enforceReadPermission方法只判断读权限)。随后执行query方法,并且返回一个cursor对象。

以上就是第二步的大致逻辑,不过不要以为这么简单就结束了。IContentProvider的query可是跨进程的,我们知道ContentProvider的query方法可是五花八门,有访问数据库返回SQLiteCursor的,有返回MatrixCursor的等等,那么IContentProvider返回的那个cursor到底是什么呢?我们来看下IContentProvider的这个IPC通信到底是怎么回事

IPC通信需要两端,对于我们的例子,这两端分别是ContentProviderProxy和ContentProviderNative,首先会执行ContentProviderProxy的query方法,然后通过binder通信执行ContentProviderNative的onTransact方法。ContentProviderProxy的query方法有一下五个主要步骤:

  1. new一个BulkCursorToCursorAdaptor对象——adaptor
  2. 填充data用于binder通信
  3. 调用mRemote.transact,这是一个阻塞的过程,直到ContentProviderNative的onTransact方法返回
  4. 读取reply数据,new一个BulkCursorDescriptor并以此初始化adaptor
  5. return adaptor

ContentProviderNative的onTransact会调用ContentProvider的query方法,并根据query返回的cursor初始化一个CursorToBulkCursorAdaptor对象,最终将BulkCursorDescriptor对象写入reply中。

至此我们知道了,不管我们的ContentProvider query方法返回的到底是什么样的cursor,最终在客户端进程都将会被封装在一个BulkCursorToCursorAdaptor对象中,那么这个BulkCursorToCursorAdaptor对象是不是就是我们在客户端调用query返回的最终类型呢?别急,往下看。

第三步:qCursor.getCount();

这一步看似鸡肋,实际上涉及到了SQLiteCursor的一个设计要点,那就是SQLiteCursor的内存共享。getCount会调用SQLiteCursor的fillWindow,在以后的文章中我会在讲到SQLiteCursor,在此我们只要知道它是强制执行数据库query就可以了。

第四步:return new CursorWrapperInner(......)

哈,看到了吧,我们在客户端调用query最终返回的是一个CursorWrapperInner类型,它是ContentResolver的一个内部类。实际上我们常用的getCount,onMove等一些列方法都是通过BulkCursorToCursorAdaptor和CursorToBulkCursorAdaptor的交互实现的。

到此,ContentProvider的访问流程就结束了,下面说一下开头买的坑:unstable和stable的ContentProvider。

在4.1之前,我们都可能会遇到过这样的场景,我们的应用程序访问了ContentProvider,但是这个ContentProvider意外挂了,这个时候我们的应用程序也将被连带杀死!这是Android处于对数据安全的考虑而做的决定,不过貌似Google也感觉这样的方式不太友好,所以在4.1以后提出了stable和unstable的概念。对于ContentResolver的query方法,我们将默认使用unstable的ContentProvider。看看下面的代码

Cursor qCursor;
            try {
                qCursor = unstableProvider.query(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(uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            }

上面是ContentResolver query中的一部分,可以看出unstable ContentProvider在query过程中如果发生了DeadObjectExeption则会被捕获,进而重新获取一个stable的ContentProvider。其实深入分析stable和unstable的ContentProvider还会有很多内容,以后有时间再说。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容