APP冷启动优化:如何使用好工具【Perfetto\ systrace \MethodTracing】

简介: APP冷启动优化:如何使用好工具【Perfetto\ systrace \MethodTracing】

APP的性能提升无非就是围绕稳定、流畅之类的指标做文章,在推动性能提升的时候,什么才是关键,热情?能力 ?规范?,个人认为是工具,用好性能分析工具,性能提升就走完了一大半,就好比:”算数我比不过小王,但我找了个电子计算器“。以提升冷启动速度为例,看看整体的性能优化流程应该是什么样子,而在这其中性能工具能带来什么。


冷启动的定义与可优化的点


如何衡量当前的性能指标,个人感觉,性能的衡量分三步: 指标制->  指标采集 -> 性能基线与优劣评级, 以上三块组成性能量化工具,有了量化工具,就可以说APP性能是好是坏,以冷启动为例,冷启动指标如何制定?单从技术上说感觉可以定义如下:


冷启动耗时 = 从APP进程创建到第一个有效页面帧[闪屏]

具体到实现上,涉及哪些环节,会怎样影响冷启动速度呢?


冷启动->系统会启动一个StartWindow占位-> 启动进程->创建Application-�>Application中初始化全局配置->启


image.png

冷启动的时候,系统一般会先启动一个占位Window,默认是个白屏窗口,复用的是第一个启动Activity配置,在体感上,主要下面的Activity配置


<item name="android:windowBackground">@drawable/xxx</item>

它一般是SplashActivity的配置,用品牌图做个中转,这个图最好要限制下尺寸,否则在解析上影响启动速度。随后系统会启动进程加载SplashActivity,启动进程主要是Application中可能有些APP全局初始化操作,尽量轻,或者延后处理,当然,也会有一些ContentProvider与Receiver影响启动这些都可以通过工具查看。


public class LabApplication extends Application {
    @Override
    public void onCreate() {
            super.onCreate()
            <!--UI中不要处理耗时操作-->
 }

之后便是Activity的创建与启动流程 :


image.png

可以看到上图的Activity启动流程都是被动消息的处理,主要是受控AMS指挥,代码中设置View及显示的流程也就上图的几个点,比如onCreate中设置Layout并inflater,当然,这不是必须的,即使不主动setContentView,在后面的wm.addView中也会创建顶层DecorView。


@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    <!--可能影响耗时-->
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

而setContentView的inflate可能是影响耗时的一个点。之后handleResumeActivity中,会想WMS添加窗口View

@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
                ...
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
                ...
            r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    <!--添加Window-->
                    wm.addView(decor, l);
                } else {
    @Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

可以看到 getDecorView会兜底处理Activity 的顶层窗口创建逻辑。addView会调用WindowManagerGlobal的addView,进而创建ViewRootImpl,利用ViewRootImpl进一步添加Window

   public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
              <!--关键-->
              root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
                <!--添加到WMS,处理UI显示-->
                root.setView(view, wparams, panelParentView);
        }
    }

而ViewRootImpl接管流程之后,所以View相关的操作都将在ViewRootImpl处理,而最终由其requestLayout触发测量、布局、绘制的动作

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
                     <!--申请下个Message绘制-->
                    requestLayout();
                <!--添加窗口-->      
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
        }
    }
}

requestLayout会scheduleTraversals预先占位一个异步消息,用于接收并doScheduleCallback触发的VSYNC信号,这样可以保证之后插入的消息都被延期处理,从而Window被添加后,UI绘制任务第一时间执行。

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        <!--异步消息-->
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        <!--插入绘制请求,触发VSYNC-->
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}

等待VSYNC消息回来后,撤离异步消息栅栏,第一时间处理UI绘制:

image.png

所以首帧的渲染一定是在Resume之后,那么具体的时机怎么把控?到底在哪,如下图所示,插入的点在哪?

image.png

网上有一些其他的实现,认为可以监听onAttachedToWindow或者OnWindowFocusChange,onAttachedToWindow的问题是可能太过靠前,还没有Draw, OnWindowFocusChange的缺点可能是太过滞后,其实可以简单认为view会的draw以后,View的绘制就算完成,虽然到展示还可能相差一个VSYNC等待图层合成,但是对于性能监测的评定,误差一个固定值可以接受:


image.png

在onResume函数中插入一条消息可以吗,理论上来说,太过靠前,这条消息在执行的时候,还没Draw,因为请求VSYNC的同步栅栏是在是在Onresume结束后才插入的,无法拦截之前的Message,但是由于VSYNC可能存在复用,Onresume中插入的消息也有可能会在绘制之后执行,这个不是完全一定的,比如点击MaterialButton启动一个Activity,第二个Activity的setView触发的VSYNC就可能复用MaterialButton的波纹触发的VSYNC,从而导致第二个Activity的performTraval复用第一个VSYNC执行,从而发生在onResume插入消息之前,如下

栅栏消息


image.png

重绘CallBack包含多个Activity的重绘

image.png

综上所述,将指标定义在第一次View的Draw执行可能比较靠谱。具体可以再DecorView上插入一个透明View,监听器onDraw回调即可,如果觉得不够优雅,就退一步,监听OnWindowFocusChange的回调,也勉强可以接受, OnWindowFocusChange一定是在Draw之后的。


有了指标,那是否达标?如何采集?基线呢?可以参考业界做法,采集方式可以无入侵打点,而优秀基线可以认为:


优秀=秒开

如果发现不达标,接下来要做的就是定位+优化,这个时候就体现分析工具的重要性,其实上述的原理分析就已经借助Studio自带的Profiler工具,在理解流程上事半功倍。


如何定位当前性能问题


冷启动每个阶段的耗时可以通过多种工具、方式来定位:可以用的有Debug.startMethodTracing跟踪,也可以利用perfetto/systrace来查看,甚至还可以用Studio自身的Profiler跟踪,每种方式都有自己的优势,可配合选择使用。


Debug.startMethodTracing 适合查看UI线程的耗时函数


Debug.startMethodTracing是通过应用插桩来生成跟踪日志,做到对方法的跟踪。但是启用剖析功能后,应用的运行速度会减慢,所以,不应使用剖析数据确定绝对时间,最大的作用是用在对比上,可以对比之前,或者对比周围函数。具体用法:

private void startTrace() {
    File file = new File(getApplication().getExternalFilesDir("android"), "methods.trace");
    Debug.startMethodTracing(file.getAbsolutePath(), 100 * 1024 * 1024);
}
<!-注意配对使用-->
private void stopTrace() {
    Debug.stopMethodTracing();
}

对于冷启动:进程启动时开启监听,在合适节点配对停止即可,之后导出.trace文件在Studio中分析,可以看到关键函数耗,Studio提供了多种模式,Flame Chart、Top Down、Bottom Up、Event,不同的模式侧重点不同。定向分析的时候,可以分段锁定范围,比如冷启动可分几个阶段排查,进程创建、Application初始化、Activity的创建、create、resume、draw等,先选定Main线程,然后将范围限制定Application阶段,如下下:


  • Flame Chart:更侧重直观反映函数耗时严重程度


image.png

比如上图,浅黄色部分其实就是需要重点关注的部分,耗时最多的函数,会最先展示,更加方便定位严重问题,大致定位问题后,就可以用Top Down 进一步看细节。


  • Top Down: 更侧重自顶向下详细排查


利用Top-Down模式可以更精确观察函数耗时与调用堆栈,更加清晰,如下在Application初始化阶段,可以清醒看到函数调用顺序、耗时等,

image.png

对于冷启动,重点排查耗时函数,尝试将非核心逻辑从UI线程中移除。同理对于闪屏Activity的onCreate跟onResume阶段所做的处理类似

image.png

从图中就很容下发现,有些Flutterboost、埋点Json解析类的耗时操作被不小心关联进了Activit的启动流程中,拖慢了冷启动速度,那就可以放到非UI线程中处理,或者延后处理。

  • Bottom Up:一种平铺的模式,


image.png

这个模式个人用的不多,罗列的函数太多,没有层次,可能单独看排名靠前的几个有些收益。


依赖profiler基本能定位哪些函数导致了冷启动速度慢,但是这些函数可能并非自己耗时严重,也许是会因为调度或者锁的原因导致慢,这个时候perfetto/systrace会提供更多帮助。


perfetto/systrace:大局与调度


perfetto地址及使用文档

perfetto/systrace是官方提供另一种性能分析工具,其中perfetto可以看做是systrace的升级版。相比MethodTracing代码插桩,无法具体到每个方法,但可以提供全局性能概览,可以更快定位问题范围,而且perfetto/systrace在全局任务调度、系统调用上更具优势,MethodTracing多少对于性能有些影响,而perfetto/systrace借助系统本身lOG,可以降低自身带来的影响,用perfetto看一下冷启动的流程,如下:

image.png

如图,首先你就能直观的看到那些阶段的耗时比较严重,然后定向分析即可,将时间段收缩,放大观察:

image.png

可以直观看出Activity启动时蓝色标记的资源解析耗时过长,定向排查后发现图大


Name    res/BKC.xml
Category    null
Start time  1s 309ms 568us 459ns
Duration    42ms 35us 682ns
Slice ID    2465

适当将图缩小,降低加载成本,经过优化缩短到8ms


Name    res/BKC.xml
Category    null
Start time  964ms 602us 749ns
Duration    8ms 260us 261ns
Slice ID    11851
type    internal_slice

再比如,对于有些阶段,UI线程莫名的睡眠,其实可以比较方便的查看是什么因素导致的,如下:


image.png

如上所示,在当前阶段,UI线程因为没有获取锁进入了睡眠,之后,被另一个线程唤起了,这就可以排查到底是哪个地方有问题,是否可以避免,大概的使用方式就是如此。


对于整体冷启动优化效果:用perfetto看比较直接


优化前:1261ms

image.png


优化后:439ms

image.png

所用的优化除了上面的措施还有部分如下措施等:


  • 延迟非必要receiver的注册
  • 闪屏广告Layout布局按需加载
  • 锁优化,进程线程间阻塞优化


所用的优化除了上面的措施还有部分如下措施等:核心原则 UI线程不做耗时操作


  • 延迟非必要receiver的注册
  • 闪屏广告Layout布局按需加载
  • 锁优化,进程线程间阻塞优化


总结


BUG是必然的,优化是持久的,如何用好工具是关键的。


目录
相关文章
|
7月前
|
缓存 移动开发 JavaScript
如何优化UniApp开发的App的启动速度?
如何优化UniApp开发的App的启动速度?
1239 139
|
7月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
939 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
529 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
8月前
|
存储 前端开发 安全
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
328 5
|
11月前
|
存储
《仿盒马》app开发技术分享--未完成订单列表展示逻辑优化(61)
上一节我们实现订单与优惠券的联合提交时,我去到订单列表页面查看生成的订单信息,发现现在的订单从信息展示到价格计算全都是有问题的。所以紧急的把对应的问题修改一下。
294 70
|
11月前
|
Java 数据安全/隐私保护 计算机视觉
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本内容提供Java生成自定义图片的示例代码,涵盖基础图像创建、文本添加及保存功能,适合学习2D图形编程。包括教学示例图片生成、文本图层处理和数字水印技术实现方案。
|
10月前
|
存储 Android开发 数据安全/隐私保护
Thanox安卓系统增加工具下载,管理、阻止、限制后台每个APP运行情况
Thanox是一款Android系统管理工具,专注于权限、后台启动及运行管理。支持应用冻结、系统优化、UI自定义和模块管理,基于Xposed框架开发,安全可靠且开源免费,兼容Android 6.0及以上版本。
1190 4
|
11月前
|
数据库
《仿盒马》app开发技术分享-- 优惠券逻辑优化(58)
我们已经实现了优惠券的领取和展示,现在已经趋近于一个完整的电商应用了,但是这时候问题又来了,我们领取完优惠券之后,我们的新用户优惠券模块依然存在,他并没有消失,既然我们是从云数据库中查询的数据,那么我们需要找到一个字段跟他对应起来,来实现新用户领券后关闭这个模块的展示,同时我们在未登录的时候他也要保持隐藏,登录后能实现优惠券的领取。然后在结算的时候得出有几张符合的券能用
157 9
|
11月前
|
存储 前端开发 JavaScript
仿真银行app下载安装, 银行卡虚拟余额制作app,用html+css+js实现逼真娱乐工具
这是一个简单的银行账户模拟器项目,用于学习前端开发基础。用户可进行存款、取款操作,所有数据存储于浏览器内存中
|
安全 数据安全/隐私保护 Android开发
【05】2025年1月首发完整版-篇幅较长-苹果app如何上架到app store完整流程·不借助第三方上架工具的情况下无需花钱但需仔细学习-优雅草央千澈详解关于APP签名以及分发-们最关心的一篇来了-IOS上架app
【05】2025年1月首发完整版-篇幅较长-苹果app如何上架到app store完整流程·不借助第三方上架工具的情况下无需花钱但需仔细学习-优雅草央千澈详解关于APP签名以及分发-们最关心的一篇来了-IOS上架app
3302 75

热门文章

最新文章