一般项目中ContentProvider使用频率极低,但我们项目中用到了大量的ContentProvider,本文会基于项目中的实际使用场景,结合系统源码深入分析ContentProvider的启动和调用原理,希望能有助于大家理解现有代码,规范开发,持续优化。
注:源码来自于系统10.0和12.0源码
一、这些问题你知道吗?
1.ContentProvider和Application的启动时机关系?
2.如何控制多个ContenProvider的启动顺序?
3.系统如何监测ContentProvider类型的ANR?
4.ContentProvider的Context有何特殊之处?
5.ContentProvider各方法的调用所处的线程?
6.CRUD方法和call方法差异?
7.ContentProvider的其他用法?
8.ContentProvider的优化?
二、启动原理
1.App启动流程概述
桌面点击图标启动一个应用的组件如Activity时,如果Activity所在的进程不存在,就会创建并启动进程。Android系统中一 般应用进程的创建都是统一由zygote进程fork创建的,AMS在需要创建应用进程时,会通过socket连接并通知到zygote进程,socket服务端是在开机阶段就创建好的,然后由zygote进程fork创建出应用进程。
整体架构如下图所示:
zygote自身进程初始化
/*frameworks/base/core/java/com/android/internal/os/ZygoteInit.java*/
public static void main(String[] argv) {//第一次进入java层
ZygoteServer zygoteServer = null;
...
try {
...
// 1.preload提前加载框架通用类和系统资源到进程,加速进程启动
preload(bootTimingsTraceLog);
...
// 2.创建zygote进程的socket server服务端对象
zygoteServer = new ZygoteServer(isPrimaryZygote);
...
//3.启动SystemServer进程
forkSystemServer(abiList, zygoteSocketName, zygoteServer);
...
// 4.进入死循环,等待AMS发请求过来
caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
...
} finally {
...
}
...
}
zygote创建应用进程并初始化
/*frameworks/base/core/java/com/android/internal/os/ZygoteInit.java*/
public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
String[] argv, ClassLoader classLoader) {
...
// 原生添加名为“ZygoteInit ”的systrace tag以标识进程初始化流程
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
RuntimeInit.redirectLogStreams();
// 1.RuntimeInit#commonInit中设置应用进程默认的java异常处理机制,
RuntimeInit.commonInit();
// 2.ZygoteInit#nativeZygoteInit函数中JNI调用启动进程的binder线程池
ZygoteInit.nativeZygoteInit();
// 3.RuntimeInit#applicationInit中反射创建ActivityThread对象并调用其“main”入口方法
return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
classLoader);
}
2.ContentProvider启动流程概述
App启动后,会调用ActivityThread的main方法,接着在main方法中创建了ActivityThread对象,并调用了它的attach方法。 在attach方法中远程调用AMS的attachApplication方法,该方法中又远程调用PMS的queryContentProviders方法获取应用注册的Provider信息, 然后调用ApplicationThread的bindApplication方法将Provider信息传递过去。在ApplicationThread在handleBindApplication方法中通过makeApplication生成Application 对象,然后调用 installContentProviders 方法初始化并加载ContentProvider,然后调用Application对象的onCreate方法,应用就这样跑起来了 。
如下图所示:
3.详细分析
App进程启动
首先是App启动,调用了ActivityThread的main方法
/*frameworks/base/core/java/android/app/ActivityThread.java*/
public static void main(String[] args) {
// 原生添加的标识进程ActivityThread初始化过程的systrace tag,名为“ActivityThreadMain”
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
// 1.创建并启动主线程的loop消息循环
Looper.prepareMainLooper();
...
// 2.attachApplication注册到系统AMS中
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
...
}
可以看到进程ActivityThread#main函数初始化的主要逻辑是:
1.创建并启动主线程的loop消息循环;
2.通过binder调用AMS的attachApplication接口将自己attach注册到AMS中
主线程初始化完成后,主线程就有了完整的 Looper、MessageQueue、Handler,此时 ActivityThread 的 Handler 就可以开始处理 Message, 包括 Application、Activity、ContentProvider、Service、Broadcast 等组件的生命周期函数,都会以 Message 的形式, 在主线程按照顺序处理,如果没有其他的 Message 需要处理,那么主线程就会进入休眠阻塞状态继续等待,这就是 App 主线程的初始化和运行原理。
看一下attach方法:
//ActivityThread.java
private void attach(boolean system, long startSeq) {
...
if (!system) {
...
final IActivityManager mgr = ActivityManager.getService();
try {
// 通过binder调用AMS的attachApplication接口将自己的ApplicationThread注册到AMS中
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
ActivityManager.getService()获取到一个IActivityManager对象,它是一个Binder对象, 通过它调用attachApplication方法实际上就是远程调用了ActivityManagerService的attachApplication方法。
<!--ActivityManagerService.java
public final void attachApplication(IApplicationThread thread) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid);
Binder.restoreCallingIdentity(origId);
}
}
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
...
//获取应用中注册的ContentProvider数据
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
msg.obj = app;
mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
}
//通过oneway异步类型的binder调用应用进程ActivityThread#IApplicationThread#bindApplication接口
thread.bindApplication(processName, appInfo, providers,
return true;
}
private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
List<ProviderInfo> providers = null;
try {
//远程调用PMS获取应用中注册的ContentProvider信息
providers = AppGlobals.getPackageManager()
.queryContentProviders(app.processName, app.uid,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
| MATCH_DEBUG_TRIAGED_MISSING, /*metadastaKey=*/ null)
.getList();
} catch (RemoteException ex) {
}
...
return providers;
}
-->
attachApplicationLocked 做了三个事情 :
1.调用generateApplicationProvidersLocked获取注册的ContentProvider
2.发出启动ContentProvider的超时消息,也就是四大组件耗时导致ANR的其中一种情况
3.远程调用了应用进程的ApplicationThread的bindApplication方法。
其中bindApplication方法我们在看完如何收集Provider后再详细讲解。
通过PMS获取所有注册过的Provider
咱们这篇文章主要是分析ContentProvider的启动流程,因此我们详细看一下是如何找到Provider的。
AppGlobals.getPackageManager()方法返回的是IPackageManager,调用了它的 queryContentProviders 方法。
这是一个Binder对象,其实际调用的是PackageManagerService的queryContentProviders方法:
<!--PackageManagerService.java
@Override
public @NonNull ParceledListSlice<ProviderInfo> queryContentProviders(String processName,
int uid, int flags, String metaDataKey) {
...
ArrayList<ProviderInfo> finalList = null;
final List<ProviderInfo> matchList =
mComponentResolver.queryProviders(processName, metaDataKey, uid, flags, userId);
final int listSize = (matchList == null ? 0 : matchList.size());
synchronized (mPackages) {
for (int i = 0; i < listSize; i++) {
final ProviderInfo providerInfo = matchList.get(i);
if (!mSettings.isEnabledAndMatchLPr(providerInfo, flags, userId)) {
continue;
}
final PackageSetting ps = mSettings.mPackages.get(providerInfo.packageName);
final ComponentName component =
new ComponentName(providerInfo.packageName, providerInfo.name);
if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) {
continue;
}
if (finalList == null) {
finalList = new ArrayList<>(listSize - i);
}
finalList.add(providerInfo);
}
}
if (finalList != null) {
finalList.sort(sProviderInitOrderSorter);//根据initOrder进行排序
return new ParceledListSlice<>(finalList);
}
return ParceledListSlice.emptyList();
}
-->
queryContentProviders方法很长,但是其主要做的事情是:
1.通过 ComponentResolver 查询所有的 Provider
2.遍历 Provider 并过滤存到 finalList
3.对 finalList 根据initOrder进行排序
注:很多Provider注册的时候可以通过initOrder决定初始化顺序,这在组件化和三方库中使用Provider作为对外注入实现类的场景很有用。因为很可能存在组件间有抽象依赖关系,只想使用接口但又不想依赖对方的具体实现,那就可以使用initOrder控制注入实现类的顺序,类似StartUp的Initializer功能。
看一下ComponentResolver是如何找到Provider的:
<!--ComponentResolver.java
private final ActivityIntentResolver mActivities = new ActivityIntentResolver();
/** All available providers, for your resolving pleasure. */
@GuardedBy("mLock")
private final ProviderIntentResolver mProviders = new ProviderIntentResolver();
/** All available receivers, for your resolving pleasure. */
@GuardedBy("mLock")
private final ActivityIntentResolver mReceivers = new ActivityIntentResolver();
/** All available services, for your resolving pleasure. */
@GuardedBy("mLock")
private final ServiceIntentResolver mServices = new ServiceIntentResolver();
List<ProviderInfo> queryProviders(String processName, String metaDataKey, int uid, int flags,
int userId) {
if (!sUserManager.exists(userId)) {
return null;
}
List<ProviderInfo> providerList = null;
synchronized (mLock) {
for (int i = mProviders.mProviders.size() - 1; i >= 0; --i) {
final PackageParser.Provider p = mProviders.mProviders.valueAt(i);
final PackageSetting ps = (PackageSetting) p.owner.mExtras;
...//根据条件过滤
if (providerList == null) {
providerList = new ArrayList<>(i + 1);
}
providerList.add(info);
}
}
return providerList;
}
-->
专门负责ContentProvider解析的ProviderIntentResolver的 mProviders 是在哪里存进去的呢? 也就是要找到 ProviderIntentResolver.addProvider 方法是在哪里调用的,我们可以看到ProviderIntentResolver.addProvidersLocked中有调用此方法,继续往后看总结起来调用时序就是:
PackageManagerService.commitPackageSettings
ComponentResolver.addAllComponents
ComponentResolver.addProvidersLocked
ProviderIntentResolver.addProvider
<!--
//ComponentResolver#ProviderIntentResolver.java
private static final class ProviderIntentResolver
extends IntentResolver<PackageParser.ProviderIntentInfo, ResolveInfo> {
@Override
...
void addProvider(PackageParser.Provider p) {//PackageParser.Provider对象存储的就是每一个ContentProvider的信息
if (mProviders.containsKey(p.getComponentName())) {
Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring");
return;
}
mProviders.put(p.getComponentName(), p);
...
}
}
//ComponentResolver.java
private void addProvidersLocked(PackageParser.Package pkg, boolean chatty) {
final int providersSize = pkg.providers.size();
StringBuilder r = null;
for (int i = 0; i < providersSize; i++) {
PackageParser.Provider p = pkg.providers.get(i);
p.info.processName = fixProcessName(pkg.applicationInfo.processName,
p.info.processName);
mProviders.addProvider(p);
...
}
void addAllComponents(PackageParser.Package pkg, boolean chatty) {
final ArrayList<PackageParser.ActivityIntentInfo> newIntents = new ArrayList<>();
synchronized (mLock) {
addActivitiesLocked(pkg, newIntents, chatty);//存储Activity的信息
addReceiversLocked(pkg, chatty);//存储BroadcastReceiver的信息
addProvidersLocked(pkg, chatty);//存储ContentProvider的信息
addServicesLocked(pkg, chatty);//存储Service的信息
}
final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
for (int i = newIntents.size() - 1; i >= 0; --i) {
final PackageParser.ActivityIntentInfo intentInfo = newIntents.get(i);
final PackageParser.Package disabledPkg = sPackageManagerInternal
.getDisabledSystemPackage(intentInfo.activity.info.packageName);
final List<PackageParser.Activity> systemActivities =
disabledPkg != null ? disabledPkg.activities : null;
adjustPriority(systemActivities, intentInfo, setupWizardPackage);
}
}
-->
<!--PackageManagerService.java
private void commitPackageSettings(PackageParser.Package pkg,
...) {
final String pkgName = pkg.packageName;
...
synchronized (mPackages) {
// We don't expect installation to fail beyond this point
// Add the new setting to mSettings
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
// Add the package's KeySets to the global KeySetManagerService
KeySetManagerService ksms = mSettings.mKeySetManagerService;
ksms.addScannedPackageLPw(pkg);
mComponentResolver.addAllComponents(pkg, chatty);
}
...
}
-->
addAllComponents方法中不仅仅存储了ContentProvider的信息,还存储了Service、BroadcastReceiver、Activity等信息, 其中PackageParser.Provider 对象是遍历PackageParser.Package的providers获取的,而PackageParser.Package的providers就是通过解析AndroidManifest.xml得到的。
PackageParser的parseBaseApplication方法就是解析方法:
<!--PackageParser.java
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
...省略
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("activity")) {
...
} else if (tagName.equals("receiver")) {
...
} else if (tagName.equals("service")) {
...
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
} else {
...
}
}
...省略
return true;
}
-->
可以看到这里解析AndroidManifest.xml是采用了XmlPullParser来进行解析的 通过PMS获取ContentProvider列表信息的流程到此结束.
通过上述分析,我们也验证了自定义的ContentProvider必须在AndroidManifest中注册这样的一个结论。
回到应用进程处理应用绑定
AMS服务在执行应用的attachApplication注册请求过程中,会通过oneway类型的binder调用应用进程ActivityThread#IApplicationThread的bindApplication接口, 而bindApplication接口函数实现中又会通过往应用主线程消息队列post BIND_APPLICATION消息触发执行handleBindApplication初始化函数
<!--
/*frameworks/base/core/java/android/app/ActivityThread.java*/
private class ApplicationThread extends IApplicationThread.Stub {
@Override
public final void bindApplication(...) {//被AMS远程调用
...
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;//providers就是从PMS中获取的ContentProvider信息
...
// 向应用进程主线程Handler发送BIND_APPLICATION消息,触发在应用主线程执行handleBindApplication初始化动作
sendMessage(H.BIND_APPLICATION, data);
}
...
}
class H extends Handler {
...
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION://在主线程处理绑定的消息
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
// 在应用主线程执行handleBindApplication初始化动作
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
}
...
}
-->
在主线程执行handleBindApplication
<!--
/*frameworks/base/core/java/android/app/ActivityThread.java*/
@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
...
// 创建应用的LoadedApk对象
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
...
// 创建应用Application的Context、触发Art虚拟机加载应用APK的Dex文件到内存中,并加载应用APK的Resource资源
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
...
// 调用LoadedApk的makeApplication函数,实现创建应用的Application对象
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
// 初始化并加载ContentProvider
installContentProviders(app, data.providers);
...
// 执行应用Application#onCreate生命周期函数
mInstrumentation.callApplicationOnCreate(app);
...
}
-->
主要完成如下几件事件:
1.根据框架传入的ApplicationInfo信息创建应用APK对应的LoadedApk对象;
2.创建应用Application的Context对象;
3.创建类加载器ClassLoader对象并触发Art虚拟机执行OpenDexFilesFromOat动作加载应用APK的Dex文件;
4.通过LoadedApk加载应用APK的Resource资源;
5.调用LoadedApk的makeApplication函数,创建应用的Application对象;
6.初始化并加载ContentProvider
7.执行应用Application#onCreate生命周期函数(APP应用开发者能控制的第一行代码);
项目中为了让其他模块中使用Koin获取实现类的时候一定能获取到,其中包括在CotentProvider中也能获取到,在Application.attachBaseContext中为整个项目收集了实现并注册了所有的Module, 这有一个理论基础支撑:Application.attachBaseContext调用在ContentProdier.onCreate前。 这里咱们为了验证这个理论,扩展看一下makeApplication的实现:
LoadedApk.makeApplication会调用到Instrumentation.newApplication
<!--
//Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
return app;
}
//Application.java
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
-->
可以看到确实attachBaseContext是执行在 installContentProviders之前的,这个理论得到验证。 接着往下看,在handleBindApplication方法中,判断如果ContentProvider列表不为空,则调用installContentProviders方法,如下所示:
<!--/*frameworks/base/core/java/android/app/ActivityThread.java*/
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
...
}
try {
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
-->
此方法核心是做两件事:
1.遍历providers并调用初始化和加载
2.publishContentProviders 将 ContentProvider 列表发布到 AMS 中目的是进行缓存,其它应用进程想要获取它的ContentProvider 的时候可以直接在缓存中遍历获取,同时移除ContentProvider的ANR消息。
再看一下installProvider做了什么?
此方法核心是做两件事:
1.遍历providers并调用初始化和加载
2.publishContentProviders 将 ContentProvider 列表发布到 AMS 中目的是进行缓存,其它应用进程想要获取它的ContentProvider 的时候可以直接在缓存中遍历获取,同时移除ContentProvider的ANR消息。
再看一下installProvider做了什么?
<!--
/*frameworks/base/core/java/android/app/ActivityThread.java*/
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
...
if (holder == null || holder.provider == null) {
...
try {
//通过类生成器生成了ContentProvider对象
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
...省略
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
...
}
}
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
...
}
/*ContentProvider.java*/
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
...
ContentProvider.this.onCreate();
...
}
public IContentProvider getIContentProvider() {
return mTransport;
}
-->
这里主要是干了两件事:
1.通过类生成器创建了ContentProvider实例并调用了ContentProvider的onCreate生命周期方法
2.installProviderAuthoritiesLocked 将获取到的IContentProvider远端句柄存入本地 mProviderMap。
4.小结
通过上述的源码分析,我们知道了ContentProvider是在AndroidManifest中注册,并且在App启动时通过反射的形式创建的,创建后调用了每一个ContentProvider对象的onCreate方法,其onCreate方法是运行在服务端进程的主线程的。 然后才调用了Application对象的onCreate方法,整个App就这样运行起来了。
三、调用原理
上面分析了ContentProvider对象是如何创建和初始化的,承接上面的内容,我们继续分析外部应用是如何调用ContentProvider的CRUD方法来和ContentProvider交互的 我们只拿其中的一个方法query进行分析,其他方法的流程与之类似。
1.调用原理概述
会先从本地缓存或者是AMS获取到通IContentProvider,如果对应ContentProvider所在的进程不存在,则会启动该进程,然后客户端通过拿到的IContentProvider远程调用对应ContentProvider的query方法后返回Cursor对象。
流程图如下:
注:由于会拉起对应ContentProvider所在的进程,因此我们项目在利用ContentProvider作为数据通道的拉起策略中分为了常驻保活、调用时保活、无保活,其实本质就是调用call方法还是调用contentResolver.notifyChange的差异。
2.详细分析
在客户端应用中,调用ContentProvider的query方法是通过Context的getContentResolver().query方法来实现的,而Context的实现者对象是ContextImpl,其 getContentResolver 方法如下所示:
//ContextImpl.java
public ContentResolver getContentResolver() {
return mContentResolver;
}
很简单的返回了一个ContentResolver对象,而上面的mContenResolver实际类型是ApplicationContentResolver, 所以getContentResolver().query方法实际上执行的是ApplicationContentResolver.query方法,而ApplicationContentResolver并没有重写query方法, 所以query方法还是在ContentResolver中实现,如下所示:
<!--ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
...
try {
...
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
...
qCursor = stableProvider.query(
mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
...
}
...
}
-->
query方法中,获取数据的步骤主要有以下两步:
1.通过acquireUnstableProvider方法获取IContentProvider,这是Binder对象。
2.通过该Binder对象远程调用ContentProvider的query方法得到Cursor对象
我们先来看第一步,acquireUnstableProvider方法如下所示:
<!--
//ContentResolver.java
public final IContentProvider acquireUnstableProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
String auth = uri.getAuthority();
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());
}
return null;
}
//ApplicationContentResolver.java
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
-->
1.判断了Uri的合法性,如果Uri的Scheme没有以SCHEME_CONTENT开头,则返回null。SCHEME_CONTENT的值是"content", 说明了和ContentProvider交互的Uri必须以"content"作为scheme
2.然后调用了acquireUnstableProvider方法,acquireUnstableProvider在子类中实现,即在ApplicationContentResolver中实现。
然后会调用到ActivityThread中:
<!--ActivityThread.java
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;
}
ContentProviderHolder holder = null;
try {
//缓存中没有,远程调用AMS的getContentProvider方法返回IContentProvider对象
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
// Install provider will increment the reference count for us, and break
// any ties in the race.
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);
...
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
if (!jBinder.isBinderAlive()) {
// The hosting process of the provider has died; we can't
// use this one.
Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ ": existing object's process dead");
handleUnstableProviderDiedLocked(jBinder, true);//将不可用的Provider句柄移除
return null;
}
// Only increment the ref count if we have one. If we don't then the
// provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable);
}
return provider;
}
}
-->
如果缓存中有IContentProvider对象,则从缓存中获取;否则从远程调用AMS获取IContentProvider对象。
缓存的放入时机也就是上面调用installProvider后放入的,获取到IContentProvider后会通过isBinderAlive判断Binder是否存活,同时进行引用计数+1,因为有可能有多个远端调用同一个Provider。
我们再看一下是如何从AMS获取IContentProvider的:
<!--
//ActivityManagerService.java
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String callingPackage, String name, int userId,
boolean stable) {
// traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getContentProvider: ", name);
try {
return mCpHelper.getContentProvider(caller, callingPackage, name, userId, stable);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
//ContentProviderHelper.java
ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
String name, int userId, boolean stable) {
...
// The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
// with cross-user grant.
final int callingUid = Binder.getCallingUid();
if (callingPackage != null && mService.mAppOpsService.checkPackage(
callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) {
throw new SecurityException("Given calling package " + callingPackage
+ " does not match caller's uid " + callingUid);
}
return getContentProviderImpl(caller, name, null, callingUid, callingPackage,
null, stable, userId);
}
-->
熟悉AIDl流程的我们知道客户端拿到远程返回的接口后必须要调用asInterface包装为Proxy后才能真正可用,那是在哪里包装IContentProvider的呢, 我们从 ContentProviderHolder 的构造中可以得到答案:
//ContentProviderHolder.java
@UnsupportedAppUsage
private ContentProviderHolder(Parcel source) {
info = ProviderInfo.CREATOR.createFromParcel(source);
provider = ContentProviderNative.asInterface( //内部会优先调用queryLocalInterface查询本地可用接口,避免无谓的跨进程,然后才使用 ContentProviderProxy 包装
source.readStrongBinder());
connection = source.readStrongBinder();
noReleaseNeeded = source.readInt() != 0;
mLocal = source.readInt() != 0;
}
内部会优先调用queryLocalInterface查询本地可用接口,避免无谓的跨进程,然后才使用 ContentProviderProxy 包装。
客户端拿到IContentProvider后远程调用query方法,实际上调用的是对应服务端ContentProvider.Transport的query方法,咱们看一下服务端Transport的实现:
<!--ContentProvider.java
class Transport extends ContentProviderNative {//Transport是实现了ContentProviderNative的内部类,这也是标准的系统AIDL做法
...
@Override
public Cursor query(@NonNull AttributionSource attributionSource, Uri uri,
...) {
...
//mInterface也就是服务端ContentProvider.this实例本身
return mInterface.query(
uri, projection, queryArgs,
CancellationSignal.fromTransport(cancellationSignal));
}
}
-->
这样就完成了客户端远程调用ContentProvider的query方法的整个过程。
3.小结
调用ContentProvider的CRUD方法是需要跨进程调用的,他有自己的AIDL接口,这点和同为四大组件的Activity不一样,Activity没有自己的AIDL接口。
其中客户端查找可用的 IContentProvider远程句柄的过程优先会用本地缓存,如果没有找到就会借助AMS完成查找。 查到到句柄后就可以直接和远程服务端通信了。
由此也得出调用线程的关系:
1.如果客户端和服务端在同一个进程,onCreate方法运行在主线程,CRUD方法运行在客户端的调用线程,因为queryLocalInterface的存在所以这就是个简单的本地调用。
2.如果客户端和服务端不在同一个进程,onCreate方法运行在服务端主线程,CRUD方法运行在服务端的Binder线程,这也是AIDL跨进程调用的规律。
四、问题回答
1.ContentProvider和Application的启动时机关系?
Application的attachBaseContext方法执行在ContentProvider初始化和onCreate调用之前,Application的onCreate方法执行在ContentProvider初始化和onCreate调用之后。
如示意图:
2.如何控制多个ContenProvider的启动顺序?
可以使用initOrder声明初始化顺序
<provider
android:authorities="com.sivan.DContentProvider"
android:exported="false"
android:multiprocess="true"
android:initOrder="100"//允许负数
android:name=".DContentProvider" />
3.系统如何监测ContentProvider类型的ANR?
ContentProvider 的生命周期默认在 Application onCreate() 之前,而且都是在主线程创建的。
因此自定义的 ContentProvider 类的构造函数、静态代码块、onCreate 函数都尽量不要做耗时的操作,会拖慢启动速度 。
那这个ANR消息是在哪里监测的呢?
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
...
//获取应用中注册的ContentProvider数据
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
msg.obj = app;
mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);//10s
}
//通过oneway异步类型的binder调用应用进程ActivityThread#IApplicationThread#bindApplication接口
thread.bindApplication(processName, appInfo, providers,
return true;
}
我们从这里可以得到答案:
AMS会在 attachApplicationLocked 方法中发出一个延时10s的消息 CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG
该 Message在所有 Provider 成功启动后的 ActivityManagerService#publishContentProviders 移除。
4.ContentProvider的Context有何特殊之处?
ContentProvider本身不属于 Context 体系结构,因此创建 ContentProvider 实例时所用的 Context 实例需要由别处获得。
Context体系结构如图:
那在Provider内调用getConetxt获取到的是什么呢?让我们重新看一下installProvider方法:
<!--/*frameworks/base/core/java/android/app/ActivityThread.java*/
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
...
if (holder == null || holder.provider == null) {
...
//根据不同使用场景,获取对应场景下的 Context 实例
if (context.getPackageName().equals(ai.packageName)) {
c = context;// 包名相同即同一应用内,则使用入参 Application 作为 Context
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {//创建属于ContentProvider所在包的上下文
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
...
localProvider.attachInfo(c, info);
}
...
}
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
...
mContext = context;
ContentProvider.this.onCreate();
...
}
public final @Nullable Context getContext() {
return mContext;
}
-->
结论:
同一个应用程序内,ContentProvider 的成员变量 mContext 等于当前应用程序的 Application。
如果跨进程或者通过 Intent # setPackage() 指定了其它应用的包名等,则需要获取对应包名对应的 Context 实例对象
同理BroadCastReceiver也很特殊,他的onReceive方法的Context参数等于调用动态注册时 a.registerReceiver 所在的a本身
BroadcastReceiver testReceiver=new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
...
}
};
5.ContentProvider各方法的调用所处的线程?
a.如果客户端和服务端在同一个进程
onCreate方法运行在主线程,CRUD方法运行在客户端的调用线程,因为queryLocalInterface的存在所以这就是个简单的本地调用。
b.如果客户端和服务端不在同一个进程
onCreate方法运行在服务端主线程,CRUD方法运行在服务端的Binder线程,这也是AIDL跨进程调用的规律
6.CRUD方法和call方法差异?
ContentProvider跨进程执行CRUD操作时,利用了 Android 的 Binder 和匿名共享内存机制。
Binder 传递 CursorWindow 对象内部的匿名共享内存的文件描述符,这样在跨进程传输中 ,结果数据并不需要跨进程传输,而是在不同进程中通过传输的匿名共享内存文件描述符来操作同一块匿名内存,这样来实现不同进程访问相同数据的目的 。
基于 mmap 的匿名共享内存机制也是有代价的。当传输的数据量非常小的时候,可能不一定划算。所以 ContentProvider 提供了一种 call 函数,它会直接通过 Binder 来传输数据。
ContentProvider 的接口调用参数和 call 函数调用并没有使用匿名共享机制,Binder有1024的限制,传输数据如果过大,就会抛出异常。
匿名共享内存有代价是因为它的创建过程和映射过程都是需要开销的。
这一点我发现我们项目里面有部分调用就存在选择不当的问题,状态数据也在用CRUD操作,应该用call替代更好。
7.ContentProvider的其他用法?
经常会遇到 Library 都需要传入 Context 参数以完成初始化,此时这个 Context 参数一般会从 Application 对象的 onCreate 方法中获取。于是,很多 library 都会提供一个 init 方法,在Application Object中完成调用,此方法依赖方需要传递 Context 对象给库进行初始化,不利于组件化,代码耦合。
那有没有可能让三方库不依赖Application,并且在自己库的内部就拿到Context完成初始化呢,答案就是ContentProvider。
下面举出一些基于 ContentProvider 机制实现无侵入地获取 Context 的例子:
:例如Lifecycle组件就声明了一个 ProcessLifecycleOwnerInitializer 用于提前获取Context注册生命周期监听
:LeakCanary
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
:Picasso
8.ContentProvider的优化?
Provider是一开机就起来的,过多的Provider和内部复杂的初始化逻辑会严重影响启动速度和开机内存,因此优化是很有必要的。
可以看一下如下的几种优化手段:
a:ContentProvider业务延迟初始化
Provider是一开机就起来的,并不是所有的都是开机时段就立即刚需的因此优化很必要,我们就可以将相关的provider的初始化功能移除,并将其托管起来,并且按需加载,用到该模块的时候再进行相关类的注册和加载。
可以看到我们项目中的实践:
<!--
val componentKeyList = CopyOnWriteArrayList<String>()
interface ICardComponent {
fun initial(content:Context)
}
//ContentProvider的实现类
abstract class xxLazyProvider : xxProvider(), KoinComponent, ICardComponent {
val TAG = this@xxLazyProvider .javaClass.simpleName
init {//随进程起来就会调用
DebugLog.d(TAG, "init inject module:${TAG} ...")
loadKoinModules(module {
factory<ICardComponent>(qualifier = named(TAG)) {//将当前实例注册到Koin
this@xxLazyProvider
}
})
componentKeyList.add(TAG)//收集所有的实例对应的Name,用于根据name注入后调用initial方法
}
override fun initial(content: Context) {//延迟调用
...
}
}
//如果权限声明同意后再遍历调用触发所有Provider的真实初始化
componentKeyList.forEach { key ->
Injector.factoryByName<ICardComponent>(key)?.initial(this)
}
-->
b:去除或者合并ContentProvider
有些provider就被用来作为模块初始化使用,并不是作为数据提供,比如作为koin的注册等,可以使用StartUp将这部分重复功能的Provider去掉,合并使用StartUp的Provider或者手动初始化
避免为每个需要初始化的组件都单独定义一个ContentProvider,从而大大应用的启动时间。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="leavesc.lifecyclecore.core.InitializerA"
android:value="androidx.startup" />
</provider>
我们项目目前采用的优化方案是用了一个自研的插件KoinModuleFinder在编译时收集需要注册的实例,在Application的attachBaseContext中统一调用进行注册。这样能解决Koin注册这种静态的场景,但无法解决子模块需要获取Conext进行初始化的场景,例如上述的第7个问题。
五、参考资料
【Android之ContentProvider的启动过程源码分析】blog.csdn.net/zhangyongfe…
【Android应用启动全流程分析(源码深度剖析)】
【你真的懂ContentProvider么】