ContentProvider启动流程分析(一)
0x01 扯东扯西的前言&概述
本片博客对应时序图上的step1—5:下接第二篇ContentProvider启动流程分析二!
同时此系列博客同步在简书发布:ContentProvider启动流程分析系列!详情戳这里即可访问~
作为安卓设计的四大组件之一,是跨进程共享数据的一把利器,所谓跨进程共享数据,通俗理解就是,应用程序A可以访问操作应用程序B共享出来的数据,这些共享出来的数据一般都有其对应的URI(统一资源标识符),那么就涉及到两个过程:
- 提供数据内容的过程:
- A应用(比如系统联系人,日历)如果希望共享自己的数据内容(比如联系人列表,日历信息),就需要通过子类重写ContentProvider六个方法来实现,ContentProvider的六个方法的共性特征是,都接受一个URI参数,通过解析URI参数匹配相应的数据内容并将其返回;
- 当一个跨进程访问数据的请求(包含RUI参数)发出后,系统首先会校验URI权限(authority),权限校验通过后,把这个访问请求传递到对应进程(具体来说是一个应用程序)的ContentProvider组件,ContetnProvider接收到URI参数后,会解析URI路径(path),然后根据路径解析结果,提供相应的数据内容并将其返回;
- 此过程在A应用程序内部实现,当A应用程序封装好了ContentProvider实现类,需要在Mainfest清单文件中进行注册,至此,A应用程序所在的进程已经提供了访问自己所在进程数据的接口,B应用程序只需要根据数据内容的URI,发出访问请求即可;
- 发出访问数据请求的过程:
- 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组件是经过哪些调用,一步一步被启动的呢?请看时序图如下:
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
哈希表,用来保存在当前应用程序进程中访问过的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)!