从源码看Application的启动流程
开篇废话
开篇废话就真的是废话,强烈建议直接跳过。
一直看重用户体验的我,很想做好一款超级好用的产品,但我只是一个默默开发的开发,一切都要跟产品大佬的奇葩需求走(这里也体谅一下产品吧,产品也是跟用户走的,还要考虑收益等等问题)。好多好多年前就开始用CSDN了,可能是早期用户的原因吧,博客质量不怎么样,但是总排名在前万名,可以看到数字,1w+就不显示数字了。一开始的CSDN是很丑的,没有支持MarkDown,排版不好看,书写也不方便,后来发现了简书,简书是超级好看,虐爆CSDN,所以后来的博客就都发表在简书上面了。但是简书毕竟太杂了,打开首页满屏鸡汤,还是CSDN专业一点,SEO也超好。一不小心回来CSDN看了一下,发现也默认使用MarkDown了啊,排版也超好看啊,甚至比简书好看多了,简书那个黑色的代码框超难看,还不能修改,还是CSDN的灰色好看,于是我又回来了!以后努力写更多有用又好看的博客吧!尽管CSDN的博客页各种广告,留言排版各种难看,个人中心通知各种乱七八糟。
大致流程
image详细流程
image查看源码方法
1、直接下载源码,之前写了篇下载源码的文章,不需要全部下载,只下载你需要的部分就行了,这样就不会几个G几个G的了,几百M而已,文章:超级简单的Android源码下载 。
2、直接在AndroidStudio
中双击shift
键搜索文件就行了。需要写注释的话,直接在源码上面写,它会弹框问你允不允许修改源码的。推荐这种方法,可以看到基本上你想看的源码了,那些看不到的可以结合线上看,链接 :http://androidxref.com
源码分析(基于API28)
一个应用的入口就是main
方法了吧,这里直接从ActivityThread
的main
方法开始看吧。
public static void main(String[] args) {
Looper.prepareMainLooper();
// ...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// ...
Looper.loop();
}
main方法重点就是这几行,Looper.prepareMainLooper()
和Looper.loop()
可以看出这些代码就是执行在应用进程了,而且就是主线程。loop之前创建了ActivityThread
对象,并调用了attach
方法,跟进看一下。
private void attach(boolean system, long startSeq) {
// ...
final IActivityManager mgr = ActivityManager.getService();
mgr.attachApplication(mAppThread, startSeq);
// ...
}
ActivityManager.getService()
拿到的是IActivityManager
,它是ActivityManagerService
的代理对象,也就是在AMS进程中了。值得注意的是这里传入了个mAppThread
,对应的类是ApplicationThread
,ActivityThread的一个内部类,后面AMS会回调此类。继续看attachApplication
,这部分代码在ActivityManagerService
里面。大概意思就是通知AMS获取和生成当前应用进程的相关信息,并进行绑定,以便管理。
附1:IActivityManager.aidl源码
附2:IApplicationThread.aidl源码
@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
// ...
// thread 是前面传过来的ApplicationThread
attachApplicationLocked(thread, callingPid, callingUid, startSeq);
// ...
}
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// ...
// 调用应用自身的进程bindApplication
thread.bindApplication(processName, appInfo, providers,
app.instr.mClass,
profilerInfo, app.instr.mArguments,
app.instr.mWatcher,
app.instr.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, isAutofillCompatEnabled);
// ...
// 下面的流程是启动Activity的了,此文先不做分析
if (mStackSupervisor.attachApplicationLocked(app)) {...}
// ...
}
thread
仍然是前面传过来的ApplicationThread
,调用bindApplication
方法通知应用进程启动Application
。我们回到ApplicationThread
的bindApplication
方法看看,回到了应用自身的进程了。
疑问:bindApplication应该是异步执行的,所以bindApplication和attachApplicationLocked应该是同步执行的。
public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial, boolean autofillCompatibilityEnabled) {
// ...
AppBindData data = new AppBindData();
// ...
sendMessage(H.BIND_APPLICATION, data);
}
bindApplication
是AMS进程远程调用,有oneway
修饰,应该是执行在子线程的,这里通过Handler
转到主线程执行,发送了H.BIND_APPLICATION
消息,继续进入handleMessage
看看。
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
// ...
break;
// ...
}
}
private void handleBindApplication(AppBindData data) {
// ...
mBoundApplication = data;
// ...
// data.info就是一个LoadedApk,虽然我也不知道是干嘛用的
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
// ...
Application app;
app = data.info.makeApplication(data.restrictedBackupMode, null);
// ...
}
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
// ...
Application app = null;
// 在manifest里面配置的Application类,没配就是默认的android.app.Application
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
// ...
try {
java.lang.ClassLoader cl = getClassLoader();
// ...
// 这个就是ApplicationContext
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 这里创建了Application对象
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
} catch (Exception e) {...}
// ...
if (instrumentation != null) {
try {
// 调用Application的onCreate方法
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {...}
}
// ...
}
从这里可以看出,Application
对象是用反射创建出来的。而且是在应用进程的主线程上创建的。先继续进入Instrumentation
的newApplication
方法看看,再回来看instrumentation.callApplicationOnCreate(app);
。
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;
}
final void attach(Context context) {
attachBaseContext(context);
// ...
}
这里调用了attachBaseContext
方法,这个方法也是常用的,比如:MultiDex.install(base)
就是写在这个方法里面。
接下来我们回到刚才那个instrumentation.callApplicationOnCreate(app);
,instrumentation是ActivityThread的一个变量,对应的类是Instrumentation
,直接进入这个类的callApplicationOnCreate
方法。
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
到这里就是我们熟悉的Application.onCreate()
了,对于一个Android应用的入口就是这里了,跑完onCreate()
就会启动Activity
,或者Service
等。
顺便说一个初学者大多不知道的点,Application
和应用不是一一对应的,如果一个应用有多个进程,那么每个进程都会有一个Application
,进程启动的时候都会先创建Application
,先跑onCreate()
方法。所以初始化一些东西的时候,最好判断一下当前是不是主进程,避免重复进行一些不必要的初始化。还有就是在应用没起来的时候启动Service
或者收到广播,都会先创建Application
,再做其它的事情。
写在最后
以前看源码总是乱七八糟的,跳来跳去,还没跟到最后就跟丢了,就算勉强看完整个流程了,过两天又忘记了,再次看依然要花很大的力气,一直都在心里骂谷歌的工程师写的都是什么破代码,后来发现用时序图把源码流程画出来之后清晰多了,一目了然,需要的时候看一下图就清楚了,了解某一个点的细节直接从指定位置开始看源码就行。时序图,以后你就是我女朋友了。