这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
之前的学习记录都是在自己的本地,借着掘金的这次8月活动的机会,也挑战一下自己。各位看官点点赞,小弟先谢过。
有一说一ContentProvider这个东西我平时用的是真的不多,但是这玩意毕竟是安卓四大组件之一,他的作用不可忽视。怎么使用他是我们作为安卓开发者必须掌握的技能之一。本文将在源码的角度上,分析ContentProvider初始化流程.
关于ContentProvider的题外话
ContentProvider作为四大组件之一,作用是IPC(跨进程通信),底层实现是Binder,经过系统的封装后,ContentProvider的使用方式已经很简单了我就不啰嗦了。实不相瞒,我头一次使用ContentProvider并非是拿来做进程通信,而是在一次项目架构升级的时候,作为子模块初始化使用。
当项目迭代到一定程度后,项目的编译会占用我们开发的很多时间,刚入坑安卓开发的时候我也不太理解为什么很多博主强调加编译速度,直到我们的项目,一次运行编译需要20分钟以上的时候,才意识到需要做编译优化了,编译优化有很多点,这里不一一描述,这里只说一点:使用ContentProvider代替Application做初始化。
项目之前对于一些常用的模块,都作为app的子模块依赖,子模块有自己的Application,使用Appjoint的方式,在子模块的Application上加注解,使用方式如下:
@ModuleSpec(priority = 10)
class UmengApplication : Application() {
}
但是后面我为了加快编译速度,决定将一些相对固定的子模块打成aar的方式依赖,比如底层网络模块,数据库模块,友盟等等。因为前期开发这些模块是需要不停修改的,慢慢的这些模块趋于稳定了,就可以打成aar了,然后在在gradle做判断,如果某个开发需要修改则进行源码依赖,否则使用aar依赖。将子模块打成aar后,我发现子模块的Application不会初始化了,因为AppJoint不支持。所以想到了使用ContentProvider来进行子模块的初始化操作。
正题:ContentProvider
ContentProvider初始化时机
在之前的# Android 11源码分析: Application的初始化流程一文中我曾提到过,后面会专门有一篇文章介绍ContentProvider初始化。 当时是在讲到在H接受到初始化Application的消息时触发handleBindApplication方法的执行,内部进行Application的创建和执行onCreate,再来看看这段代码
android.app.ActivityThread
......
try {
1 通过反射获取Instrumentation
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
......
Application app;
......
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
2.创建Application(内部也是反射)
app = data.info.makeApplication(data.restrictedBackupMode, null);
......
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
本文关注点:初始化ContentProvider
installContentProviders(app, data.providers);
}
}
try {
3.初始化Application
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}finally {
......
}
}
......
}
可以看到,执行初始化ContentProvider的时机在makeApplication和callApplicationOnCreate直接,那最起码可以得出一个结论:ContentProvider初始化的时机在Application的onCreate之前
有问题么,没有问题。
那么问题来了,啥叫最起码得出一个结论,这不是还有别的结论吗? 是的。因为前面还提到了在makeApplication之后啊。 再去看看Instrumentatio创建Application做了啥和有关的事就知道了
android.app.Instrumentation
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;
}
之前并没有提到app.attach(context)这句代码,今天就去一探究竟。
android.app.Application
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
对的。。我说了这么多,就是下最后的结论:ContentProvider初始化的时机在Application的attachBaseContext之后,在onCreate之前
ContentProvider初始化做了什么
想要弄明白ContentProvider初始化做了什么,那就需要去installContentProviders方法看一看了。
android.app.ActivityThread
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
遍历ProviderInfo列表
for (ProviderInfo cpi : providers) {
if (DEBUG_PROVIDER) {
StringBuilder buf = new StringBuilder(128);
buf.append("Pub ");
buf.append(cpi.authority);
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
}
各自启动
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
启动后放入数组中
results.add(cph);
}
}
try {
将数组传给AMS
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
installContentProviders完成了通过installProvider方法完成ContentProvider的启动,并且将启动了的ContentProvider放在了数组中,传递给了AMS,AMS内部会将他们存起来,这样外部调用者就可以直接从AMS中获取ContentProvider了。
android.app.ActivityThread
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
......
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
localProvider.attachInfo(c, info);
......
}
通过反射创建ContentProvider对象,调用attachInfo方法
android.content.ContentProvider
public void attachInfo(Context context, ProviderInfo info, boolean testing) {
......
ContentProvider.this.onCreate();
}
attachInfo方法会调用上参数的同名方法,执行ContentProvider的onCreate,这也就意味着ContentProvider的启动完成了
使用ContentProvider初始化项目需要注意的地方
- 因为ContentProvider的onCreate() 在Application 的 onCreate() 方法之前,所以如果有其他模块的初始化,需要考虑是否会有初始化顺序的问题。
- 有一些第三方库,例如友盟,他其实是要求在Application的onCreat方法中进行初始化的,所以使用ContentProvider初始化的不行的。其实利用门面模式,封装一个UmManager,在主项目Application中初始化