Android冷启动优化

2022-09-14  本文已影响0人  ModestStorm

1.冷启动时间检测

通过adb检测

adb shell am start -W package/XXXActivity

运行结果分析

TotalTime:应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面第一帧显示。不包括前一个应用的Activity的pause耗时。
WaitTime:一般比TotalTime大点,是AMS启动Activity的总耗时,包括前一个应用的pause耗时。
Android 5.0以下没有WaitTime,一般情况下我们只需要关注TotalTime即可。

Application 中初始化应用所需的业务、工具、 UI 等组件,导致耗时。

2.冷启动流程

640.jpeg

2.1 Application的onCreate()方法

通常,我们会在这里做很多的初始化操作,各种库,业务组件等等,如果这里的任务执行过多,且都是在主线程里串行执行的,会大大影响冷启动速度
主要解决的也是这里的耗时。

2.1.1 将串行任务改为并发任务,合理创建使用线程池

一股脑的将所有任务都放在子线程中去调度是否是最好的呢?子线程数量并不是越多越好,过多的子线程任务会与主线程竞争cpu资源,进而造成主线程获取的cpu时间片短造成卡顿,得不偿失。
所以首先需要确定放在子线程的任务是IO密集型任务还是CPU密集型任务(运算逻辑)。

确定任务是CPU密集型还是IO密集型
我们需要知道某一个任务是否是 CPU 消耗型的任务(运算类的操作),还是说 IO 类型的任务( 如文件读写SP/布局异步加载),前者消耗的CPU时间片较多,我们就把它放在 定容 线程池里调度,后者消耗的时间片少,我们就把它放在缓存线程池中,这样,技能充分的调用CPU资源,又不容易过度占用CPU,使得任务 并发 运行,达到时间优化的目的。

定容线程池:CPU密集型任务线程池,线程容量是固定的,可以根据具体业务场景确定线程数量,比如1个线程。

缓存线程池:IO密集型任务,可以创建2*cpu数量的子线程,创建子线程的时候设置线程优先级,进一步降低子线程与主线程争抢资源的概率。

对于难以确定任务是CPU密集型还是IO密集型任务,可以使用systrace来查看任务的耗时。

1.代码插入收集

private void initAnalyzeAync() {
        TraceCompat.beginSection("initAnalyzeAync");
        PbnAnalyze.setEnableLog(BuildConfig.DEBUG);
        PbnAnalyze.setEnableAnalyzeEvent(true);
        initAnalyze();
        TraceCompat.endSection();
    }

2.找到systrace.py

3.执行python命令

python systrace.py-t 10 -a <package_name> -o xxtrace.html sched gfx view wm am app

4.运行App,等待html文件生成

5.打开html文件,查看耗时cpu Duration为消耗cpu的时间,wall Duration为总时间

640-2.jpeg

cpu Duration几乎占了全部的wall Duration,所以这个任务为cpu消耗型任务,所以我们优化的时候要把这个任务放在定容线程池中。

2.1.2 确定任务调度的顺序,使用CountDownLatch同步器执行

开发中经常会遇到这种场景,闪屏页的启动必须依赖于某个库初始化完成才行,直白一点来说就是在application中阻塞执行这个任务,基于我们的多线程 并发 任务调度,最简便的方法就是任务管理器使用CountDownLatch,初始化CountDownLatch(n)时指定n为需要执行的任务数量,在主线程中调用countDownLatch.await()阻塞,工作线程每执行完一个任务调用countDownLatch.countDown(),直到所有任务都执行完毕后,countDownLatch.await()阻塞结束恢复执行,这样既实现了多个任务并发执行的同时也维护了正确任务的执行顺序。

2.2 Activity的onCreate

可以在应用启动的时候开启工作线程预加载首页Activity的布局,AsyncLayoutInflater达到第一屏快速显示的目的。这也是可以考虑的一种方案。

2.3 查看应用启动过程中的GC日志

在线下的开发模式下使用systrace 可以很方便地追踪关键系统调用的耗时情况,但是不支持应用程序代码的耗时分析。

查看systrace支持的类型

python systrace.py --list-categories

通过插桩,我们可以看到应用主线程和其他线程的函数调用流程。它的实现原理非常简单,就是将下面的两个函数分别插入到每个方法的入口和出口。

class Trace {
  public static void i(String tag) {
    Trace.beginSection(name);
  }

  public static void o() {
      Trace.endSection();
  }
}

综合来看,“systrace + 函数插桩”似乎是比较理想的方案,而且它还可以看到系统的一些关键事件,例如 GC、System Server、CPU 调度等信息。

当然这里面有非常多的细节需要考虑,比如怎么样降低插桩对性能的影响、哪些函数需要被排除掉(可在debug环境下使用)

3.其他优化方案

3.1 FaceBook推出的Redex优化Dex方案,官方说使用Redex可以减少20%的apk体积和降低25%的启动速度。

其原理是将应用启动相关的类放入同一个主dex中,减少应用启动时主dex查询类的时间。

systrace适合线下使用,要想观察线上用户使用的真实情况进一步优化需要建立一套完善的数据收集工作才行,包括应用内存使用情况,FPS,线程数量,流量,CPU监控才能持续优化。

2.冷启动优化

冷启动时间=laucher进程的Activity.onPause + 应用进程的创建 + 应用进程的onCreate + Activity的创建和第一帧显示的时间

laucher进程的Activity.onPause + 应用进程的创建: 这两个的时间是由于系统调度完成的,RD没有多少发挥空间。

1.应用进程的onCreate :它是基础SDK初始化的地方,可以优化耗时时长。具体办法是:

(1). 将串行任务改为并发任务,合理创建使用线程池:首先需要确定任务是CPU密集型还是IO密集型

  1. CPU密集型任务放到定容线程池中执行,核心线程数量一般设置为2,最大线程数量不超过cpu个数
  2. IO密集型任务放到缓存线程池中去执行,线程数量一般设置为两倍cpu数量,
  3. 创建子线程的时候设置线程优先级,进一步降低子线程与主线程争抢资源的概率。

(2).确定任务调度的顺序,使用CountDownLatch同步器执行,当所有依赖的任务执行完成的时候调用countDown方法,await不再阻塞会返回继续执行。

2.Activity的onCreate:使用AsyncLayoutInflater异步加载首屏布局,进入页面的时候可以快速重用View。

3.Render耗时统计分析:有没有超过16ms
AOP方案:使用hook instrumation方案统计Activity页面渲染耗时
从onResume执行完成到第一帧渲染完成所花费的时间就是Render耗时。Render耗时可以用下面的代码计算出来。
Activity.java

@Override
protected void onResume() {
    super.onResume();
    final long start = System.currentTimeMillis();
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            Log.d(TAG, "onRender cost:" + (System.currentTimeMillis() - start));
            return false;
        }
    });
}

4.预加载启动的首页Activity

5.动态加载so,nativeload比较耗时

其他优化方案:

对于启动优化,还有一些黑科技,首先,就是我们采用了类预加载的方式,可以在MultiDex.install方法之后起一个线程,然后用Class.forName的方式来预加载类,当这个类真正被使用的时候,就不用再进行类加载的过程了。

FaceBook推出的Redex优化Dex方案,官方说使用Redex可以减少20%的apk体积和降低25%的启动速度。
其原理是将应用启动相关的类放入同一个主dex中,减少应用启动时主dex查询类的时间。

WebView提前创建好,因为webview创建的耗时较长,如果首屏有h5的页面,可以提前创建好。Application的onCreate时,就可以开始在子线程中进行后面要用到的Layout的inflate工作了,最先想到的应该是官方提供的AsyncLayoutInflater

1.Activity预加载

long startTime = System.currentTimeMillis();
          预加载 Class.forName
通过上面的统计,我们知道哪些类加载比较耗时,可选择在合适的时机,将对应的类(特别是kotlin的很多类)适当在子线程加载。

Class.forName(className, true, context.classLoader) 
1
//其中initialize参数设置为true,表示会初始化静态代码块,和赋值静态变量等操作
 Log.d( "preNewActivity 耗时: " + (System.currentTimeMillis() - startTime));
上一篇下一篇

猜你喜欢

热点阅读