提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


记录项目的一次App启动优化过程

  • 前言
  • 一、App启动优化时机?
  • 二、优化点
  • 1.Application OnCreate
  • 2.Activity 生命周期中耗时操作
  • 三、如何分析耗时
  • 1.使用adb命令获取总的启动时间
  • 2.利用TraceView分析启动时间
  • 3.使用AspectJ 在方法上标注DebugLog注解,来统计各方法耗时
  • 三、优化点
  • 1.异步加载
  • 2.延期加载IdleHandler,或者放到需要用到的地方之前加载()
  • 3.启动的Activity布局足够简单层级不能过深
  • 4.项目启动多进程会多次执行Application OnCreate
  • 总结
  • 参考文章



前言

应用的启动速度缓慢是我们在开发过程中经常会遇到的问题,比如启动缓慢导致的黑屏,白屏问题,本篇博客旨在记录一次App启动优化的过程。



一、App启动优化时机?

首先我们来看张图

ios应用的启动时间怎么抓 应用启动时间记录_ios应用的启动时间怎么抓

经历的生命周期是

ios应用的启动时间怎么抓 应用启动时间记录_初始化_02


冷启动的时间其实就是上面的这段时间,在这些生命周期中,我们自己的业务处理尤其是第三方的SDK初始化都是放在Application 的OnCreate中的,当然启动的第一个Activity的生命周期中如果有耗时任务也会影响启动速度。

二、优化点

1.Application OnCreate

2.Activity 生命周期中耗时操作

三、如何分析耗时

从点击应用的启动图标开始创建出一个新的进程直到我们看到了界面的第一帧,这段时间就是应用的启动时间

1.使用adb命令获取总的启动时间

adb shell am start -W [PackageName]/[PackageName.SplashActivity]

在Android 8.0的手机上会展示下图参数,TotalTime 就是本次启动的耗时

ios应用的启动时间怎么抓 应用启动时间记录_App_03


该指令一共给出了三个时间:

ThisTime:最后一个启动的Activity的启动耗时
TotalTime:自己的所有Activity的启动耗时
WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)

2.利用TraceView分析启动时间

在Application OnCreate 开始和结尾打上trace。

Debug.startMethodTracing("app-start-log");
...
initNetSDK();
initCrashReport();
preLoadWebView();
...
Debug.stopMethodTracing();

程序启动后,会生成trace文件。位置在storage/emulated/0/Android/data/包名/files/app-start-log.trace 使用DDMS 打开后就可以看到如下图界面

ios应用的启动时间怎么抓 应用启动时间记录_加载_04


一般只需要关注:Calls + Recur Calls / Total和 Cpu Time / Call

Cpu Time / Call反映调用次数不多,但每次调用却需要花费很长时间的函数

Calls + Recur Calls / Total反映自身占用时间不长,但调用却非常频繁的函数

3.使用AspectJ 在方法上标注DebugLog注解,来统计各方法耗时

@Around("(method() || constructor()) && @annotation(debugLog)")
 public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, DebugLog debugLog) throws Throwable {
        enterMethod(joinPoint, debugLog);
		//时间差就是 methodExeuTime = stopNanos - startNanos
        long startNanos = System.nanoTime();
        Object result = joinPoint.proceed();
        long stopNanos = System.nanoTime();
		//打印方法耗时日志
        exitMethod(joinPoint, debugLog, result, TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos));

        return result;
    }

三、优化点

必要且耗时:启动初始化,考虑用线程来初始化
必要不耗时:首页绘制
非必要但耗时:数据上报、插件初始化
非必要不耗时:不用想,这块直接去掉,在需要用的时再加载

然后按需进行加载优化

ios应用的启动时间怎么抓 应用启动时间记录_android_05

1.异步加载

我的项目里主要是x5浏览器SDK初始化,优化时间

private void preinitX5WebCore() {
 		//放入线程池初始化
        Injection.provideJobExecutor().execute(() -> QbSdk.initX5Environment(getApplicationContext(), null));
    }

放入异步模块中还有CrashReport,短视频SDK,Oss文件上传,FFmpeg库的初始化,云店模块日志初始化

2.延期加载IdleHandler,或者放到需要用到的地方之前加载()

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {  
            // 初始化UIKit模块
            initUIKit();
            return false;
        }
    });

把Activity中耗时(必须主线程执行的)的任务放入IdleHandler执行,那么这个时候当主线程执行完队列里的消息才会执行遍历执行IdleHandler

3.启动的Activity布局足够简单层级不能过深

  • 通过减少冗余或者嵌套布局来降低视图层次结构
  • 用 ViewStub 替代在启动过程中不需要显示的 UI 控件
  • 使用自定义 View替代复杂的 View 叠加

4.项目启动多进程会多次执行Application OnCreate

云信会拉起自己的:core推送进程,所以不需要在每个进程中都去执行初始化操作

//需要在两个进程中执行
        NIMClient.init(this, getLoginInfo(), NimSDKOptionConfig.getSDKOptions(this));
        if (NIMUtil.isMainProcess(this)) {
        	//只在主进程初始化
        	...
        	initApp();
        	init
        }

总结

单前我们的App原来启动需要2s左右,经过上面的步骤优化后启动时间控制在1s左右,目前大部分App启动耗时也是在这个水平,App启动优化其实能做的还有很多,下面的这些可以从系统层面去优化。当然App的优化需要贯穿到整个App的开发当中需要不但去发掘新的可优化点,不打磨才是王道。

  • 启动窗口优化
  • 系统调度优化
  • GC 优化
  • IO 优化
  • 资源重排
  • 类重排
  • 厂商优化

如有不对之处还望指出,共同学习,加油打工人!!!


参考文章

链接: Android App 启动优化全记录.
链接: Android App启动优化深度实践 (由2.4s优化到1s以内).