一、数据访问者
ContentProvider组件是Android四大组件之一,它的使用频率低于前三者,但是也非常重要。它提供了一种组件进程向外部进程暴露数据的通用方案。在该组件进程,数据存储方式可以是数据库、sharedpreferences、xml、文件或网络。外部进程通过统一的接口实现对组件进程的数据访问,使数据提供者和访问者完全解耦,并且不用关心数据的存储方式。
我们在使用它时,比如向某个进程ContentProvider组件插入数据,在Activity组件中定义如下代码。
ContentResolver contentResolver = getContentResolver();
Uri uri = Uri.parse("content://xxx/xxx");
ContentValues values = new ContentValues();
values.put("name", "Demo");
Uri returnuir = contentResolver.insert(uri, values);
首先,通过在Activity组件调用getContentResolver方法,获取一个ContentResolver访问者,ContentResolver是抽象类。
然后,通过统一资源标识符Uri,匹配ContentProvider,调用ContentResolver访问者的增删查改方法,访问者的#insert方法实现插入。
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
Activity组件Wrapper类中,调用ContextImpl实现类的getContentResolver方法。
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
ContentResolver对象在它的ContextImpl内部,它的构造方法初始化。
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, ...) {
mOuterContext = this;
mMainThread = mainThread;
if (user == null) {
user = Process.myUserHandle();
}
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
原来,我们获取的ContentResolver是一个ApplicationContentResolver对象,继承了ContentResolver抽象类,实现acquireProvider方法。下面是调用它的插入insert方法。
public final Uri insert(Uri url, ContentValues values) {
//先ContentResolver的final方法
IContentProvider provider = acquireProvider(url);
//provider为空说明Uri无法识别,抛出异常
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
return createdRow;
} catch (RemoteException e) {
return null;
} finally {
releaseProvider(provider);
}
}
先查找IContentProvider,然后具体调用它的insert方法。最后会释放掉IContentProvider。
下面分析一下查找IContentProvider的过程,这是比较重要的部分哈。通过里面调用子类ApplicationContentResolver实现的acquireProvider方法实现。
public final IContentProvider acquireProvider(Uri uri) {
//SCHEME_CONTENT是"content"
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
if (auth != null) {
//触发子类实现的acquireProvider方法
return acquireProvider(mContext, auth);
}
return null;
}
统一资源标识符一般有四个部分组成,协议content://,授权信息域名,是ContentProvider唯一的标识符,目录即是表明,最后是文件,具体的某项记录ID,协议://域名/目录/文件#片段标示符。
这里会判断,如果统一资源标识符协议不是content,不符合Uri,返回空。getAuthority方法,获取的解析Uri得到的authorities字符串。在ContentProvider数据提供者中,通过UriMatcher的addURI添加需匹配的Uri,包括authority,存储路径或者表名,以及匹配码。UriMatcher#match方法时,如果传入Uri匹配,则返回匹配码。
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
调用ActivityThread类的acquireProvider方法,传入的参数是解析Uri得到的授权部分信息字符。
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);
} catch (RemoteException ex) {
}
if (holder == null) {
//无法找到auth对应的provider
return null;
}
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
在ActivityThread的方法中,首先是acquireExistingProvider方法,根据auth和userId创建一个ProviderKey值,在本进程的ArrayMap中查找ProviderClientRecord,若找到返回其内部的IContentProvider。
ArrayMap mProviderMap。
ActivityThread类内部ProviderMap,负责保存本地ProviderClientRecord。
当本地IContentProvider不存在时,访问Ams服务getContentProvider方法,请求Ams服务启动ContentProvider。
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
//IApplicationThread是空抛出异常
return getContentProviderImpl(caller, name, null, stable, userId);
}
调用Ams的getContentProviderImpl方法,返回一个ContentProviderHolder,它是在IActivityManager接口中定义的Parcelable类型,封装IContentProvider。有下面一段代码。
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null) {
//ContentProvider所在进程存在,但是还发布到Ams。
if (!proc.pubProviders.containsKey(cpi.name)) {
//加入ContentProviderRecord记录,同时让组件进程启动
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
//ContentProvider所在进程还不存在,创建
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
if (proc == null) {
return null;
}
}
查看一下ContentProvider组件所在进程是否存在,如果ProcessRecord是空,进程未创建,startProcessLocked方法,创建一个目标ContentProvider组件进程。
进程存在,是否已经发布到Ams
如果进程已存在,且ProcessRecord内部ArrayMap没有保存ContentProviderRecord,回调App进程scheduleXxx方法,启动ContentProvider。如果在ProcessRecord内部ArrayMap查找到进程的ContentProvider已经启动并发布,不需要回调Provider组件的App进程。
final ArrayMap pubProviders = new ArrayMap<>();
回到ContentProviders组件所在App进程,调用installContentProviders方法,启动ContentProvider,遍历ProviderInfo,每一个都调用本地installProvider方法。
private void installContentProviders(
Context context, List providers) {
final ArrayList results =
new ArrayList();
for (ProviderInfo cpi : providers) {
IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
try {
ActivityManagerNative.getDefault().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
}
}
这里调用installProvider方法,传入ContentProviderHolder是空,当它是空时,创建ContentProvider组件对象,获取内部IContentProvider,然后,触发attachInfo方法。attachInfo方法中,初始化内部Context,调用组件onCreate方法。
try {
final ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
localProvider.attachInfo(c, info);
} catch (Exception e) {
}
方法最后,调用Ams的publishContentProviders方法,将ContentProvider发布到Ams服务,传入的是ContentProviderHolder列表。
发布到Ams服务,根据ContentProviderHolder在ProcessRecord内部的ArrayMap(即pubProviders)中查找ContentProviderRecord记录,将该记录放入Ams的ProviderMap,它存储Ams的ContentProviderRecord。
再次回到ActivityThread的acquireProvider方法。
最终,Ams服务getContentProviderImpl方法返回ContentProviderHolder对象。从Ams服务获取到ContentProviderHolder,调用ActivityThread的installProvider方法,这里调用的该方法传入ContentProviderHolder不是空,不会再执行onCreate方法启动组件,在本地进程创建ProviderClientRecord记录保存ArrayMap,下次可以直接从本地获取IContentProvider,更新Ref计数。
下面是一张数据操作时查找IContentProvider代理的流程图。
二、数据通信原理
前面介绍过,在数据提供者所在进程,当Ams服务发起scheduleInstallProvider方法回调时,在ActivityThread的installProvider方法中,创建ContentProvider对象,执行onCreate方法,这样,数据提供者组件就启动起来了,并且会发布到Ams服务。
在请求数据的进程中,通过IContentProvider的数据操作方法实现组件访问。进程间通信采用Binder方案,IContentProvider是通信业务接口。
ContentProvider是抽象类,子类实现数据操作,数据操作的四种方法,增删查改。
//增
public abstract Uri insert(Uri uri, ContentValues values);
//删
public abstract int delete(Uri uri, String selection,
String[] selectionArgs);
//查
public abstract Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs,
String sortOrder);
//改
public abstract int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs);
从Ams获取的IContentProvider是一个Binder代理。在ContentProvider组件内部引用一个Transport对象。
private Transport mTransport = new Transport();
它是ContentProvider内部类,继承ContentProviderNative,实现通信服务端IContentProvider具体数据操作业务。由它去调用继承组件实现的增删查改抽象方法。
class Transport extends ContentProviderNative {
}
在访问端进程触发从Ams服务返回的IContentProvider的数据插入方法时,在数据提供者进程,将触发Transport的insert方法。
@Override
public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return rejectInsert(uri, initialValues);
}
final String original = setCallingPackage(callingPkg);
try {
//重写的ContentProvider的insert方法
return maybeAddUserId(ContentProvider.this.insert(uri, initialValues), userId);
} finally {
setCallingPackage(original);
}
}
该方法会进入我们ContentProvider子类重写的insert方法,可以实现数据写入,我们重写该方法,自由选择数据持久化方案。其他的数据操作方法流程和数据插入一样。
三、总结
请求端组件,获取ContentResolver抽象类的实现对象,调用它的增删查改方法操作数据。
根据Url,查找IContentProvider,先本地Map,找到ProviderClientRecord,去取内部IContentProvider。
未找到,Ams查询,返回ContentProviderHolder,内部IContentProvider返回,并创建一个ProviderClientRecord封装IContentProvider存本地。Ams查询时,若未发布,或进程问创建,先组件提供者进程去启动进程或者Provider组件,然后发布到Ams。
数据共享通信原理采用Binder机制。请求端从Ams获取的IContentProvider就是业务接口代理对象。
组件提供者进程ContentProvider组件实现增删查改方法,内部的Transport会调用他们。
任重而道远