App启动优化方案

作者: 皮球二二 | 来源:发表于2017-12-01 13:38 被阅读361次

    你一定遇到过这种情况:点击App图标,先出现一个白屏或者黑屏页面,然后过一会才进入主界面。这时候你会懵逼,这是什么呀?!这种用户体验是极度糟糕的。通过本篇文章,我们就会知道这个问题产生的原因以及如何避免这个问题的产生

    App启动方式

    冷启动:冷启动指的是应用程序从无到有的启动方式。在应用程序自设备启动以来第一次启动或系统杀死应用程序等情况下会发生冷启动。这种类型的启动在最小化启动时间方面的挑战是最大的,因为系统和应用程序比其他启动状态有更多的工作要做。

    来看看应用程序冷启动的整体流程


    应用程序冷启动过程

    application以及activity初始化是一个相当复杂的过程,而白屏或者黑屏恰恰就是这个过程的产物:在应用程序刚启动到应用程序初始化完全之间有一段时间空隙,这时候系统创建了一个空白窗口,叫StartingWindow。这个窗口让人感觉activity已经启动了只是没填充好数据。它是个临时窗口,对应的WindowType是TYPE_APPLICATION_STARTING。这个空白窗口是为了告诉用户,系统已经接受到操作,正在响应。应用程序初始化完成加载完第一帧之后,这个窗口就会被移除。
    Window的顶层是DecorView,所以StartingWindow显示一个空DecorView,但是会给这个DecorView应用当前activity指定的Theme,如果这个activity没有指定Theme就用application的。activity启动时,windowBackground比setContentView()要先加载,这一段时间如果Theme是Light,屏幕就是白色的;如果是Dark,就会显示黑屏。
    上述就是黑白屏的原因了

    热启动:热启动要比冷启动简单得多,开销也更低。热启动下,系统只是把你的activtiy带到前台而已。如果应用程序中所有的activtiy仍然留存在内存中,那么就可以避免重复初始化对象还有绘制和渲染布局。但如果内存紧张造成应用程序被回收,那这些工作依然要重新执行。热启动与冷启动有着相同的屏幕显示行为:系统进程在直到应用程序完成渲染activity为止之前会一直显示一个空白窗口,即StartingWindow。

    温启动
    用户退出你的App,然后又重新启动它。这个过程可能会让应用程序继续执行,但是需要重新创建activity。

    App启动时间

    为了对App启动性能进行正确的评估,启动时间这一指标就显得尤为重要。App的启动时间指的是从点击应用的启动图标开始到我们看到了界面的第一帧这段时间

    系统提供的启动时间方案

    自Android 4.4之后,logcat新增一个输出,关键字叫Displayed,这个关键字可以粗略的提现App启动时间

    Displayed
    ADB命令提供的启动时间方案
    adb shell am start -w packagename/activity
    

    我们来运行一下这个命令看看效果

    adb shell am start -W com.house365.aizuna/com.house365.rent.ui.activity.signin.SplashActivity
    Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.house365.aizuna/com.house365.rent.ui.activity.signin.SplashActivity }
    Warning: Activity not started, its current task has been brought to the front
    Status: ok
    Activity: com.house365.aizuna/com.house365.rent.ui.activity.index.IndexActivity
    ThisTime: 234
    TotalTime: 234
    WaitTime: 251
    Complete
    

    可以看到这里有三个时间参数ThisTime、TotalTime、WaitTime,简单介绍一下这三个参数:
    ThisTime:一连串启动的activity中的最后一个activity的启动耗时
    TotalTime:新进程和Activity的启动耗时,但不包括前一个应用activity中pause()方法的耗时
    WaitTime:总的耗时,包括前一个应用activity中pause方法的耗时和新应用启动的时间
    一般情况下TotalTime跟ThisTime的值是相同的。有一个场景会导致这2个值不相等,比如点击App图标,先启动一个无界面的activity做逻辑处理,接着又启动一个有界面的activity

    时间流程
    • 在第①个时间段内,AMS创建ActivityRecord记录块和选择合理的Task、将当前resume的activity进行pause
    • 在第②个时间段内,启动进程、调用无界面activity的onCreate()等、 pause()/finish()无界面的activity
    • 在第③个时间段内,调用有界面activity的onCreate()、onResume()

    需要注意的是这些时间值虽然每次都不一样,也谈不上绝对的正确,但是他们之间的差别不会太大,还是能够说明一些问题的

    利用TraceView分析启动时间

    TraceView是什么,TraceView是Android平台特有的数据采集和分析工具,主要用做热点分析,找出最需要优化的点。TraceView从代码层面分析性能问题,针对每个方法来分析,比如当我们发现自己的App出现卡顿的时候,可以用它来分析出现卡顿时在方法中有没有很耗时的操作。

    通过TraceView,可以得到两组很重要的数据:
    单次执行最耗时的方法
    执行次数最多的方法

    TraceView

    采集数据的方式有两种,
    第一种是通过代码来得到traceview文件,你可以直接在需要统计的代码部分头尾设置,比如我这里是统计application中的onCreate()方法

    @Override
    public void onCreate() {
        super.onCreate();
        Debug.startMethodTracing("aizuna.trace");
        *****************  省略中间无数代码  *******************
        Debug.stopMethodTracing();
    }
    

    生成的traceview文件会自动放在SDCARD上,没有SDCARD卡会出现异常,所以使用这种方式需要确保应用的AndroidMainfest.xml中的SD卡的读写权限是打开的,其中aizuna.trace是traceview文件的名字,然后用adb导出traceview文件。


    traceview文件
    adb pull /storage/sdcard0/aizuna.trace /Users/renyu/Downloads
    

    使用Android Device Monitor打开traceview


    image.png

    第二种同样是使用Android Device Monitor


    image.png

    先选择应用进程,然后点击Start Method Profiling(开启方法分析),按钮会变为Stop Method Profiling(停止方法分析),开启方法分析后,对应用的目标页面进行测试操作,测试完毕后停止方法分析,界面会自动跳转到DDMS的trace分析界面。

    两种方式的对比:第一种方式更精确到方法,起点和终点都是自己定,不方便的地方是自己需要添加方法并且要导出文件,第二种方式的优缺点刚好相反。

    开发者最关心的数据主要是:
    Calls + Recur Calls / Total:调用次数+递归次数,一般用来查找自身占用时间不长,但调用却非常频繁的方法
    Cpu Time / Call:某个函数消耗CPU的平均时间,一般用来查找调用次数不多,但每次调用却需要花费很长时间的方法
    耗时超过500ms都是值得注意的
    点击每个方法, 可以看到其父方法(调用它的)和它的所有子方法(它调用的),同时上方的该方法执行时间轴会闪动,显示为该方法的执行线程及相对时长

    现在来看看我的项目中application启动耗时

    TraceView

    大多数都是集中在第三方库,比如阿里热修复、Fresco

    从Theme方向来优化加载时间

    我们都希望自己App在启动加载过程中与加载完成之后的主题效果接近,而不是很突兀的将这个缓慢的过程被系统直接展示出来
    有一个较通用的做法就是直接使用windowDisablePreview。windowDisablePreview就是禁用窗口的预览动画。在SplashActivity显示出来之前,系统永远不会使用该Activtiy的主题来显示加载过程,这样就保证了不会出现白屏或者黑屏现象。但是,与设置windowIsTranslucent属性一样,如果启动过程中有过多复杂的操作,就会出现过了好几秒才展现App,会给用户造成App没有正常运行的错觉,微信貌似就是这种体验
    谷歌建议不要使用此种方法,而是建议遵循Material Design,在主题上使用windowBackground属性为SplashActivity提供一个简单的自定义Drawable

    先创建一个drawable

    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
      <!-- The background color, preferably the same as your normal theme -->
      <item android:drawable="@android:color/white"/>
      <!-- Your product logo - 144dp color version of your app icon -->
      <item>
        <bitmap
          android:src="@drawable/product_logo_144dp"
          android:gravity="center"/>
      </item>
    </layer-list>
    

    随后运用到style上

    <style name="SplashTheme" parent="AppTheme">
        <item name="android:windowBackground">@drawable/drawable_launch_screens</item>
    </style>
    

    最后配置在AndroidManifest上

    <activity
        android:name=".ui.activity.signin.SplashActivity"
        android:configChanges="orientation|keyboard|keyboardHidden|screenSize"
        android:screenOrientation="portrait"
        android:theme="@style/SplashTheme">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
    
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    

    如果你想过渡到App本身的主题,只需要在super.onCreate()方法和setContentVIew()方法之前调用setTheme(R.style.AppTheme)即可

    public class MyMainActivity extends AppCompatActivity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        // Make sure this is before calling super.onCreate
        setTheme(R.style.Theme_MyApp);
        super.onCreate(savedInstanceState);
        // ...
      }
    }
    

    这里简单介绍下其中陌生的属性:
    android:opacity=”opaque”是为了防止在启动的时候出现背景的闪烁
    layer-list你可以自行百度一下相关的文档,不是很复杂

    性能问题分析

    上面的做法可以达到"秒开"APP的效果,不过却不是真正提升App启动的速度。真正优化这些问题还需对application和activity进行下手。
    这里给出几点建议:
    1、很多第三方SDK都放在application初始化,我们可以将初始化的代码放到使用到的地方再进行相关操作,比如科大讯飞、百度地图这种SDK
    2、SplashActivity中的布局尽量减少复杂性以及布局深度,因为在View绘制的过程中,测量也是很耗费性能的。也可以采用ViewStub的形式进行懒加载优化
    3、不要在这两处方法中进行耗时操作。这些I/O操作也应该放到使用到的地方或子线程中执行。
    4、对于多进程的应用来说,每新建一个进程就会初始化application一次,这样会造成application的onCreate()方法执行多次,所以需要考虑哪些初始化操作只要执行1次

    public static String getProcessName(int pid) {    BufferedReader reader = null;    try {        reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline"));        String processName = reader.readLine();        if (!TextUtils.isEmpty(processName)) {            processName = processName.trim();        }        return processName;    } catch (Throwable throwable) {        throwable.printStackTrace();    } finally {        try {            if (reader != null) {                reader.close();            }        } catch (IOException exception) {            exception.printStackTrace();        }    }    return null;}
    

    5、Multidex的使用,也是拖慢启动速度的元凶,必须要做优化。

    不过有些时候真的没办法去去除这些初始化的功能,比如Fresco,比如热修复,所以我们也只能尽量的做上述这些事情

    参考文章

    Launch-Time Performance
    Android 中如何计算 App 的启动时间?
    App启动白屏和黑屏如何处理?
    Android性能优化第(六)篇---TraceView 分析图怎么看

    相关文章

      网友评论

      • VMI_0572:赞一个
      • a3f4fdbfbb5e:如果启动页不是图片而是个布局的话应该怎么弄呢
        皮球二二:@阿祥呦 你这些应该在splash里面做,目前的解决方案只是一个假的drawable,跟ios那张静态图一样
        a3f4fdbfbb5e:@r17171709 还有个问题就是启动页是个图片的话 如果我想加个alpha动画怎么搞 xml布局需要放个view 然后实现么
        皮球二二:@阿祥呦 好像不行,因为是设置在drawable里面,不过你可以通过layer-list简单布局一下

      本文标题:App启动优化方案

      本文链接:https://www.haomeiwen.com/subject/cjsvbxtx.html