Code Monkey home page Code Monkey logo

coldstart's Introduction

App 启动优化

启动方式

  • 冷启动

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

  • 暖启动

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

  • 热启动

    相比暖启动,热启动时应用做的工作更少,启动时间更短。热启动产生的场景很多,常见如:用户使用返回键退出应用,然后马上又重新启动应用

热启动和暖启动因为会从已有的进程中来启动,不会再创建和初始化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:

    ctivityonCreat

    ​ 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 工程冷启动白屏时间比线上工程短一些

LICENCE

NONE

掘金同步更新

coldstart's People

Contributors

hidetag avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

coldstart's Issues

外链唤起的问题

在外链唤起的情况下,使用的是目标 Activity 的 windowBackground。
如果这个 Activity 自己设置了背景色,可能会比较怪。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.