Android性能优化——启动优化

App 的启动速度是用户的第一体验,互联网中有一个八秒定律,如果用户等待八秒App 还没打开,70%的用户都会停止等待

 一、启动分类

官方 App startup time 

  • 冷启动

         耗时最多,衡量标准

  • 热启动

         最快。  后台~前台

  • 温启动

         较快。只会重走activity的生命周期,不会走进程的创建以及Application的创建和生命周期

冷启动流程

  1. 用户点击 
  2. 触发IPC 操作
  3. Process.start 进程创建
  4. ActivityThread 是每个单独进程的入口,会有一个main方法,进行消息循环的创建以及handler的创建
  5. bindApplication 通过反射创建Application 以及调用application的生命周期
  6. Activity 的生命周期 LifeCycle 
  7. ViewRootImpl 开始真正的界面绘制

冷启动之前(这个过程无法干预)

  • 启动App 
  • 加载空白Window 
  • 创建进程

随后任务

  • 创建Application 
  • 启动主线程
  • 创建MainActivity 
  • 加载布局
  • 布置屏幕
  • 首帧绘制

优化方向
        Application 和Activity 的生命周期

二、启动时间的测量方式

adb 命令方式

        adb shell am start -W packagename/首屏Activity 

    特点:

  • 线下使用方便,不能带到线上
  • 非严谨,精确时间

手动打点方式
        启动时埋点,启动结束埋点,二者差值
     特点:       

         精确,可带到线上,推荐使用

     避开误区

  • 启动时间开始的位置在application 中的attachBaseContext 
  • 启动时间结束的位置采用采用Feed 第一条展示
  • addOnDrawListener 要求Api 16

ThisTime 最后一个Activity启动耗时
TotalTime 所有Activity 的启动耗时
WaitTime AMS启动Activity 的总耗时

三、启动优化使用到的工具

 traceview,systrace 

  • 两种工具互相补充
  • 正确认识工具及不同场景选择合适的工具

traceview

  • 优点
    •  图形的形式,展示代码执行的时间,调用栈信息等
    • 信息全面,包含所有线程信息
  •  使用方式

      Debug.startMethodTracing(“文件名”);默认8M大小,如果想要更大,传参bufferSize 
      Debug.stopMethodTracing() 结束
      就会生成一个文件,位置在sd卡 :Android/data/packagename/files

在Android Studio 右边有一个Devices File Explorer 可以很方便的打开手机系统的文件
添加开启和结束的代码后,然后运行,在存储的位置下,刷新,会生成一个设置的文件名.trace 文件

  • 如何分析
    • 左上是通过代码精确指定的时间范围,左下角有个时间搓,不是特别重要
    • 左下是线程信息,可以看到线程的总数,也可以看到每个线程在具体的时间做了哪些事 

右边有四个Tab 

  • Top Down

total:总时间

self :

children:

举例:调用了A函数,整体时间是total,在A函数中调用了一行代码,然后执行B函数,它的selfTime是执行了一行代码的时间,childrenTime就是B函数执行的时间,selfTime和childrenTime之和一定等于totalTime

函数的调用列表,点击相应的jump to souse 可以跳入详细的代码中

ThreadTime 一定会变少,CPU执行的时间
Wall Clock Time 代码发生在这个线程上,真正执行的时间

  • Call chat 

每一行显示的是函数调用的时间段,垂直方法被调用着
系统Api 调用颜色是橙色
应用自身的函数调用是绿色
第三方Api 调用是蓝色

  • Flame chat 火焰图

倒置的调用图表,会收集相同的调用顺序

  • Bottom up

谁调用了我,和Top down 是相反的

总结:
        运行时开销严重,整体都会变慢这个工具太强大了,会抓去所有线程的所有执行函数以及顺序
        可能会带偏优化方向
        traceview 和cpu profiler 
                traceview 的好处可以在代码中进行埋点,用cpu profiler 进行分析
                单纯的用cpu profiler 来抓取精确的启动位置,几乎不可能

 systrace

结合Android内核的数据生成html 报告
Python脚本

  • 使用方式

• python systrace.py -t 10 [other-options] [categories]

  • 官方文档:

https://developer.android.com/studio/command-line/systrace#command_options

  • 使用案例

python /Users/Liuzhao.Future/Library/Android/sdk/platform-tools/ systrace/systrace.py -b 32768 -t 5 -a com.optimize.performance -o performance. html sched gf view wm am app

  • 代码中使用

开启:TraceCompat.beginSection(“apponCreate ”)
结束:TraceCompat.endSection 

  • 分析html 文件

CPU核数跟不同手机是有关系的,有些手机厂商8个核都给你,有的手机8核,但是只给你使用4核

  • 总结:
    • 轻量级,开销小,埋了哪一点就去做哪一点
    • 直观反映CPU利用率

cpuTime 和 wallTime

  • wallTime 是代码执行的时间
  • cpuTime 是代码消耗CPU的时间(重点指标)

举例:为什么两者会不一样
        锁冲突,比如一个线程执行需要获取锁A,但是锁A被其他线程持有,没有得到释放,而这个线程是一个轻量级的,只占用了cpu 一点时间,所以这个时候cpuTime 就会很短,而wallTime 很长

四、如何优雅的获取CPU耗时

需要知道具体哪个方法占用了大量时间

  • 常规方式:手动埋点
    • 侵入性强
    • 工作量大
  • AOP方式:Aspect Oriented Programming ,面向切面编程
    • 针对同一类问题的统一处理
    • 无侵入添加代码

Aspect使用

  • classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
  • implementation 'org.aspectjaspectjrt:1.8.+'
  • apply plugin: 'android-aspectix'

介绍

  • Join Points
    • 程序运行时的执行点 ,可以作为切面的地方
    • 函数调用、执行
    • 获取、设置变量
    • 类初始化
  • PointCut
    • 带条件的JoinPoints
  • Advice
    • 一种Hook,要插入代码的位置
    • Before : PointCut之前执行
    • After :PointCut之后执行
    • Around: Pointcut之前、之后分别执行
  • 语法简介
    //Before:Advice,具体插入位置
    //execution :处理Join Point的类型
    //(* android.app.Activity.on**(.)):匹配规则
    @Before("execution(* android.app.Activity.on** (.))") 
    public void onActivityCalled (JoinPoint joinPoint) thr
    ows Throwable {
        ……
    }
  • 使用案例
@Aspect
public class Performanceaop{

    Around("call(*com.optimize.performance.PerformanceApp.**(..))") 
    public void getTime(ProceedingJoinPointjoinPoint){
        Signature signature=joinPoint.getSignature(); 
        String name=signaturetoShortString(); 
        long time=System.currentTimeMillis(); 
        try {
            joinPoint.proceed();
        } catch(Throwable throwable){
            throwable.printStackTrace();
        }
        LogUtils.i(msg:name+" cost "+(System.currentTimeMillis() - time));
    }
}
  • 优点
    • 无侵入性
    • 修改方便

五、异步优化

  • 常规异步优化:使用线程池进行异步优化
  • 启动器(异步启动优化的最优解)

常规异步方式

常规异步方式需要注意点:

  • 并不是所有的代码都可以直接异步
  1. 不符合异步要求:有的任务必须在主线程中执行
  2. 需要在某阶段完成,在splash界面就要用到异步任务,在执行界面的时候,异步任务还没完成某一些代码必须在某一个阶段完成,解决方案:CountDownLatch 相当于自己加了个锁
  3. 区分CPU 密集型还是IO密集型任务

常规异步方案痛点:

  • 代码不够优雅
  • 场景不好处理(依赖关系),在特定的时间内结束某个任务
  • 维护成本高

启动器方式

核心思想:
     充分利用CPU 多核,自动梳理任务顺序
启动器流程:

  • 代码Task化,启动逻辑抽象为Task 
  • 根据所有任务的依赖关系生成一个有向无环图
  • 多线程按照排序后的优先级依次执行

六、延迟初始化

  • 常规方案:handler.postDelay 
  • 更优方案:对延迟任务进行分批初始化

常规方案的问题:

  • 时机不便控制
  • 导致Feed卡顿

更优方案的优点:利用了IdleHandler特性,空闲执行

  • 执行时间明确
  • 缓解Feed卡顿

七、总结:启动优化总方针

  • 异步 ,延迟,懒加载
  • 技术和业务相结合

八、注意事项

  • 收敛启动代码修改权限
  • 结合Ci修改启动代码需要Review 或 通知

九、其它方案

  • 提前加载SharedPreferences 

        在multidex之前加载,充分利用此阶段CPU 
        覆写getApplicationContext返回this

  • 启动阶段不启动子进程

        子进程会共享CPU资源,导致主进程CPU 紧张
         注意启动顺序,App onCreate 之前是ContentProvider 

  • 类加载优化,提前异步类加载
  • 启动阶段抑制GC 
  • CPU锁频

猜你喜欢

转载自blog.csdn.net/weixin_42277946/article/details/131808262