唉,最近面试被问到这个问题,好久没温书细节记不清呀,记录一下吧。
冷启动
1. 用户点击应用图标
当用户在主屏幕或应用列表中点击应用图标时,Launcher进程会接收到点击事件,并通过PackageManager获取应用的启动信息(如Activity的入口信息)。Launcher是Android系统的桌面应用,负责管理应用图标和启动应用。
2. Launcher 请求 AMS 启动应用
- Launcher会通过Binder机制向Activity Manager Service(AMS)发送启动应用的请求。AMS是Android系统中负责管理Activity生命周期、进程启动和调度等核心服务的系统组件。
- 具体来说,Launcher会调用AMS的相关接口,如
startActivity方法,并传递应用启动所需的信息,如应用的包名、启动Activity的类名等。
3. AMS检查应用进程是否存在
- AMS接收到Launcher的请求后,会首先检查目标应用的进程是否已经存在。由于是冷启动,此时应用进程通常还未创建。
- 如果进程不存在,AMS会向Zygote进程发送创建新进程的请求。
4. Zygote进程创建应用进程
- Zygote 是 Android 系统中的一个特殊进程,它在系统启动时就已经创建,并预先加载了一些常用的类和资源。
- 当 AMS 请求创建新的应用进程时,Zygote 会通过fork()系统调用复制自身来创建一个新的进程,这个新进程就是目标应用的进程。
- 这样做的好处是可以避免每次启动应用都重新加载系统资源,从而提高应用的启动速度。
AMS发起请求
- AMS通过Zygote的Socket接口(/dev/socket/zygote)向Zygote发送请求,告知需要启动一个新的应用进程。
- AMS使用ZygoteState类封装了与Zygote通信的状态信息,包括输入流和输出流。通过BufferedWriter将启动参数(如应用包名、ABI等)写入Socket,并调用flush()方法发送信号。
Zygote接收请求
- Zygote进程通过LocalServerSocket监听来自AMS的连接请求。当接收到请求时,Zygote会创建一个新的ZygoteConnection实例来处理该请求。
- Zygote从Socket中读取AMS发送的启动参数,并解析出需要启动的应用进程的信息。
Zygote fork子进程
- Zygote接收到启动请求后,会通过fork()系统创建一个新的子进程。子进程会基础Zygote的运行时环境(如JVM、核心库等),从而实现快速启动。
返回结果
- 子进程创建完成后,Zygote会通过Binder将子进程的PID和启动状态返回给AMS。
AMS处理结果
- AMS通过Binder接口接收Zygote的返回结果,并根据结果决定是否需要进一步的操作(如启动Activity等)
5. 应用进程初始化
- 新创建的应用进程启动后,会首先调用ActivityThread类的main()方法,这是应用进程的入口点。
- 在main()方法中,会创建一个Looper对象并启动消息循环,用于处理应用中的各种消息和事件。
- 同时,应用进程会通过 Binder 机制与 AMS 建立连接,告知 AMS 自己已经启动成功。
1. 调用 ActivityThread.main() 方法
当 Zygote 进程通过 fork() 系统调用创建出应用进程后,应用进程的入口便是 ActivityThread 类的 main() 方法。以下是 main() 方法的简化代码示例:
public static void main(String[] args) {
// 为当前线程创建 Looper
Looper.prepareMainLooper();
// 创建 ActivityThread 实例
ActivityThread thread = new ActivityThread();
// 调用 attach 方法,与 AMS 建立连接
thread.attach(false);
// 开启消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
2. 创建 Looper 对象并启动消息循环
2.1 Looper.prepareMainLooper()
- 该方法用于为当前线程(主线程)创建一个 Looper 对象。Looper 是 Android 消息机制的核心组件之一,它负责从消息队列中取出消息并分发给相应的处理者。
- 在 Android 中,每个线程只能有一个 Looper 对象,prepareMainLooper() 方法会确保主线程的 Looper 被正确初始化。
2.2 Looper.loop()
- 调用 loop() 方法后,Looper 会进入一个无限循环,不断从消息队列中取出消息并进行处理。
- 当有新的消息进入消息队列时,Looper 会将消息分发给对应的 Handler 进行处理。这样,应用就能处理各种事件,如用户的点击、系统的广播等。
3. 创建 ActivityThread 实例
在 main() 方法中,会创建一个 ActivityThread 类的实例。ActivityThread 是应用进程的核心类,它负责管理应用的所有 Activity、Service 等组件的生命周期。
4. 调用 attach() 方法与 AMS 建立连接
thread.attach(false)
- 调用 ActivityThread 实例的 attach() 方法,将应用进程与 AMS 建立连接。
- 在 attach() 方法中,会通过 Binder 机制向 AMS 发送消息,告知 AMS 应用进程已经启动成功。
- 同时,AMS 会将应用进程的相关信息记录下来,以便后续对应用进行管理和调度。
5. 初始化 Application 对象
- 在与 AMS 建立连接后,ActivityThread 会创建并初始化应用的 Application 对象。Application 是一个全局的单例对象,在应用的整个生命周期中只会存在一个实例。
- 会调用 Application 的 onCreate() 方法,开发者可以在这个方法中进行一些全局的初始化操作,如初始化第三方库、配置全局变量等。
6. 注册组件回调
- ActivityThread 会注册一些组件的回调,如 Activity、Service、BroadcastReceiver 等的生命周期回调。
- 这些回调会在相应组件的生命周期发生变化时被触发,从而实现对组件的管理和调度。
6. AMS 启动应用的主 Activity
- AMS 在得知应用进程启动成功后,会继续处理之前的启动请求,向应用进程发送启动主 Activity 的指令。
- 应用进程接收到指令后,会通过类加载器加载主 Activity 的类,并创建主 Activity 的实例。
- 接着,会调用主 Activity 的生命周期方法,如onCreate()、onStart()、onResume()等,完成 Activity 的初始化和显示。
1. AMS 接收到启动请求并检查相关信息
请求接收
当应用进程初始化完成并与 AMS 建立连接后,AMS 会继续处理之前 Launcher 发送的启动应用主 Activity 的请求。该请求包含了启动 Activity 所需的详细信息,如目标 Activity 的包名、类名、启动标志等。
信息检查
AMS 会对这些信息进行检查,验证请求的合法性,例如检查目标 Activity 是否在应用的 AndroidManifest.xml 文件中正确声明。
2. 检查 Activity 栈和任务栈
- 在 Android 中,Activity 栈用于管理 Activity 的生命周期和显示顺序,而任务栈则是一组相关 Activity 的集合。每个应用可能有一个或多个任务栈,每个任务栈中可以包含多个 Activity。
- AMS 会检查当前是否已经存在目标 Activity 所属的任务栈。如果存在,会根据启动标志(如
FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP等)决定如何处理该 Activity。例如,如果设置了FLAG_ACTIVITY_NEW_TASK,则可能会创建一个新的任务栈来启动该 Activity;如果设置了FLAG_ACTIVITY_CLEAR_TOP,则会清除任务栈中该 Activity 之上的所有 Activity。-
3. 创建 ActivityRecord 对象
- ActivityRecord 是 AMS 中用于表示一个 Activity 实例的对象,它包含了 Activity 的各种信息,如组件信息、状态信息、所属任务栈等。
- AMS 根据启动请求中的信息创建 ActivityRecord 对象,并将其添加到相应的任务栈和 Activity 栈中。
4. 检查应用进程状态
- AMS 会再次检查目标 Activity 所属的应用进程是否已经准备好。由于前面已经完成了应用进程的初始化,这里主要是确认进程是否正常运行,是否已经与 AMS 建立了有效的连接。
- 如果应用进程出现异常或未正常启动,AMS 可能会采取相应的措施,如重新启动应用进程或向用户显示错误信息。
5. 调用 ApplicationThread 的相关方法启动 Activity
- ApplicationThread 是应用进程中与 AMS 进行通信的桥梁,它通过 Binder 机制接收 AMS 发送的指令,并在应用进程中执行相应的操作。
- AMS 通过 Binder 调用 ApplicationThread 的 scheduleLaunchActivity 方法,将 ActivityRecord 对象和相关信息传递给应用进程。
6. 应用进程接收启动指令并处理
- 应用进程中的 ApplicationThread 接收到 scheduleLaunchActivity 方法的调用后,会将启动 Activity 的请求封装成一个消息发送到主线程的消息队列中。
- 主线程的 Looper 从消息队列中取出该消息,并调用 ActivityThread 的 handleLaunchActivity 方法来处理启动 Activity 的请求。
7. 加载 Activity 类并创建实例
- ActivityThread 会使用类加载器(通常是 PathClassLoader)加载目标 Activity 的类。类加载器会从应用的 APK 文件中查找并加载所需的类文件。
- 加载成功后,通过反射机制创建 Activity 的实例。反射机制允许在运行时动态创建对象和调用方法。
8. 调用 Activity 的生命周期方法
9. 渲染 Activity 界面
- 在 onCreate 方法中调用 setContentView 方法设置布局文件后,系统会对布局文件进行解析,将布局文件中的各个视图组件转换为实际的 View 对象。
- 解析完成后,系统会对这些 View 对象进行测量、布局和绘制操作,最终将 Activity 的界面显示在屏幕上。
7. 渲染界面
- 在主 Activity 的onCreate()方法中,通常会调用setContentView()方法来设置 Activity 的布局。
- 系统会对布局文件进行解析和渲染,将布局中的各个视图组件绘制到屏幕上,最终完成应用的启动,用户可以看到应用的界面。
1. 触发渲染
- Activity 启动:当 Activity 启动并调用 setContentView() 方法设置布局时,会触发界面的渲染。
- 数据更新:当视图的数据发生变化,例如调用 setText() 更新 TextView 的文本内容,可能会触发视图的重绘。
- 用户交互:如点击按钮、滑动屏幕等操作,可能会导致视图状态改变,从而触发渲染。
2. 测量阶段(Measure)
- 目的:确定每个视图的大小。
- 过程:父视图会调用子视图的 measure() 方法,并传递测量规格(MeasureSpec),子视图根据这个规格和自身的布局参数计算出自己的大小,然后将结果返回给父视图。父视图会根据子视图的大小进一步计算自身的大小,这个过程会递归进行,直到所有视图都完成测量。-
3. 布局阶段(Layout)
- 目的:确定每个视图在屏幕上的位置。
- 过程:父视图调用子视图的 layout() 方法,将计算好的位置信息传递给子视图,子视图根据这些信息确定自己在父视图中的位置。同样,这个过程也是递归进行的,从根视图开始,依次为每个子视图分配位置。
4. 绘制阶段(Draw)
- 目的:将视图绘制到屏幕上。
- 过程:视图的 draw() 方法会被调用,该方法会按照一定的顺序进行绘制操作,通常包括绘制背景、绘制内容、绘制子视图、绘制前景等步骤。绘制操作会使用 Canvas 对象进行图形绘制,最终将视图的内容显示在屏幕上。
5. Choreographer 机制
Android 系统使用 Choreographer 来协调渲染操作,确保视图的渲染与屏幕的刷新周期同步。Choreographer 会在每个垂直同步信号(VSync)到来时触发渲染操作,避免出现画面撕裂等问题,保证界面的流畅性。
帧率
在 Android 中,理想的帧率是 60 FPS,即每帧的渲染时间不超过 16.67 毫秒。
1. 优化布局
- 减少视图层级
复杂的视图层级会增加测量、布局和绘制的时间,尽量使用简单的布局,避免嵌套过深。例如,使用 ConstraintLayout 可以减少布局嵌套。
- 避免过度绘制
过度绘制指的是在同一区域重复绘制多次,会浪费 CPU 和 GPU 资源。可以通过 Android Studio 的布局检查工具来检测和优化过度绘制问题。
2. 异步处理耗时操作
避免在主线程进行耗时的计算、网络请求等操作,因为这些操作会阻塞主线程,导致渲染无法及时完成,从而降低帧率。可以使用 AsyncTask、HandlerThread、RxJava 等方式将耗时操作放到子线程中执行。
3. 合理使用缓存
对于一些静态或变化不频繁的视图,可以使用视图缓存,避免每次都进行重新绘制。例如,RecyclerView 的 ViewHolder 机制就是一种视图缓存的应用。
4. 限制动画帧率
在实现动画效果时,根据实际需求合理设置动画的帧率,避免过高的帧率导致性能问题。例如,使用 ValueAnimator 时可以通过 setFrameDelay() 方法设置帧延迟。
垂直同步信号(VSync)
显示屏以固定的频率刷新画面,这个刷新频率通常是 60Hz 或 120Hz 等,即每秒刷新 60 次或 120 次。每次刷新的时间点就是垂直同步信号(VSync)到来的时刻。Choreographer 会利用这个信号来同步渲染操作。
1. 注册回调
应用程序可以通过 Choreographer 的 postFrameCallback() 或 postFrameCallbackDelayed() 方法注册一个帧回调(FrameCallback)。当注册回调时,Choreographer 会将回调添加到一个队列中等待处理。
2. 等待 VSync 信号
Choreographer 会通过底层的机制(如 VsyncReceiver)监听 VSync 信号。当 VSync 信号到来时,Choreographer 会被唤醒。
3. 处理回调
当 VSync 信号到来时,Choreographer 会依次处理队列中的帧回调。在处理回调时,会按照一定的顺序执行不同类型的任务,主要包括以下三个阶段:
- 输入事件处理阶段:处理用户的输入事件,如触摸、按键等。
- 动画处理阶段:更新和绘制动画,确保动画的流畅播放。
- 绘制阶段:进行视图的测量、布局和绘制操作,将界面绘制到屏幕上。
4. 循环执行
Choreographer 会不断地循环执行上述过程,每次 VSync 信号到来时都会处理队列中的回调,从而实现界面的持续更新。
6. ANR
ANR(Application Not Responding)即应用无响应,是 Android 系统为了保证用户体验,在应用程序无法及时响应用户操作时触发的一种机制。
触发场景
- 输入事件(按键、触摸等)在 5 秒内没有得到处理
- BroadcastReceiver 在 10 秒(前台)或 60 秒(后台)内没有执行完毕
- Service 在 20 秒(前台)或 200 秒(后台)内没有完成启动
实现原理
1. 消息处理机制
Android 应用的主线程(UI 线程)使用 Looper 和 MessageQueue 来处理消息。当有事件发生时,系统会将相应的消息放入消息队列中,Looper 会不断地从消息队列中取出消息并进行处理。如果主线程被阻塞,消息队列中的消息就无法及时处理,从而可能导致 ANR。
2. 超时监控机制
Android 系统通过设置定时器来监控各种操作的执行时间。当启动一个可能会触发 ANR 的操作时,系统会启动一个定时器。如果在规定的时间内操作没有完成,定时器就会触发 ANR 处理流程。
热启动
应用进程已经存在,只是 Activity 被暂停(onPause)或停止(onStop),此时系统只需将 Activity 重新恢复到前台,调用相应的生命周期方法(如onRestart()、onStart()、onResume())即可,启动速度最快
温启动
介于冷启动和热启动之间,应用进程仍然存在,但 Activity 已经被销毁,需要重新创建 Activity 实例并调用其生命周期方法