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

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

背景

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

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

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

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

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

而咱们APP的冷启动时间为:bash

执行命令行网络

adb shell am start -W -n 包名/包名.activity.MainEntryActivity
复制代码

ThisTime:最后一个启动的Activity的启动耗时;
TotalTime:本身的全部Activity的启动耗时;
WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和本身Activity的启动)。
app

也就是说,在启动的时候,须要2.4s异步

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

接下来,就经过本篇内容,深刻的剖析一下,启动慢的缘由,并给出合理的解决方案。

启动过程分析

开始,优化启动的过程,那么就要先了解一下,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/…

欢迎关注公共号

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

相关文章
相关标签/搜索