美文网首页Android性能优化Android高阶
从systrace看app冷启动过程(一)-首帧绘制前的准备

从systrace看app冷启动过程(一)-首帧绘制前的准备

作者: Stan_Z | 来源:发表于2019-04-15 19:57 被阅读75次

    原创文章,转载注明出处,多谢合作。

    本文从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流程

    这部分就是走scheduleLaunchActivity流程了,内容包括:

    创建Activity,并走对应的生命周期。这个过程主要是应用布局的加载:

    setContentView 创建DecorView,并把xml的View树解析出来,加到DecorView上的contentParent部分。之后Activity 调用makeVisible 通过WindowManagerGlobal执行addView操作,开启下一个阶段的绘制工作。

    至此,第一个Vsync信号响应阶段就简单介绍完了,中间牵扯到的其他标签有兴趣的可以一一追源码看下,这里就不赘述了。下一篇分析首帧绘制与渲染过程。

    相关文章

      网友评论

        本文标题:从systrace看app冷启动过程(一)-首帧绘制前的准备

        本文链接:https://www.haomeiwen.com/subject/qgvpwqtx.html