【项目优化】App启动优化实战

96
pphdsny
0.7 2018.08.28 17:27* 字数 1256

前言

某次在开发阶段,发现App启动过程中既然有3-4s的白屏时间,瞬间慌了,到底干了些什么???

分析

启动时间统计

# 完整命令
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
# 常用命令
adb shell am start -W packageName/Activity类全路径(启动Activity)
# -c和-a参数是可选的,允许您为intent指定<category>和<action>。

启动时间如下(小米5s)-Debug环境:

ThisTime TotalTime WaitTime
4357 4357 4384

看到数据还是很惊讶的,立马下载了个线上版本,用上述命令统计了下启动时间 -Release环境:

ThisTime TotalTime WaitTime
1279 1279 1304

心中终于松了一口气,既然是这个版本出的问题,那就开始查原因。

ThisTime:最后一个启动的Activity的启动耗时;

TotalTime:自己的所有Activity的启动耗时(多个Activity,如果只启动一个Activity的时候TotalTime=ThisTime);

WaitTime: ActivityManagerService启动App的Activity时的总时间

如果只关心某个应用自身启动耗时,参考TotalTime;如果关心系统启动应用耗时,参考WaitTime;如果关心应用有界面Activity启动耗时,参考ThisTime

详情计算说明可参考:https://www.zhihu.com/question/35487841

官方指导

官方指导:launch-time

官方译文:应用启动时长

从指导文章中可以看出,App启动时长过长,基本就是做了很多耗时的操作,比如在Application的onCreate中做了很多初始化导致。

找问题瓶颈

我们需要知道到底哪些操作引起了耗时,我们需要去分析Application中都做了哪些耗时操作,于是找了以下一些工具:

  • Method Tracing
  • Android Profiler
  • Systrace(待研究)
  • BlockCanary
  • 自己手动统计

经过一些尝试后发现,只有Systrace和自己手动统计能满足自己需求,后面细说。

自己手动统计

没有好的方法只能用笨方法,虽然要改造原有代码,但好在灵活性较高,快速出结果:

    public static void traceMethod(String source, Action0 func) {
        if (PropertyUtils.isProduct()) {
            func.call();
        } else {
            long startTime = System.currentTimeMillis();
            func.call();
            long endTime = System.currentTimeMillis();
            LogUtils.w("Method Measure: source:[" + source + "][" + (endTime - startTime) + "]");
        }
    }

将你需要统计时长的方法,经过traceMethod进行一层包装,在log中进行参看。这个方式是否笨拙,但是有效简单。后续如有其他方式可以再替换。

时长数据

[getComponent().inject]--[278]
[RouterManager.init]--[0]
[EnvironmentController.getInstance().init(provider)]--[1]
[UIManager.init(this)]--[0]
[UtilsManager.init(this)]--[7]
[DRImageLoader.init(this)]--[242]
[initLibs]--[252]
[initViewBinding]--[61]
[initRouter]--[923] —>37
[TinkerHelper.initTinker()]--[2]
[initAppComponent()]--[0]
[initARouter()]--[1]
[initCrashHandler()]--[2]
[initLibs()]--[654]
[SDKInitializer.initialize(this)]--[60]
[initPushSdk()]--[47]
[initStatisticsTools()]--[256]
[initOnAppProcess()]--[11]

[Application onCreate]—[2405]

时长分析

超过100的都应该优化

initRouter—923ms->37ms

if (PropertyUtils.isDebugOpen()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this);

罪魁祸首就是下面两行代码,也不知道干了啥,去掉后时间降到37ms

 ARouter.openLog();     // 打印日志
 ARouter.openDebug();

initLibs()— 654ms

这个方法里面做了很多依赖库的初始化,再进行细分看下各个依赖库的init耗时

[DRCustomerNetController.getInstance().init()]--[78]
[initUBT()]--[215]
[initCustomerId()]--[0]
[initResourceConfig()]--[3]
[RongYunManager.init()]--[281]
[PlatformManager.init()]--[101]
[getRouterService.init()]--[1]

需要优化的几个地方:

  • initUBT
  • RongYunManager.init
  • PlatformManager.init

initUBT—215ms

牵扯到UBT组件,后续再优化

RongYunManager.init—281ms

移至子线程

PlatformManager.init—101ms

移至子线程

getComponent().inject—278ms

牵扯到组件实例生成,暂不处理

DRImageLoader.init--242ms

不可移至子线程,初始化移至第一次调用

必须在Application中进行调用,底层fresco的imageView在实例化的时候就会去检测

initStatisticsTools--256ms

移至子线程

优化后结果

ThisTime TotalTime WaitTime
2838 2838 2850

[getComponent().inject]--[230]
[RouterManager.init]--[0]
[EnvironmentController.getInstance().init(provider)]--[0]
[UIManager.init(this)]--[1]
[UtilsManager.init(this)]--[6]
[DRImageLoader.init(this)]--[78]
[initLibs]--[89]
[initViewBinding]--[63]
[initRouter]--[33]
[TinkerHelper.initTinker()]--[1]
[initAppComponent()]--[0]
[initCrashHandler()]--[1]
[ISPMUBTProtocol.init()]--[252]
[initUBT()]--[260]
[initCustomerId()]--[0]
[initResourceConfig()]--[6]
[initAppEnv()]--[269]
[getRouterService.init()]--[2]
[initLibs()]--[273]
[SDKInitializer.initialize(this)]--[44]
[initPushSdk()]--[42]
[initOnAppProcess()]--[12]
Method Measure:Application onCreate耗时:805
Method Measure:initOnWorkThread耗时:653

优化后发现Application的onCreate还耗时800多毫秒,主要是由于UBT、Component、Image的初始化导致,暂无较好优化方案,后续观察;

还有个很奇怪的现象就是App启动要2838,减去Application的805,还剩2000+的时间,这部分时间是谁消耗的,暂无定论,猜测是一些static和单例导致的。由于没有办法去监控,先优化到这样子。

优化方案

  • 异步初始化(比如一些非必须的组件可以放在子线程进行初始化)
  • 延迟初始化(一般的launchActivity会有几秒的启动屏,可以在里面做一些初始化操作,比如ImageLoader的init)
  • 懒加载,用到才初始化(有很多组件并不是应用启动时候就要使用,这个可以参考Dagger)

实现代码如下:


    /**
     * 在App线程进行初始化
     */
    private void initOnAppProcess() {
        TinkerHelper.initTinker();
        initRouter();
        DRCustomerNetController.getInstance().init();
        initAppComponent();
        initCrashHandler();
        initComponentService();
        //资源下发配置
        initResourceConfig();
        SDKInitializer.initialize(this);
        DRPushManager.initPushSdk();
        initGrowingio();
        registerActivityLifecycleCallbacks(new PageLifecycleCallbacks());
    }

    /**
     * 非阻塞的,在子线程进行初始化
     */
    private void initOnWorkThread() {
        new Thread() {
            @Override
            public void run() {
                long startTime = System.currentTimeMillis();
              //不与主线程争抢资源
                            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
                //self lib
                initPlatform();
                //third lib
                RongYunManager.getInStance().init(innerInstance, AppConstans.RONG_CLOUD_APP_KEY);
                initBugly();
                long endTime = System.currentTimeMillis();
                LogUtils.d("Method Measure:initOnWorkThread耗时:" + (endTime - startTime));
            }
        }.start();
    }

后记

发现小米设备的启动要2s+之后,又用其他设备尝试了下基本在1s以内,通过systrace捕获发现小米中bindApplication就用了1.5s左右。后续有编译了个release版本重新运行了结果如下:

ThisTime TotalTime WaitTime
835 835 849

看到这个结果还是很欣慰的(提速了35%),看来release版本还是做了一些优化,小米系统坑啊!

项目优化
Web note ad 1