ContentProvider是Android应用开发的四大组件之一,并且源码相对于其他几个也是比较简单的。因此我们先来看看它的源码。ContentProvider的使用我们会涉及到外部程序调用应用的ContentProvider来查询数据,也有监听数据的变化,以及ContentProvider的安装。我们先来看安装部分的源码。
ContentProvider根据它的名字就知道,他是一个内容提供者,它提供了整删改查的接口,方便Android应用跨应用跨进程的数据共享,在Android系统中,相册,通讯录等等都是通过ContentProvider来共享数据让其他应用可以使用。看源码,我们需要关注两个点,一个是ContentProvider如何安装的,另一个就是当我们发起一个查询的时候,是怎样和内容提供的那个进程进行交互的。
ContentProvider的安装触发通常有两个场景,一是外部程序需要使用ContentProvider的时候,另一个是在应用Application启动的时候,这个一般触发的场景有,启动Service,启动Activity。这所有场景的共同点都是拉起进程,初始化Application。这两大场景来安装ContentProvider除了开始的路径会有差别,后面的部分大致都相等。因此我们这里以启动App同时安装Provider作为分析路径。
ContentProvider相关的类有如下这些:
classDiagram
class IContentProvider
<<interface>> IContentProvider
class ContentProviderRecord
class ProviderMap
class ProcessProviderRecord
class ContentProvider
class ContentProviderNative
class Transport
class ProviderClientRecord
class Binder
<<abstract>> ContentProvider
class ProviderInfo
<<abstract>> ContentProviderNative
ContentProviderRecord *-- IContentProvider
ContentProviderRecord *-- ProviderInfo
IContentProvider <|.. ContentProviderNative
Binder <|-- ContentProviderNative
ContentProviderNative <|-- Transport
ContentProvider *-- Transport
class ContentProviderHolder
ContentProviderHolder *-- IContentProvider
ProviderClientRecord *-- IContentProvider
ProviderClientRecord *-- ContentProvider
ProviderClientRecord *-- ContentProviderHolder
ContentProviderHolder *-- ProviderInfo
ProviderMap o-- ContentProviderRecord
ProcessProviderRecord o-- ContentProviderRecord
PackageManager解析出来的Provider信息通过ProviderInfo来保存,我们平时创建的ContentProvider它有一个内部类Transport,在它里面实现了Binder的客户端和服务端。通过它的Binder代理,AMS进程能够执行ContentProvider的增删改查,这不是本文的重点,下次再说。在App进程内,除了我们的ContentProvider对象,还会构建ProviderClientRecord对象,ContentProvider和它的binder对象和ProviderInfo信息会存在这个对象中。在服务端也有一个对应的ContentProviderRecord对象,里面存储了IContentProvider binder对象和ProviderInfo。并且客户端和服务端的这些record都会存到Map中去,这样方便后续调用的时候查找。而构建所有这些record和把他们放入到Map中的过程,其实就是Provider的安装过程。
我们开始看代码,在应用进程创建完成之后,AMS会执行attachApplicationLocked从而来创建App的Application对象,因此我们从这里开始看,而进程的启动可以挖个坑以后再写。最初起点应该在ActivityManagerService的attachApplicationLocked方法中:
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
? mCpHelper.generateApplicationProvidersLocked(app)
: null;
...
final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
thread.bindApplication(processName, appInfo,
app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
providerList, null, profilerInfo, null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.getCompat(), getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.getDisabledCompatChanges(), serializedSystemFontMap,
app.getStartElapsedTime(), app.getStartUptime());
可以看到AMS中首先是通过mCpHelper去生成当前应用的Provider列表,之后调用应用进程的bindApplication的时候再带过去。mCpHelper是一个ContentProviderHelper对象,我们先来看看它的generateApplicationProvidersLocked方法:
List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
final List<ProviderInfo> providers;
providers = AppGlobals.getPackageManager().queryContentProviders(
app.processName, app.uid, ActivityManagerService.STOCK_PM_FLAGS
| PackageManager.GET_URI_PERMISSION_PATTERNS
| PackageManager.MATCH_DIRECT_BOOT_AUTO, /*metaDataKey=*/ null)
.getList();
int numProviders = providers.size();
final ProcessProviderRecord pr = app.mProviders;
pr.ensureProviderCapacity(numProviders + pr.numberOfProviders());
for (int i = 0; i < numProviders; i++) {
// NOTE: keep logic in sync with installEncryptionUnawareProviders
ProviderInfo cpi = providers.get(i);
boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
if (singleton && app.userId != UserHandle.USER_SYSTEM) {
// This is a singleton provider, but a user besides the
// default user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process, // it runs in the process of the default user. Get rid of it.
providers.remove(i);
numProviders--;
i--;
continue;
}
final boolean isInstantApp = cpi.applicationInfo.isInstantApp();
final boolean splitInstalled = cpi.splitName == null || ArrayUtils.contains(
cpi.applicationInfo.splitNames, cpi.splitName);
if (isInstantApp && !splitInstalled) {
// For instant app, allow provider that is defined in the provided split apk.
// Skipping it if the split apk is not installed.
providers.remove(i);
numProviders--;
i--;
continue;
}
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, app.userId);
if (cpr == null) {
cpr = new ContentProviderRecord(mService, cpi, app.info, comp, singleton);
mProviderMap.putProviderByClass(comp, cpr);
}
pr.installProvider(cpi.name, cpr);
if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.longVersionCode,
mService.mProcessStats);
}
mService.notifyPackageUse(cpi.applicationInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER);
}
return providers.isEmpty() ? null : providers;
}
上面第三行代码为调用PackageManagerService去读取当前应用所有的ContentProvider信息并存储到ProviderInfo列表中,具体代码在ComputerEngine中,这里不分析了。ProviderInfo中存储了每一个ContentProvider的信息,包括它的组件名称,查询的authority,运行的进程,读写的权限等等。这里我们需要注意一下,我们在Manifest文件中声明ContentProvider的时候,是可以指定它所运行的进程的,在这个地方,我们传进来而的app也是一个ProcessRecord进程,它对应的是我们的一个进程的记录而不是app的记录,因此,我们拿到的ProviderInfo也是当前进程需要启动的所有进程。
随后会开启一个循环对每一个Provider做处理,在37行,通过packagename和name组合出ComponentName,这个和其他的构造Activity,Service等的类似。随后会尝试从ProviderMap中获取已经存在的记录,正常情况下这里都是空,如果一个App有多个进程,并且provider可以在多个进程运行,那么这里可能是可以拿到缓存的。
如果没有拿到缓存,我们会开始创建ContentProviderRecord,这是ContentProvider在AMS当中的记录,而它也会放到ProviderMap中,这样下次使用的时候就不需要再次创建了。
43行会调用ProcessProviderRecord的installProvider,这里只是把这条record存放到ProcessRecord的mProviders中去。
我们再回到AMS的代码中去,AMS当中会把我们的List<ProviderInfo>包装成一个ProviderInfoList对象,最后调用到ApplicationThread的bindApplication方法,从而把这些东西传递到App进程。
来到ActivityThread的源码,bindApplication会把AMS带过来的数据封装成AppBindData,通过sendMessage把传递ActivityThread类,并且调用它的handleBindApplication方法,其中我们会看到如下代码:
Application app;
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
mInstrumentation.callApplicationOnCreate(app);
上面第5行就是去安装ContentProviders。第二行是去创建我们的Application,其中会调用Application的attach方法,第9行是调用Application的onCreate方法,可见ContentProvider的一些方法是在他们两之间执行的,这也是为什么很多SDK通过使用ContentProvider来初始化他们的代码。继续看installContentProviders的代码:
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
上面的代码就是去遍历每一个ContentProvider去安装,我们继续看installProvider的代码:
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
}
...
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
// System startup case.
packageInfo = getSystemContext().mPackageInfo;
}
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
localProvider.attachInfo(c, info);
}
...
synchronized (mProviderMap) {
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
ComponentName cname = new ComponentName(info.packageName, info.name);
holder = new ContentProviderHolder(info);
holder.provider = provider;
holder.noReleaseNeeded = true;
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
mLocalProviders.put(jBinder, pr);
mLocalProvidersByName.put(cname, pr);
}
...
}
以上代码简化很多,仅保留启动App安装Provider的代码。第10行到第17行的代码,为通过LoadedApk通过反射去创建ContentProvider这个对象,随后通过它拿到IContentProvider对象,也就是它的Binder对象。随后调用attachInfo方法:
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
mCallingAttributionSource = new ThreadLocal<>();
mContext = context;
if (context != null && mTransport != null) {
mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
Context.APP_OPS_SERVICE);
}
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
setAuthorities(info.authority);
}
ContentProvider.this.onCreate();
}
可以看到其中是为ContentProvider设置一些信息,包括它的Context,以及把Manifest上面设置的一些属性,权限之类的保存到当前这个对象中,最后会调用onCreate方法,这会执行我们重写时写的代码。
再回到installProvider方法,在28行,会创建ContentProviderHolder,随后调用installProviderAuthoritiesLocked把Provider和它所对应的authority对应,并创建ProviderClientRecord,代码如下:
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
ContentProvider localProvider, ContentProviderHolder holder) {
final String auths[] = holder.info.authority.split(";");
final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
...
final ProviderClientRecord pcr = new ProviderClientRecord(
auths, provider, localProvider, holder);
for (String auth : auths) {
final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord existing = mProviderMap.get(key);
if (existing != null) {
} else {
mProviderMap.put(key, pcr);
}
}
return pcr;
}
上面的代码很简单,就是创建了ProviderClientRecord,其中保存了auths,我们创建的ContentProvider,以及IContentProvider,ContentProviderHolder,最后把每个authority作为key, ProviderClientRecord作为value,存放到了mProviderMap中。
上面的代码执行完之后,在installProvider中,又分别以binder对象和ComponentName对象为key,ProviderClientRecord对象为value存放到map中。
这一切做完之后,我们还需要回到installContentProviders方法的最后,看看第10行的代码,看代码名称是发布我们的Provider,那我们继续到AMS中去看代码,其中主要调用了如下代码:
mCpHelper.publishContentProviders(caller, providers);
继续去ContentHelper中看代码:
void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
synchronized (mService) {
final ProcessRecord r = mService.getRecordForAppLOSP(caller);
for (int i = 0, size = providers.size(); i < size; i++) {
ContentProviderHolder src = providers.get(i);
ContentProviderRecord dst = r.mProviders.getProvider(src.info.name);
if (dst == null) {
continue;
}
ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
mProviderMap.putProviderByClass(comp, dst);
String[] names = dst.info.authority.split(";");
for (int j = 0; j < names.length; j++) {
mProviderMap.putProviderByName(names[j], dst);
}
r.addPackage(dst.info.applicationInfo.packageName,
dst.info.applicationInfo.longVersionCode, mService.mProcessStats);
synchronized (dst) {
dst.provider = src.provider;
dst.setProcess(r);
dst.notifyAll();
dst.onProviderPublishStatusLocked(true);
}
}
}
}
上面的代码第3行,通过我们传过来的IApplicationThread来获取到我们的进程在AMS当中对应的ProcessRecord。随后会遍历每一个ContentProviderHolder,检查ProcessRecord当中的Record是否都有,随后会把ProcessRecord当中所存储的的CotentProviderRecord按照类名和authority分别存储到mProviderMap当中,ContentProviderHolder会存储到ContentProviderRecord当中。
最后也放一下整个流程的流程图方便看代码:
sequenceDiagram
autonumber
box LIGHTYELLOW SYSTEM_SERVER进程
participant AMS
participant ContentProviderHelper
participant PMS
participant ProcessProviderRecord
end
box LIGHTGREEN 应用进程
participant ApplicationThread
participant ActivityThread
participant ContentProvider
end
rect rgb(191, 223, 255)
note right of AMS: attachApplicationLocked
AMS->>+ContentProviderHelper: generateApplicationProvidersLocked
ContentProviderHelper->>+PMS: queryContentProviders
PMS-->>-ContentProviderHelper: List<ProviderInfo>
ContentProviderHelper->>ProcessProviderRecord: installProvider
ContentProviderHelper-->>-AMS: List<ProviderInfo>
AMS->>ApplicationThread: bindApplication(binder call)
end
ApplicationThread->>ActivityThread: handleBindApplication
rect rgb(191, 223, 255)
note right of ActivityThread: installContentProviders
ActivityThread->>ActivityThread: installProvider
ActivityThread->>ContentProvider: attachInfo
ActivityThread->>ActivityThread: installProviderAuthoritiesLocked
end
ActivityThread->>AMS: publishContentProviders
AMS->>ContentProviderHelper: publishContentProviders
至此,就执行完了所有的ContentProvider安装的工作。至于使用ContentProvider的场景,我们之后在继续分析。本文以Android13的代码分析,如果读者对照最好也是以同样版本的代码看。以上是本人关于Android代码阅读的一点分享,由于个人可能存在一些误区,难免会有理解错误,或者笔误,如有发现,欢迎指正,也欢迎读者与我交流Android技术。
原文地址(掘金的类图展示有点问题,可去原文查看): http://localhost:1313/2024-08-15-android-content-provider-install/