系列专栏:
- 安卓高频面经解析大全专栏链接:150道安卓高频面试题全解析
- 安卓高频面经解析大全目录详情 : 安卓面经_anroid面经_150道安卓常见基础面试题全解析
- 安卓系统Framework面经专栏:Android系统Framework面试题解析大全
- 安卓系统Framework面经目录详情:Android系统面经_Framework开发面经_150道面试题答案解析
- Android进阶知识体系解析专栏链接:Android进阶知识体系解析
- Android进阶知识体系解析目录详情:Android进阶知识体系解析_20大安卓进阶必备知识点
- 嵌嵌入式面经解析大全专栏链接 :嵌入式面经C++软件开发面经111道面试全解析
- 嵌入式面经解析大全目录详情 : 嵌入式面经111道面试题全解析C/C++可参考
本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对常见安卓高频开发面试题的理解;
网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,承诺提供专栏内容免费技术答疑,直接咨询即可。助您提高安卓面试准备效率,为您面试保驾护航!
正文开始⬇
应用程序的启动流程,是较为高频的面试题,因为涉及的知识点比较多,适合考验一个人安卓技术的掌握程度,不过原理也比较复杂。本人也被问过这题目,不过当时只是简单地但全面的描述了从桌面点击应用图标到应用启动的完整流程,不涉及具体源码,最终面试官也还是很满意的。面试官可能会问:
- 你知道桌面点击应用图标到应用显示期间系统实现了什么流程吗?⭐⭐⭐⭐
- 冷启动和热启动的区别 ⭐⭐
- 桌面点击启动APP和从另一个程序跳转过去有什么区别?⭐⭐
目录
- 1、流程概述
- 2、基础知识
- 3、启动流程
- Step1 点击图标
- Step2 创建App进程
- Step3:绑定Application
- Step4:scheduleLaunchActivity()
- Step5 启动Activity
- 4、桌面点击启动APP和从另一个程序跳转过去有什么区别?
1、流程概述
从点击桌面的APP图标,到APP主页显示出来,大致会经过以下流程:
- 点击桌面App图标,Launcher进程采用Binder跨进程机制向system_server进程发起startActivity请求;
- system_server进程接收到请求后,向Zygote进程发送创建进程的请求,Zygote进程fork出新的子进程,即新启动的App进程;
- App进程,通过Binder机制向sytem_server进程发起attachApplication请求(绑定Application);
- system_server进程在收到请求后,进行一系列准备工作后,再通过binder机制向App进程发送scheduleLaunchActivity请求;
- App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息。主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()/onStart()/onResume()等方法,经过UI渲染结束后便可以看到App的主界面。
2、基础知识
文章开头就提到,APP启动流程涉及的知识比较多,在此总结一下各个基础知识:
- 冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动,下文讲述的APP启动流程属于冷启动;
- 热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动;
- 一个APP就是一个单独的进程,对应一个单独的Dalvik虚拟机;
- Launcher:我们打开手机桌面,手机桌面其实就是一个系统应用程序,这个应用程序就叫做“Launcher”。同样的,下拉菜单其实也是一个应用程序,叫做“SystemUI”;
- Binder:跨进程通讯的一种方式,在本系列文章《Binder》一文有详述;
- Zygote:Android系统基于Linux内核,当Linux内核加载后会启动一个叫“init”的进程,并fork出“Zygote”进程。Zygote意为“受精卵”,无论是系统服务进程,如ActivityManagerService、PackageManagerService、WindowManagerService等等,还是用户启动的APP进程,都是由Zygote进程fork出来的;
- system_server:系统服务进程,也是Zygote进程fork出来的。该进程和Zygote进程是Android系统中最重要的两个进程,系统服务ActivityManagerService、PackageManagerService、WindowManagerService等等都是在system_server中启动的;
- ActivityManagerService:活动管理服务,简称AMS,负责系统中所有的Activity的管理;
- App与AMS通过Binder进行跨进程通信,AMS与Zygote通过Socket进行跨进程通信;
- Instrumentation:主要用来监控应用程序和系统的交互,是完成对Application和Activity初始化和生命周期的工具类。每个Activity都持有Instrumentation对象的一个引用,但是整个进程只会存在一个Instrumentation对象;
- ActivityThread:依赖于UI线程,ActivityThread不是指一个线程,而是运行在主线程的一个对象。App和AMS是通过Binder传递信息的,那么ActivityThread就是专门与AMS的外交工作的。ActivityThread是APP的真正入口,APP启动后从ActivityThread的main()函数开始运行;
- ActivityStack:Activity在AMS的栈管理,用来记录经启动的Activity的先后关系,状态信息等。通过ActivtyStack决定是否需要启动新的进程;
- ApplicationThread:是ActivityThread的内部类,是ActivityThread和ActivityManagerServie交互的中间桥梁。在ActivityManagerSevice需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通信;
3、启动流程
本文主要讲解apk启动的流程,在面试中可以将完整流程讲出来,最多讲到几个关键函数,已经非常好了,因此下文尽量不涉及源码,需要学习源码的同学可自行百度或者参考:《startActivity启动过程分析》
Step1 点击图标
系统启动过程中,会启动PMS服务,该服务会扫描解析系统中所有APP的AndroidManifest文件,在Launcher应用启动后,会将每个APP的图标和相关启动信息封装在一起。回想平时应用开发中,启动一个新的Activity是通过startAcitvity()方法。因此在桌面点击应用图标,也是在Luancher这个应用程序里面根据当前点击的APP的启动信息,执行startAcitvity()方法,通过Binder通信,最后调用ActivityManagerService的startActivity()方法。流程图如下:
Step2 创建App进程
从AMS的startActivity()方法经过一系列逻辑,如下图所示:
执行到startSpecificActivityLocked()方法:
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
r.task.stack.setLaunchTime(r);
if (app != null && app.thread != null) {//1
try {
if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
|| !"android".equals(r.info.packageName)) {
app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
mService.mProcessStats);
}
realStartActivityLocked(r, app, andResume, checkConfig);//2
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting activity "
+ r.intent.getComponent().flattenToShortString(), e);
}
}
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false, true); //3
}
代码命名还是可以清晰地看出逻辑,在[注释1]
如果当前Activity所在的Application有运行的话,会执行注释2处的代码,也就是“真正的startActivity”,所以[注释2]
的函数名为:“realStartActivityLocked()”。然而如果是第一次启动app,则app是‘null’,因此代码会执行到[注释3]
的mService.startProcessLocked()方法。接着又经过一系列调用,如下图:
主要流程是通过socket通道传递参数给Zygote进程,让Zygote创建出新的App进程。最后执行到了applicationInit()方法,并调用invokeStaticMain()方法:
private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
Class<?> cl;
try {
cl = Class.forName(className, true, classLoader);//4
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
"Missing class when invoking static main " + className,
ex);
}
Method m;
try {
m = cl.getMethod("main", new Class[] { String[].class });//5
} catch (NoSuchMethodException ex) {
throw new RuntimeException(
"Missing static main on " + className, ex);
}
...
throw new ZygoteInit.MethodAndArgsCaller(m, argv);//6
}
传入的型参className是“android.app.ActivityThread“,从代码看,是通过反射来获取到android.app.ActivityThread类的实例,并运行该类中的main()方法。到此应用程序的进程就完成了,并运行了代表主线程的ActivityThread类的main()方法.,
Step3:绑定Application
在main()方法里主要做了两件事:
- 依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环;
- 调用attach()方法,将给该新建的APP进程和指定的Application绑定起来;
/frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq); //7
...
Looper.loop();
阅读源码,[注释7]
thread.attach()之后的调用逻辑如下:
attachApplication() -> attachApplicationLocked() -> bindApplication()
bindApplication()会发送BIND_APPLICATION的消息到消息队列中, 最终通过handleBindApplication()方法处理该消息. 然后调用makeApplication()方法来加载App的classes到内存中。
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(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
Step4:scheduleLaunchActivity()
经过Step2 - Step3后,系统就有了当前新APP的进程了,接下里的调用顺序就相当于从一个已经存在的进程中启动一个新的Actvity。因此回到Step2,如果当前Activity所在的Application有运行的话,就会执行[注释2]
的realStartActivityLocked()方法,并调用scheduleLaunchActivity();
//frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
...
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
...
return true;
}
Step5 启动Activity
scheduleLaunchActivity()发送一个LAUNCH_ACTIVITY消息到消息队列中, 通过 handleLaunchActivity()来处理该消息。在 handleLaunchActivity()通过performLaunchActiivty()方法回调Activity的onCreate()方法和onStart()方法,然后通过handleResumeActivity()方法,回调Activity的onResume()方法,最终显示Activity界面。
scheduleLaunchActivity
4、桌面点击启动APP和从另一个程序跳转过去有什么区别?
两种方法的原理都是通过startActivity()方法,唯一的区别在于桌面点击启动APP时的intent的action相对单一,而从另一个程序跳转或者启动的样式可能更多种。