原创文章,转载注明出处,多谢合作。
本文从systrace的角度通过关键标签,来简单看下app冷启动牵扯到的图形渲染的整个流程。代码参考Android 9.0。
以优酷app冷启动为例:
先看第一帧显示过程,如下图所示:
整个过程包括:
- 应用程序启动。
- 首帧的绘制与渲染。
- 首帧的合成与送显。
注:
我们可以看到systrace中,每一帧主要分红、黄、绿三种颜色:
- 红色: 代表从performTraversals到renderthread绘制完成的时间超过2 * vsync,定义成terrible frame。
- 黄色: 代表时间超过1 * vsync 不到 2* vsync,在有renderthread的情况下,超过一个vsync是不一定会导致掉帧的,所以只是黄色,定义成bad frame。
- 绿色: 代表时间在1 * vsync以内, 定义成ok frame。
先看第一个过程:应用程序启动。
因为展开的图太长了,这个过程又主要拆分为如下两个阶段:
1)进程创建阶段
Zygote初始化过程中,最后会调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作。
那么在App冷启过程中,先确定调用的Activity以及对信息与权限的验证,并调整其task和stack之后,会开始准备创建进程。
runSelectLoop之前流程是将启动参数封装成ZygoteState。Zygote 待命的loop通过socket获取到请求信息,runOnce就开始读取参数列表, 并执行forkAndSpecialize来创建进程,之后就是创建Binder线程池以及通过反射调用ActivityThread的main函数等等。PostFork过程就在forkAndSpecialize与handleChildProc之间,描述进程fork的过程。而ZygoteInit部分则是描述进程启动之后一些准备工作。
以PostFork为例:
frameworks/base/core/java/com/android/internal/os/Zygote.java
133 public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
134 int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
135 int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) {
...
143 if (pid == 0) {
144 Trace.setTracingEnabled(true, runtimeFlags);
146 // Note that this event ends at the end of handleChildProc,
147 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
148 }
149 VM_HOOKS.postForkCommon();
150 return pid;
151 }
从这里看到了traceBegin的标签,而且通过注释我们知道对应的End标签在handleChildProc.
frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java
844 private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
845 FileDescriptor pipeFd, boolean isZygote) {
...
871 // End of the postFork event.
872 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
...
}
另外,CPU调度也提一下,也是systrace需要重点关注的部分:
这部分表示CPU调度状态:
- 灰色:Sleeping
- 蓝色:Runnable (它可以运行,但是需要等待调度程序唤醒)
- 绿色:Running
- 橙色:Uninterruptible sleep 由于 I/O 负载而不可中断休眠
如果当前部分存在耗时情况,先对比看看相同部分的Running 时间是否差不多,如果差很多可能是方法本身耗时了,如果差不多,那么可能是调度上耗时了,比如Uninterruptible sleep状态太多,如果是这种情况的话则需要看紧随其后的Runnable,跟一下wake up它的对应进程or线程是谁,一步步分析下去,找到root issue。
2)Activity启动阶段
进程创建后,继续Activity的启动阶段,包括对要启动的Activity信息、权限等的验证,数据的封装、任务栈的调整等等一系列操作,最后进入应用的ActivityThread。
ActivityThreadMain部分:
frameworks/base/core/java/android/app/ActivityThread.java
6764 public static void main(String[] args) {
6765 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
6782 ...
6783 Looper.prepareMainLooper();
...
6796 ActivityThread thread = new ActivityThread();
6797 thread.attach(false, startSeq);
6808 // End of event ActivityThreadMain.
6809 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
6810 Looper.loop();
6811
6812 throw new RuntimeException("Main thread loop unexpectedly exited");
6813 }
ActivityThreadMain这就是反映了ActivityThread的main方法执行过程,这个过程很简单就是创建ActivityThread并让它的Looper消息泵循环跑起来。所以一般这个过程不会有什么耗时问题。
其中有个不起眼的方法:attach 看看它的调用流程:
ActivityThread.attach() ->AMS.attachApplicationLocked()->ActivityThread.bindApplication - >sendMessage(H.BIND_APPLICATION, data);
那么就到了接下的bindApplication过程了。
bindApplication部分:
class H extends Handler {
1665 public void handleMessage(Message msg) {
1666 if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
1667 // MIUI ADD
1668 long startTime = SystemClock.uptimeMillis();
1669 switch (msg.what) {
1670 case BIND_APPLICATION:
1671 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
1672 AppBindData data = (AppBindData)msg.obj;
1673 handleBindApplication(data);
1674 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1675 break;
...
}
}
这个标签对应的就是 handleBindApplication过程,应用进程的创建是从Zygote fork的进程,copy了一份Zygote进程的内存拷贝,在当前过程中,就是正式往fork出来的进程中填充属于当前应用私有的资源与数据。
注:
Zygote 刚fork出应用进程时,两者是共享物理内存的,且对data区内容只有可读权限,一方开始了写操作了,即会开辟一块新的内存,保存新的修改内容。这技术就是fork copy-on-write.
bindApplication这个过程包括:
- 加载应用资源、load Class到内存。
- 创建上下文。
- 初始化Instrumentation,并通过它调用application的onCreate。Instrumentation是ActivityThread与组件之间的传话官,ActivityThread通过H分发出来的对应的操作都是通过Instrumentation来调用组件实现。
注:
google原生逻辑是安装应用的时候解压APK包,对dex做初步优化,你会发现在安装完应用后 data/app/<packageName>/oat/arm / 路径下会生成 .odex .vdex文件。之后会在空闲状态下执行dex2oat。这部分之后开章节来详细说。
继续:
这部分就是走scheduleLaunchActivity流程了,内容包括:
创建Activity,并走对应的生命周期。这个过程主要是应用布局的加载:
setContentView 创建DecorView,并把xml的View树解析出来,加到DecorView上的contentParent部分。之后Activity 调用makeVisible 通过WindowManagerGlobal执行addView操作,开启下一个阶段的绘制工作。
至此,第一个Vsync信号响应阶段就简单介绍完了,中间牵扯到的其他标签有兴趣的可以一一追源码看下,这里就不赘述了。下一篇分析首帧绘制与渲染过程。