ContentProvider启动流程分析(一)

0x01 扯东扯西的前言&概述


本片博客对应时序图上的step1—5下接第二篇ContentProvider启动流程分析二!

同时此系列博客同步在博客园发布:ContentProvider启动流程分析系列!详情戳这里即可访问~

作为安卓设计的四大组件之一,是跨进程共享数据的一把利器,所谓跨进程共享数据,通俗理解就是,应用程序A可以访问操作应用程序B共享出来的数据,这些共享出来的数据一般都有其对应的URI(统一资源标识符),那么就涉及到两个过程:

  1. 提供数据内容的过程:
    • A应用(比如系统联系人,日历)如果希望共享自己的数据内容(比如联系人列表,日历信息),就需要通过子类重写ContentProvider六个方法来实现,ContentProvider的六个方法的共性特征是,都接受一个URI参数,通过解析URI参数匹配相应的数据内容并将其返回;
    • 当一个跨进程访问数据的请求(包含RUI参数)发出后,系统首先会校验URI权限(authority),权限校验通过后,把这个访问请求传递到对应进程(具体来说是一个应用程序)的ContentProvider组件,ContetnProvider接收到URI参数后,会解析URI路径(path),然后根据路径解析结果,提供相应的数据内容并将其返回;
    • 此过程在A应用程序内部实现,当A应用程序封装好了ContentProvider实现类,需要在Mainfest清单文件中进行注册,至此,A应用程序所在的进程已经提供了访问自己所在进程数据的接口,B应用程序只需要根据数据内容的URI,发出访问请求即可;
  2. 发出访问数据请求的过程:
    • B应用程序如果希望跨进程访问A应用程序共享出来的数据,需要调用Context#getContentResolver()#query()|update()|insert()|delete(),无非就是对数据内容进行增删改查操作,涉及到一个类ContentResolver,具体调用的是ContentResolver的增删改查方法;与SQLite数据库的增删改查不同,ContentResolver的增删改查方法需要接受一个URI参数,这个URI参数就是希望访问的数据内容的URI;
    • 此过程在B应用程序内部实现,通过在B进程访问A进程的私有数据,完成跨进程共享数据的过程!

模拟一个跨进程请求数据的场景:A应用程序(AxxApp)在MainActivity中向B应用程序(BxxApp,也即系统自带的联系人应用)的联系人数据发起跨进程的访问请求。B应用程序中,SubContentProvider类继承ContentProvider组件类,并重写了ContentProvider的六个成员函数。

根据以上场景,当A应用程序发出访问请求,请求携带系统联系人数据URI,系统联系人数据对应的URI值是 ContactsContract.CommonDataKinds.Phone.CONTENT_URI,当访问请求发出后,系统会根据对应的URI,启动B应用程序中ContentProvider组件SubContentProvider,并把联系人数据返回给A应用程序。那么B程序的SubContentProvider组件是经过哪些调用,一步一步被启动的呢?请看时序图如下:

ContentProvider启动时序图

0x02ContentProvider启动流程分析


再来结合源码分步梳理一遍详细经过,对应时序图的step1-->step5,过程如下:

时序图step1 --> Context#getContentResolver()

在A程序进程的MainActivity中调用getContentResolver函数,根据MainActivity的多重继承关系,MainActivity继承了Activity,而Activity又继承了ContextWrapper,所以我们可以发现,因为当前Activity持有Context的引用,所以实际上调用的是ContextWrapper.getContentProvider()函数来获得ContentResolver对象,记作resolver变量。

ContextWrapper类的成员函数getContentResolver()源码如下:

public class ContextWrapper extends Context {
    Context mBase;
    ....
    @Override
    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }
    ....
}

可以看到ContextWrapper类的成员变量mBase指向了一个ContextImpl对象,它是在MainActivity组件启动时创建的,因此mBase.getContentResolver()实际上的调用是ContextImpl.getContentResolver()函数来获得一个ContentResolver对象resolver的。

ContextImpl类的成员函数getContentResolver()源码如下:

class ReceiverRestrictedContext extends ContextWrapper {
    ....
    private final ApplicationContentResolver mContentResolver;
    ....
    /*构造函数*/
    private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
        ....
        /*创建ApplicationContentResolver对象*/
        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
    }
    
    /*返回ContentResolver对象*/
    @Override
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }
}
  • ContextImpl类的成员变量mContentResolver,是一个ApplicationContentResolver对象,在构造方法中被创建后,直接在getContentResolver()函数中简单的被返回给调用者。
  • 也即,MainActivity中getContentResolver()函数,最终获得的是一个ApplicationContentResolver对象;
  • 接着就调用这个ApplicationContentResolver对象的acquireProvider()函数获取BxxApp应用程序的SubContentProvider组件的一个代理对象;
  • 也就是获得与ContactsContract.CommonDataKinds.Phone.CONTENT_URI联系人数据URI对应的一个ContentProvider组件对象。

时序图step2,3 --> ContentResolver#acquireProvider()

ApplicationContentResolver是ContentResolver的实现类,它重写了父类的acquireProvider()函数,所以实际上调用的是ContentResolver子类ApplicationContentResolver的成员函数acquireProvider(),ApplicationContentResolver#acquireProvider()源码如下:

private static final class ApplicationContentResolver extends ContentResolver {
    private final ActivityThread mMainThread;
    ....

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

    @Override
    protected IContentProvider acquireProvider(Context context, String auth) {
        return mMainThread.acquireProvider(context,
                ContentProvider.getAuthorityWithoutUserId(auth),
                resolveUserIdFromAuthority(auth), true);
    }
}
  • 可以看出ApplicationContentResolver类的成员变量mMainThread指向了一个ActivityThread对象,它是在构造函数中初始化的。
  • 在函数acquireProvider内部,其实调用的是ActivityThread类的成员函数acquireProvider(),这个函数会返回一个ContentProvider组件的代理对象,而这个代理对象代理的,正是联系人数据URI对应的COntentProvider组件!

时序图step4—5 --> ActivityThread#acquireProvider()/acquireExistingProvider()

接下来看ActivityThread类的acquireProvider()函数的源码如下:

public final class ActivityThread {
    ....
    public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
            return provider;
        }
        ....
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), auth, userId, stable);
        } ....
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }
    ....
    public final IContentProvider acquireExistingProvider(
        Context c, String auth, int userId, boolean stable) {
        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                incProviderRefLocked(prc, stable);
            }
            return provider;
        }
    }
}
  • ActivityThread类的acquireProvider()函数中,首先通过acquireExistingProvider()函数得到一个ContentProvider组件的代理对象IContentProvider对象provider;

  • acquireExistingProvider()函数持有一个全局的mProviderMap变量,表示一个HashMap<ProviderClientRecord>哈希表,用来保存在当前应用程序进程中访问过的ContentProvider组件的代理ProviderClientRecord对象;

  • 此函数的逻辑是,从mProviderMap表中获取与权限参数(auth)以及用户ID参数(userId)对应的ProviderClientRecord代理对象,并将其返回!

  • 再回到acquireProvider()函数中,得到provider对象后进行判空,如果provider非空则直接将其返回给调用者了;

  • 如果provider为空,接下来会先获得ActivityManagerService类的一个代理对象,接着调用ActivityManagerService代理对象的成员函数getContentProvider()来请求与权限参数(auth)以及用户ID参数(userId)对应的ContentProvider组件的代理对象;

  • 并通过ActivityManagerService将这个代理对象返回。ActivityManagerService返回的ContentProvider组件的代理对象,使用一个ContentProviderHolder对象来进行描述。

  • 接下来,会调用ActivityThread类的成员函数installProvider(),将这个ContentProviderHolder对象封装成一个ContentClientRecord对象,并把这个ContentClientRecord对象存入全局变量mProviderMap中。

  • 这样如果后面再次接收到,对相同URI对应的ContentProvider的访问请求,对于每个ContentProvider组件来说了,只需要被ActivityThread请求一次即可,当再次收到相同的请求只需要从全局变量mProviderMap中将其取出来返回给调用者即可!

然后按照函数执行的先后顺序,分为两个片段,先分析ActivityManagerService代理对象(记作ActivityManagerProxy)的成员函数getContentProvider()的具体实现(对应时序图step6—19),然后再分析ActivityThread类的成员函数installProvider()的具体实现(对应时序图step20)。

0x03 参考文献与简单的结语


未完,转接下一篇:ContentProvider启动流程分析二(对应时序图step6—14)!

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

推荐阅读更多精彩内容