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

前言

某次在开发阶段,发现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版本还是做了一些优化,小米系统坑啊!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容