Android App启动优化深度实践 (由2.4s优化到1s以内)
这是第「28篇」原创分享
朝阳杨少爷(ID:CY_YANG_DA_YE),专注于Android领域的开发者。
背景
我们的APP新版本,从2017年4月份提交第一行代码开始,就现在已经有两年半的时间,在这两年多的时间里,APP的内容内容不断丰富,例如先后加入了求职招聘、问答、个人中心、二手机,小视频等等模块。同时对于以前的旧功能也在不断地完善,例如,丰富了发帖的内容、小视频详情页像抖音一样方便快捷、标签的聚合更加精准的吸引用户。
在功能、内容丰富的同时,不免会引入很多第三方的工具,例如友盟、个推、神策、腾讯云的视频组件、IM及时通讯组件,GreenDao数据库等等
随着代码量越来越多,第三方工具越来多的导致APP,首次启动的时候,时间较长,用户体验较差。
Nimbledroid统计Google Play各类别APP冷启动平均耗时 (Nexus 5 + Android 4.4)
而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
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) {
new AlertDialog.Builder(getApplicationContext())
.setTitle("未检测到可用的URL")
.setMessage("请确诊URL地址可用")
.setPositiveButton("退出", null).show();
}
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左右,如果大家有好的想法和建议,欢迎给我留言!我们一起讨论!
共同学习,写下你的评论
评论加载中...
作者其他优质文章