冷启动html
当启动应用时,后台没有该应用的进程(常见如:进程被杀、首次启动等),这时系统会从新建立一个新的进程分配给该应用java
暖启动android
当启动应用时,后台已有该应用的进程(常见如:按back
键、home
键,应用虽然会退出,可是该应用的进程是依然会保留在后台,可进入任务列表查看),因此在已有进程的状况下,这种启动会从已有的进程中来启动应用git
热启动github
相比暖启动,热启动时应用作的工做更少,启动时间更短。热启动产生的场景不少,常见如:用户使用返回键退出应用,而后立刻又从新启动应用shell
热启动和暖启动由于会从已有的进程中来启动,不会再建立和初始化Application
数据库
平时咱们讨论中基本都会将暖启动和热启动合在一块儿统称为热启动,由于暖启动与热启动差别很小,若是不是特别留意启动流程,那么在用户体验和感官上没有直接差别,可是在framework
层执行时是有必定差别的。本次优化点也是围绕冷启动和热启动来作,将暖启动与热启动统称为热启动缓存
另外有一点,从绝对时间上来看,app
安装后的首次启动将会最耗时,由于首次启动会新建数据库,sp
文件,各类缓存,配置等性能优化
白屏或黑屏,具体是哪个,取决于app
的Theme
使用的是dark
仍是light
主题微信
Android Studio 引发的白屏
2.x
时代的AS
开启了instant run
之后可能会致使白屏,但实际完整的apk
包不会出现此问题
冷启动引发的白屏/黑屏
点击你app
那一刻到系统调用Activity.onCreate()
之间的时间段。在这个时间段内,WindowManager
会先加载app
主题样式中的windowBackground
做为app
的预览元素,而后再真正去加载activity
的layout
布局
暖启动/热启动引发的白屏/黑屏
这点在配置较好,内存空间充足的手机上不是很明显,但低端手机或者内存吃紧的状况下依旧会出现”闪屏”效果,持续时间很短,一闪而过
我将冷启动优化分为可控阶段和不可控阶段
不可控阶段
点击app
之后到初始化Application
之间这段时间,系统接管,从Zygote
进程中fork
建立新进程,GC
回收等等一系列操做,和咱们app
无关
可控阶段
初始化Application
开始,以下图
从上图能够看到,整个冷启动流程中至少有两处onCreate
,分别是Application
和Activity
,整个流程都是可控的。因此,onCreate
方法作的事情越多,冷启动消耗的时间越长
Logcat 自动打印
从Android 4.4(API 19)
开始,Logcat
自动帮咱们打印出应用的启动时间。这个时间从应用启动(建立进程)开始计算,到完成视图的第一次绘制(即Activity
内容对用户可见)为止
04-25 14:53:09.317 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.activity.InitializeActivity: +4s256ms
04-25 14:53:11.077 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.main.MainActivity: +559ms
复制代码
04-25 14:53:20.407 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.activity.InitializeActivity: +178ms
04-25 14:53:22.447 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.main.MainActivity: +131ms
复制代码
com.android.server.am.ActivityRecord#reportLaunchTimeLocked(long curTime)
中打印出来的:private void reportLaunchTimeLocked(final long curTime) {
final ActivityStack stack = task.stack;
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;
if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);
EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
userId, System.identityHashCode(this), shortComponentName,
thisTime, totalTime);
StringBuilder sb = service.mStringBuilder;
sb.setLength(0);
sb.append("Displayed ");
sb.append(shortComponentName);
sb.append(": ");
TimeUtils.formatDuration(thisTime, sb);
if (thisTime != totalTime) {
sb.append(" (total ");
TimeUtils.formatDuration(totalTime, sb);
sb.append(")");
}
Log.i(ActivityManagerService.TAG, sb.toString());
}
mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
if (totalTime > 0) {
//service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
}
displayStartTime = 0;
stack.mLaunchStartTime = 0;
}
// normal time:统计的是 Activity 从启动到界面绘制完毕的时间
// total time :统计的是 normal time + Activity 栈创建完毕的时间
复制代码
测量 Activity 启动时间
Activity
的reportFullyDrawn()
咱们能够在Activity
的任意位置调用此方法已打印你想看到的、执行完某个方法的最终时间。它会在Logcat
里打印从apk
初始化(和前面Displayed
的时间是同样的)到reportFullyDrawn()
方法被调用用了多长时间
ActivityManager: Displayed com.Android.myexample/.StartupTiming: +768ms
复制代码
在4.4
上调用reportFullyDrawn()
方法会崩溃(可是log
仍是能正常打印),提示须要UPDATE_DEVICE_STATS
权限 ,可是这个权限没法拿到
try {
reportFullyDrawn();
} catch (SecurityException e) {
}
复制代码
本地调试启动时间
上述命令能够直接启动对应包名的对应activity
,但要注意不是所有activity
都能使用这个命令直接启动
adb screenrecord 命令
bugreport
选项(它能够在frames
中添加时间戳-应该是L
中的特性)的screenrecord
命令app
图标,等待app
显示,ctrl-C
中止screenrecord
命令,在手机存储中会生 成aunch.mp4
视频文件,而后pull
到电脑frame
时间戳。一直往前直到你发现app
图标高亮了为止。这个时候系统已经处理了图标上的点击事件,开始启动app
了,记录下这一帧的时间。继续播放帧直到你看到了app
整个UI
的第一帧为止。根据不一样状况(是否有启动窗口,是否有启动画面等等)。事件和窗口发生的实际顺序可能会有不一样。对于一个简单的app
来讲,你会首先见到启动窗口,而后渐变出app
真实的UI
。在你看到UI
上的任何内容以后,再记录下第一帧,这时app
完成了布局和绘制,准备开始显示出来了。同时也记录下这一帧所发生的时间((UI displayed) - (icon tapped))
获得app
从点击到绘制就绪的全部时间。虽然这个时间包含了进程启动以前的时间,可是至少它能够用于跟其余app
比较因而可知,app
冷启动时间大约为4s
,热启动时间大约为132ms
.
从启动流程分析
减小两处onCreate()
中的初始化操做,将部分初始化移动到IntentService
中进行
从用户体验分析
将app
首页的按返回键响应修改成响应Home
键,曲线救国。让用户觉得app
确实退出了,可是其实是点了Home
键。如此一来,下次点击app
图标的时候,直接唤起,不须要进行初始化操做,主要能够避免再次走闪屏页,参考美团,微信,QQ,淘宝等(实现的效果同样,可是实现方式就不得而知了)
Home
键的操做同样,底部选中tab
没有作自切换Home
键的操做不同,底部选中tab
作了自切换tab
,而后再退出利用Google
官方文档推荐的方式,咱们将启动页界面的主题设置为SplashTheme
。此界面是冷启动后首先加载的界面:
主题内的代码以下:
这个主题至关于丢了一张图片做为背景,也就是红色背景Logo
和Slogan
图片,无版本号
此时咱们已经“消除了”白屏/黑屏页,将冷启动的白屏/黑屏单调的纯色背景替换为咱们即将展现给用户的 InitializeActivity
界面的图片,从系统的Window
到咱们本身App
跳转过程,使用了全屏属性,以达到无缝跳转
须要说明的是,这一步作了以后,对总体启动时间并无任何的减小,时间不变,只是说给用户的体验要友好不少,再也不显示一个突兀的白屏/黑屏界面 ------将锅甩给本身app
,app
太卡,竟然在InitializeActivity
要等这么久(固然用户不知道的是:系统window
界面和InitializeActivity
不是同一个界面)
固然也能够采用另外一种方式,那就是将上面的主题中的backgroud设置为透明用户点击了图标开始启动的时候,界面上没有任何变化,由于此时系统启动的那个白屏/黑屏界面背景透明的 ------将锅甩给系统,太卡,点了图标竟然隔了这么久才显示InitializeActivity
接下来咱们查看Application
和InitializeActivity
的onCreate()
是否有能够迁移到IntentService
中的代码:
能够看到,其实当中的逻辑不是不少,而且都是须要在Application
中初始化完毕的,不能单独提出来进行初始化,其中只有GrowingIO
能够考虑提出来
推送初始化能够提出来
onCreate 中乍看没有什么耗时操做,内部的几个方法也都是必需要的业务逻辑,惟一能动的就是内部针对界面停留作的延时时间,目前是2s,能够减小到1s左右。
最后一步,在MainActivity
中处理返回键逻辑。
将确实是退出的逻辑替换为按Home
键:
这种作法给用户一个假象:用户按返回键退出,可是实际上并没退出,app
处于后台,下次点击图标时直接唤起
针对这种操做,须要注意几个点
onRestart
中须要判断tab
状态onSaveInstanceState
和onRestoreInstanceState
中须要保存和恢复数据,用于判断用户是点了 home
仍是back
,这两种操做须要区分开,同时须要保存tab
状态Home
键事件结果
冷启动:
热启动:
由上图可知,优化后的冷启动时间大约为3544ms
,热启动时间大约为127ms
,相比以前的4121ms
以及151ms
来讲有必定的提高,白屏效果也被消除了。
可是其实提高最大的点不是白屏优化,由于咱们没有把Application
和Activity
中onCreate
的逻辑减小并提到IntentService
中。
最大的提高点是,咱们让用户退出app
时,形成假象,让用户觉得他确实退出了app
,但实际上咱们是藏在后台,当用户热启动或者温启动时,咱们不用再通过InitializeActivity
的流程进入首页。
onCreate
中尽可能避免作过多的初始化动做,若是必须,那么考虑IntentService
Back
和Home
键的动做作一些假象,使用户按Back
键时觉得他退出了,以减小下次启动的没必要要动做(建议:非即时消息类和社交类app
,这种作法慎用,由于可能有流氓之嫌。。。(逃))Activity#moveTaskToBack(true)