美文网首页
ContentProvider启动流程分析(一)

ContentProvider启动流程分析(一)

作者: 一只酸奶牛哇 | 来源:发表于2016-10-12 22:32 被阅读1264次

    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)!

    相关文章

      网友评论

          本文标题:ContentProvider启动流程分析(一)

          本文链接:https://www.haomeiwen.com/subject/qctwyttx.html