Android App 冷启动优化方案

App 启动优化

启动方式

  • 冷启动html

    当启动应用时,后台没有该应用的进程(常见如:进程被杀、首次启动等),这时系统会从新建立一个新的进程分配给该应用java

  • 暖启动android

    当启动应用时,后台已有该应用的进程(常见如:按back键、home键,应用虽然会退出,可是该应用的进程是依然会保留在后台,可进入任务列表查看),因此在已有进程的状况下,这种启动会从已有的进程中来启动应用git

  • 热启动github

    相比暖启动,热启动时应用作的工做更少,启动时间更短。热启动产生的场景不少,常见如:用户使用返回键退出应用,而后立刻又从新启动应用shell

热启动和暖启动由于会从已有的进程中来启动,不会再建立和初始化Application数据库

平时咱们讨论中基本都会将暖启动和热启动合在一块儿统称为热启动,由于暖启动与热启动差别很小,若是不是特别留意启动流程,那么在用户体验和感官上没有直接差别,可是在framework层执行时是有必定差别的。本次优化点也是围绕冷启动和热启动来作,将暖启动与热启动统称为热启动缓存

另外有一点,从绝对时间上来看,app安装后的首次启动将会最耗时,由于首次启动会新建数据库,sp文件,各类缓存,配置等性能优化


白屏/黑屏问题

  • 白屏或黑屏,具体是哪个,取决于appTheme使用的是dark仍是light主题微信

  • Android Studio 引发的白屏

    2.x时代的AS开启了instant run之后可能会致使白屏,但实际完整的apk包不会出现此问题

  • 冷启动引发的白屏/黑屏

    点击你app那一刻到系统调用Activity.onCreate()之间的时间段。在这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,而后再真正去加载activitylayout布局

  • 暖启动/热启动引发的白屏/黑屏

    这点在配置较好,内存空间充足的手机上不是很明显,但低端手机或者内存吃紧的状况下依旧会出现”闪屏”效果,持续时间很短,一闪而过

优化

我将冷启动优化分为可控阶段和不可控阶段

  • 不可控阶段

    点击app之后到初始化Application之间这段时间,系统接管,从Zygote进程中fork建立新进程,GC回收等等一系列操做,和咱们app无关

  • 可控阶段

    初始化Application开始,以下图

    冷启动应用程序工做流示意图

    从上图能够看到,整个冷启动流程中至少有两处onCreate,分别是ApplicationActivity,整个流程都是可控的。因此,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
    复制代码
    • 这个 log 信息是从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 启动时间

    ActivityreportFullyDrawn()

    咱们能够在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都能使用这个命令直接启动

    • 热启动 :

    热启动

    • 冷启动:

    冷启动

    • 只须要关注TotalTime便可
  • 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作了自切换
    • QQ:同微博
    • 淘宝:启动后在首页按返回键,会先回到第一个tab,而后再退出

开始优化

  • 利用Google官方文档推荐的方式,咱们将启动页界面的主题设置为SplashTheme。此界面是冷启动后首先加载的界面:

    mainifest

    主题内的代码以下:

    style

    这个主题至关于丢了一张图片做为背景,也就是红色背景LogoSlogan图片,无版本号

    此时咱们已经“消除了”白屏/黑屏页,将冷启动的白屏/黑屏单调的纯色背景替换为咱们即将展现给用户的 InitializeActivity界面的图片,从系统的Window到咱们本身App跳转过程,使用了全屏属性,以达到无缝跳转

    须要说明的是,这一步作了以后,对总体启动时间并无任何的减小,时间不变,只是说给用户的体验要友好不少,再也不显示一个突兀的白屏/黑屏界面 ------将锅甩给本身appapp太卡,竟然在InitializeActivity要等这么久(固然用户不知道的是:系统window界面和InitializeActivity不是同一个界面)

    固然也能够采用另外一种方式,那就是将上面的主题中的backgroud设置为透明用户点击了图标开始启动的时候,界面上没有任何变化,由于此时系统启动的那个白屏/黑屏界面背景透明的 ------将锅甩给系统,太卡,点了图标竟然隔了这么久才显示InitializeActivity

  • 接下来咱们查看ApplicationInitializeActivityonCreate()是否有能够迁移到IntentService中的代码:

    • BaseApplication:

    BaseApplication

    ​ 能够看到,其实当中的逻辑不是不少,而且都是须要在Application中初始化完毕的,不能单独提出来进行初始化,其中只有GrowingIO能够考虑提出来

    • MainApplication:

    MainApplication

    ​ 推送初始化能够提出来

    • InitializeActivity:

    ActivityonCreate

    ​ onCreate 中乍看没有什么耗时操做,内部的几个方法也都是必需要的业务逻辑,惟一能动的就是内部针对界面停留作的延时时间,目前是2s,能够减小到1s左右。

  • 最后一步,在MainActivity中处理返回键逻辑。

    将确实是退出的逻辑替换为按Home键:

    activity返回键

    这种作法给用户一个假象:用户按返回键退出,可是实际上并没退出,app处于后台,下次点击图标时直接唤起

    针对这种操做,须要注意几个点

    • onRestart中须要判断tab状态
    • onSaveInstanceStateonRestoreInstanceState中须要保存和恢复数据,用于判断用户是点了 home仍是back,这两种操做须要区分开,同时须要保存tab状态
    • 经过广播监听Home键事件
    • ......
  • 结果

    冷启动:

    优化后冷启动

    热启动:

    优化后热启动

    由上图可知,优化后的冷启动时间大约为3544ms,热启动时间大约为127ms,相比以前的4121ms以及151ms来讲有必定的提高,白屏效果也被消除了。

    可是其实提高最大的点不是白屏优化,由于咱们没有把ApplicationActivityonCreate的逻辑减小并提到IntentService中。

    最大的提高点是,咱们让用户退出app时,形成假象,让用户觉得他确实退出了app,但实际上咱们是藏在后台,当用户热启动或者温启动时,咱们不用再通过InitializeActivity的流程进入首页。


总结

  • 白屏/黑屏界面使用图片替换
  • onCreate中尽可能避免作过多的初始化动做,若是必须,那么考虑IntentService
  • 首页中对BackHome键的动做作一些假象,使用户按Back键时觉得他退出了,以减小下次启动的没必要要动做(建议:非即时消息类和社交类app,这种作法慎用,由于可能有流氓之嫌。。。(逃)
  • Activity#moveTaskToBack(true)

参考资料


TODO

  • [ ] 探究:为什么新建的 Hello World 工程冷启动白屏时间比线上工程短一些

代码传送门

相关文章
相关标签/搜索