App启动流程
文章目录
前言
作为 Android 用户,我们会点击桌面图标,然后一个 app 就启动了了,在这个过程中到底发生了什么呢?换句话说 Android app 是怎么启动的呢?如果和我有一样的好奇,我们来一起走进代码的细节中了解一下。
桌面 app
我们平时都不会管 Framework 层,自然也不会去写桌面 app。那我给大家看一看系统带的桌面Activity是怎么启动我们的 Activity 的.
Intent intent = new Intent(mIntent);
ListItem item = mActivitiesList.get(position);
intent.setClassName(item.packageName, item.className);
if (item.extras != null) {
intent.putExtras(item.extras);
}
startActivity(intent);
原来也是调用startActivity,不过是隐式调用,通过包名和类名来启动对应的Activity,可是,这时候还没有Application呢,怎么能启动Activity呢?看过我前一篇文章的读者们会想到,这个过程中启动了Application,那我们就来看看这是如何启动的。
启动 Application
上篇 Activity 启动流程中的文章中提到,在流程图中,有一个方法很重要,它是startSpecificActivityLocked,因为上一篇讲的是Activity启动流程,所以这里和 App 启动相关的地方就略过没讲,既然上一篇画了流程图,这一篇我们就细细讲一下 App 的启动。
先看一下startSpecificActivityLocked引出的启动 App 进程。
这个方法我就不贴代码了,简单来说,如果进程存在,就启动 Activity,若不存在,调用下面的方法启动进程。我们就直接讲启动进程的方法。
ActivityManagerService startProcessLocked
boolean isActivityProcess = (entryPoint == null);
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs);
这里是这个方法的核心过程, 调用Process.start方法启动一个进程。
看到这里,相信能解决不少开发者拥有的共同疑惑:
为什么看到某些博客会说ActivityThread的main方法是 application 启动的时候调用的main方法呢?
原来调用Process.start的时候指定了 entryPoint 为android.app.ActivityThread,所以,这里启动之后就会调用ActivityThread的main方法。
这里就像我们平时写 Java 程序一样,启动一个程序,会分配一个单独的 Java Runtime,从main方法开始执行。
那么我们就继续跟入,Process.start就是调用了startViaZygote方法,这个方法的注释是
// Starts a new process via the zygote mechanism
通过zygote机制启动新进程
这个方法忽视掉增加参数的过程之后,这里也就一个函数
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
看看函数名openZygoteSocketIfNeeded,openZygoteSocket,嗯,代码跟进去就像函数名说的,通过 Socket 和 Zygote 交互,将需要的参数通过 socket 传给 Zygote,它就会创建好我们需要的 App 进程。
这样就返回了一个新进程,ActivityThread的main方法就要开始执行了。
ActivityThread main
public static void main(String[] args) {
Environment.initForCurrentUser();
AndroidKeyStoreProvider.install();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper(); // 初始化不允许退出的MainLoop
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 使用liunx epoll开始死循环等待消息
// 大家有兴趣的话看MessageQueue的nativePollOnce方法
// 对应的c++代码 android_os_MessageQueue.cpp
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这个代码相信大家在很多博客都见过,在这篇文章中不重要的代码解析放在了代码的注释中。
下面我们来看看这个重要的方法
thread.attach(false);
ActivityThread attach
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
}
}
}
});
这里重要方法就是mgr.attachApplication(mAppThread);,但是多复制了一些代码,为了提一下 GC,什么时候虚拟机进行 GC 呢?这里给了我们答案,在使用超过 3⁄4 最大内存的时候。
继续回来跟代码,和前面一样,这里使用的是ActivityManagerService作为IActivityManager,
mgr.attachApplication(mAppThread)这个方法就是调用了attachApplicationLocked方法。
ensurePackageDexOpt(app.instrumentationInfo != null
? app.instrumentationInfo.packageName
: app.info.packageName);
if (app.instrumentationClass != null) {
ensurePackageDexOpt(app.instrumentationClass.getPackageName());
}
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
这里主要是两段,第一段就是单纯为了解惑 dexOpt 是什么时候生成的呢?就是这个时候。
后面是一个方法: ActivityThread的bindApplication方法。
在忽视bindApplication方法中的一些初始化之后,就做了一件事,给H这个Handler发送BIND_APPLICATION消息,然后H会调用handleBindApplication方法。
ActivityThread handleBindApplication
// If the app is Honeycomb MR1 or earlier, switch its AsyncTask
// implementation to use the pool executor. Normally, we use the
// serialized executor as the default. This has to happen in the
// main thread so the main looper is set right.
if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
Message.updateCheckRecycle(data.appInfo.targetSdkVersion);
if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
} else {
// Small heap, clamp to the current growth limit and let the heap release
// pages after the growth limit to the non growth limit capacity. b/18387825
dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
}
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
List<ProviderInfo> providers = data.providers;
if (providers != null) {
installContentProviders(app, providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
mInstrumentation.callApplicationOnCreate(app);
这里放这些代码,但是只会详解生成Application的函数:
data.info.makeApplication(data.restrictedBackupMode, null)。
剩下的我们就提以下几点:
AsyncTask在不同的targetSdkVersion中行为不同,为什么呢,不止 AsyncTask 代码的不同,还因为这里修改了线程池的实现。targetSdkVersion是 app 运行时拿到的 sdk 版本信息之一,另一个是minSdkVersion,所以,Google 对 app 的限制就全加在了targetSdkVersion上。- 有些 app 会设置
FLAG_LARGE_HEAP属性,这个属性具体做了什么呢?从这里就是线索。 ContentProvider的初始化原来是在Application的onCreate之前attachBaseContext之后。Application的生命周期也是由Instrumentation来控制的
看完了上面这几点,我们详细看一下生成Application的函数:
LoadedApk makeApplication
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
java.lang.ClassLoader cl = getClassLoader();
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
这就是我们需要关注的这个方法的内容。
你没有声明自己的Application的话,将会使用系统默认的android.app.Application。
Application的初始化依旧是反射,通过clazz.newInstance()来生成,这也就说明了,为什么我们不能写带参数的构造函数。
其次,生成Application的时候还会回调attachBaseContext方法,这里也就成为了一个 App 最早执行代码的位置。这样,一个Application就生成并启动了。
到这里,ActivityThread的bindApplication方法就告一段落了。也就意味着整个Application的启动完成了。
总结
这样,我们就大概了解了一个 App 从点击桌面 icon,到Application完全启动的过程。
了解这个过程,首先让我们对 Manifest 中各种属性有了更深的了解;其次,了解了 Android 代码的执行过程,以写 Java 程序的视角来看 Android 程序就不再那么奇怪了;最后,有了对源码的了解,你才能写出更好的代码。