Android App启动优化深度实践 (由2.4s优化到1s以内)

3,191 阅读9分钟

朝阳杨少爷(ID:CY_YANG_DA_YE),专注于Android领域的开发者、分享者。

背景

我们的APP新版本,从2017年4月份提交第一行代码开始,就现在已经有两年半的时间,在这两年多的时间里,APP的内容内容不断丰富,例如先后加入了求职招聘、问答、个人中心、二手机,小视频等等模块。同时对于以前的旧功能也在不断地完善,例如,丰富了发帖的内容、小视频详情页像抖音一样方便快捷、标签的聚合更加精准的吸引用户。

在功能、内容丰富的同时,不免会引入很多第三方的工具,例如友盟、个推、神策、腾讯云的视频组件、IM及时通讯组件,GreenDao数据库等等

随着代码量越来越多,第三方工具越来多的导致APP,首次启动的时候,时间较长,用户体验较差。

Nimbledroid统计Google Play各类别APP冷启动平均耗时 (Nexus 5 + Android 4.4)
[图片上传失败...(image-c82b9a-1565334335822)]
而Google Play排名前100的非游戏类的应用的冷启动时间统计为

39个冷启动时间在2秒以内
73个冷启动时间在3秒以内

而我们APP的冷启动时间为:

执行命令行

adb shell am start -W -n 包名/包名.activity.MainEntryActivity

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

也就是说,在启动的时候,需要2.4s

可见,还有很大的优化空间。

接下来,就通过本篇内容,深入的剖析一下,启动慢的原因,并给出合理的解决方案。

启动过程分析

开始,优化启动的过程,那么就要先了解一下,Android系统,在启动一个APP的过程是什么样的?都做了什么事儿?

冷启动、热启动

冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用。

热启动:当启动应用时,后台存在该应用的进程(back键,home键,应用退出,但是没有销毁),从已有的进程中启动。

因为热启动是后台进程已经存在了,所以启动速度比较快,这里提到的优化是指冷启动。

Android冷启动过程相对比较复杂,需要经历35步,简单来说,需要5个过程

整个应用程序的启动过程要执行很多步骤,但是整体来看,主要分为以下五个阶段:

一. Step1 - Step 11:
Launcher通过Binder进程间通信机制通知ActivityManagerService,
它要启动一个Activity;

二. Step 12 - Step 16:
ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态;

三. Step 17 - Step 24:
Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,
于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,
即将要启动的Activity就是在这个ActivityThread实例中运行;

四. Step 25 - Step 27:
ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,
以便以后ActivityManagerService能够通过这个Binder对象和它进行通信;

五. Step 28 - Step 35:
ActivityManagerService通过Binder进程间通信机制通知ActivityThread,
现在一切准备就绪,它可以真正执行Activity的启动操作了。

在更加简单一点,从用户点击桌面图标开始,大致会经历以下几个过程:



在这个启动过程当中,可能会存在的几个问题:

1.点击图标很久都不响应。

2.首页显示太慢。

3.首页显示后无法操作。

而接下来,重点解决的问题就是,点击图标很久都不响应的问题。

优化工具

当,遇到一个问题的时候,到最后解决的步骤,无外乎有以下几个步骤。


Hugo是 jake Wharton大神制作的工具,可以在log当中打印每个方法的执行时间,可以帮我们定位到运行时间比较长的方法和函数,配置也相对简单。

接下来,就在代码当中实践一下。

本想观察一下 Application当中,初始化的时间

  @DebugLog
  @Override
    public void onCreate() {
        super.onCreate();
        initHttpClient();
//        //初始化配置
        initGlobal();
//        CrashCaptureHandler.getInstance().initCrashHandler();
        initJPush();
        initGetTuiPush();
        initSource();
        initSmallVideoRecord();
        initIM();
        if (BuildConfig.DEBUG) {
            boolean hostUrlEnable = new HostUrlUtils().initHost(URLConstants.class.getDeclaredFields(), URLConstants.RELEASE_SERVER);
            if (hostUrlEnable) {
            Dispatcher.hostUrlClass = URLConstants.class;
            }
           
        } else {
            //正式环境才开始crash防护
            install();
            initAliyunLog();
        }
        initHuoDongHeZi();

        initSensor();
        initUmeng();
        initLinkMe();
        initRN();
    }

观察打印的Log

2019-08-10 16:10:09.752 30634-30634/> V/MyApplication: ⇢ onCreate()
2019-08-10 16:10:09.941 30678-30678/? V/MyApplication: ⇢ onCreate()
2019-08-10 16:10:10.004 30692-30692/? V/MyApplication: ⇢ onCreate()
2019-08-10 16:10:10.124 30737-30737/? V/MyApplication: ⇢ onCreate()
2019-08-10 16:10:10.529 30678-30678/? V/MyApplication: ⇠ onCreate [587ms]
2019-08-10 16:10:10.569 30634-30634/? V/MyApplication: ⇠ onCreate [816ms]
2019-08-10 16:10:10.832 30737-30737/? V/MyApplication: ⇠ onCreate [707ms]
2019-08-10 16:10:10.881 30692-30692/? V/MyApplication: ⇠ onCreate [877ms]
2019-08-10 16:10:13.917 31109-31109/?:xg_service_v3 V/MyApplication: ⇢ onCreate()
2019-08-10 16:10:14.441 31109-31109/?:xg_service_v3 V/MyApplication: ⇠ onCreate [524ms]

可以发现,原来初始化onCreat被多次调用!

正常情况下,一个应用会开启一个进程,那么application会被执行一次,说明在启动的过程当中,不仅如此,因为初始化的重复调用,导致初始化的时间就有 2391ms!这就极大的影响到APP的启动速度!给用于一种,点击完图标没有反应的感觉。

解决问题

既然,问题已经发现了,就要解决这些问题。

既然发现是进程的问题,那么就要看看,在启动的过程当中,总共有哪些进程。

2019-08-10 16:40:07.881 3166-3166/? D/yzc: ?
2019-08-10 16:40:07.978 3225-3225/? D/yzc: ?:pushcore
2019-08-10 16:40:08.128 3298-3298/? D/yzc: ?:ipc
2019-08-10 16:40:08.172 3245-3245/? D/yzc: ?:pushservice
2019-08-10 16:40:11.754 3739-3739/?s:xg_service_v3 D/yzc: ?s:xg_service_v3

从中,可以看到除了APP的进程之外,还有 信鸽推送的进程,ipc进程,个推进程,极光的进程。

之前可能某些业务需要,集成了三家的推送,而每个推送都会在后台默默的开启一个进程来接收推送消息。自然就会导致APP的第一次初始化启动很慢。

和运营小伙伴确认,当前只有信鸽推送在使用,个推,极光就给去掉。那么去掉之后,再看一下启动时间。



同时也避免了多次初始化的问题!

相比之前的2391ms 优化了800ms! 接下来,继续优化,看看哪里还有可以优化的空间。

继续优化

Application初始化优化

在Application当中,可以看到,初始化的内容有

initGlobal();
初始化配置
initIM();
初始化及时通讯
initSource();
初始化APP相关版本配置
initSmallVideoRecord();
初始化短视频
install();
初始化放在Crash
initAliyunLog();
初始化阿里云相关内容
initHuoDongHeZi();
初始化活动盒子
initSensor();
初始化神策
initUmeng();
初始化友盟
initLinkMe();
初始化深度连接
initRN();
初始化ReactNative

分别统计一下每个初始化需要的的时间。

initBbsGlobal [73ms]
initIM [95ms]
initSource [0ms]
initSmallVideoRecord [11ms]
install [0ms]
initAliyunLog [19ms]
initHuoDongHeZi [69ms]
initSensor [191ms]
initUmeng [14ms]
initLinkMe [63ms]
initRN [0ms]

根据统计,可以看到耗时较长的方法有神策、IM聊天、活动盒子、深度连接、配置文件。
对于这些时间长的,在不影响功能的情况下,可以尝试放在子线程当中进行初始化,不占用主线程的资源。

 new Thread(new Runnable() {
            @Override
            public void run() {
                initIM();
                initSource();
                initSmallVideoRecord();
                initLinkMe();
                initUmeng();
                if (!URLConstants.RELEASE_SERVER) {
                    boolean hostUrlEnable = new HostUrlUtils().initHost(URLConstants.class.getDeclaredFields(), URLConstants.RELEASE_SERVER);
                    if (!hostUrlEnable) {
                        new AlertDialog.Builder(getApplicationContext())
                                .setTitle("未检测到可用的URL")
                                .setMessage("请确诊URL地址可用")
                                .setPositiveButton("退出", null).show();
                    }
                    Dispatcher.hostUrlClass = URLConstants.class;
                } else {
                    //正式环境才开始crash防护
                    install();
                    initAliyunLog();
                }
                initHuoDongHeZi();

            }
        }).start();

经过改造,看一下启动时间。



相比之前又优化了 300ms!,相比没有优化之前优化了1132ms! 也就是快了 1秒 多的时间,可以说效果还是十分明显的。

同时可以看到,Application的初始化时间当前仅需199ms


MainEntryActivity初始化优化

在 Application的优化告一段落,关注一下 MainEntryActivity 这个 Activity 是进入app的一个Activity,在这个 Activity的onCreate()当中 进行了一些请求操作,看看这里面有什么可以优化的地方。

在这个里,有两个操作造成耗时时间比较长,一个是

isCityInviteNetWork()

这个方法是当用户登录的时候,根据用户获取到用户的地理位置信息并存储。

这个方法完全可以放在子线程当中,不占用主线程的资源。

checkAdversiterment()

这个是在网络获取开屏广告的方法,这个需要请求接口,获取到开屏广告并展示的方法。

对于这个方法,也可以子线程当中异步操作,第一次请求下来缓存起来,从第二次进入APP,在进行展示,这样避免了主线程的耗时操作。

经过这些操作之后,可以看到启动时间,已经优化到了1s左右。


最后

虽然,没有目前没有达到秒开的效果,但是比起优化之前已经有了很大的区别,优化效果。今后,还会继续优化,争取达到一个更好的效果。

之前我看有朋友说可以设置主题达到秒开的效果,我试了一下好像不太行,也需要大概500ms左右,如果大家有好的想法和建议,欢迎给我留言!我们一起讨论!

参考:
www.jianshu.com/p/e69d22ec0…
www.jianshu.com/p/1e65aa223…
www.jianshu.com/p/496529bd1…
blog.csdn.net/u013263323/…

欢迎关注公共号

关注公众号会有更多收获!