随着App的逻辑不断巨大,一不留意就会将耗时的操作放置在运用发动进程之中,导致运用发动速度越来越慢,用户体验也越来越差。优化发动速度是简直一切大型App运用开发者需求考虑的问题。优化发动速度之前首先需求精确测量App发动时刻,这样有利于咱们更精确可量化地看出优化作用,也能够辅导咱们进行继续优化。转载请注明出处:Lawrence_Shen 同时能够参阅2019年的功能剖析文章:Android功能剖析&发动优化

##- 运用命令行办法 运用命令行办法核算屡次发动某个Activity的平均用时能够在shell中执行如下指令:

adb shell am start -S -R 10 -W com.example.app/.MainActivity

其间-S表明每次发动前先强行中止,-R表明重复测试次数。每一次的输出如下所示信息。

Stopping: com.example.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.app/.MainActivity }
Status: ok
Activity: com.example.app/.MainActivity
ThisTime: 1059
TotalTime: 1059
WaitTime: 1073
Complete

其间TotalTime代表当时Activity发动时刻,将屡次TotalTime加起来求平均即可得到发动这个Activity的时刻。 ###缺点

  1. 运用的发动进程往往不只一个Activity,有或许是先进入一个发动页,然后再从发动页翻开真实的主页。某些状况下还有或许中间经过更多的Activity,这个时分需求将多个Activity的时刻加起来。
  2. 将多个Activity发动时刻加起来并不彻底等于用户感知的发动时刻。例如在发动页或许是先等候某些初始化完结或许某些动画播放完毕后再进入主页。运用命令行核算的办法仅仅核算了Activity的发动以及初始化时刻,并不能表现这种等候任务的时刻。
  3. 没有在AndroidManifest.xml对应的Activity声明中指定<intent-filter>或许属性没有android:exported="true"的Activity不能运用这种命令行的方式核算发动时刻。

##-思考更精确的办法   以上基于命令行的办法存在诸多问题,迫使咱们思考怎样才能得到从用户视点上调查更精确的发动时刻。在测验其他办法之前,咱们先界说一下怎样才是从用户视点上调查的发动时刻。 ###冷发动、热发动(留意不是官方的界说,是咱们从用户视点考虑的界说)

  • 冷发动时刻:冷发动表明用户初次翻开运用,这时进程还没创立,包含了Application创立的进程。冷发动时刻指从第一次用户点击Launcher中的运用图标开端,到主页内容全部展现出来的时刻。
  • 热发动时刻:热发动表明用户在主页按了返回,主页Activity现已Destroy,不过Application仍在内存中存在,对应的进程并没有被杀掉,不包含Application创立进程。热发动时刻指在Application依然存在的状况下,从用户点击桌面图标,到主页内容全部展现出来的时刻。

###App发动流程   要优化以及剖析发动时刻,需求先了解App的发动流程。以冷发动为比如,Application以及Activity的发动流程如下,参阅文章[3][4][5][6]

如何统计Android-App启动时间

更为直观和简单的流程图参阅Colt McAnlis在Android Performance Patterns Season 6中的表述。有爱好的同学能够点击链接看看(Youtube链接)。

如何统计Android-App启动时间

  从流程图以及参阅Colt McAnlis的Android Performance Patterns[6]得知,在冷发动的进程中,首先会经过AMS在System进程展现一个Starting Window(通常状况下是个白屏,能够经过设置Application的theme修正),接着AMS会经过Zygote创立运用程序的进程,并经过一系列的进程后调用Application的attachBaseContext()onCreate()然后终究调用Activity的onCreate()以及进行View相关的初始化作业。在Activity展现出来后会替换掉之前的Starting Window,这样发动进程结束。 ###怎么加log   参阅[1]发现在Activity中onWindowFocusChanged()办法是最好的Activity对用户可见的标志,因此综合上一节的剖析,咱们能够考虑在Application的attachBaseContext()办法中开端核算冷发动计时,然后在真实主页Activity的onWindowFocusChanged()中中止冷发动计时,这样就能够开端得到运用的冷发动时刻。

public void onWindowFocusChanged(boolean hasFocus)

Called when the currentandroid.view.Window of the activity gains or loses focus. This is the best indicator of whether this activity is visible to the user.

为了方便核算,设置一个Util类专门做计时,增加的代码如下:

/**
 * 计时核算东西类
 */
public class TimeUtils {
    private static HashMap<String, Long> sCalTimeMap = new HashMap<>();
    public static final String COLD_START = "cold_start";
    public static final String HOT_START = "hot_start";
    public static long sColdStartTime = 0;
    /**
     * 记载某个事情的开端时刻
     * @param key 事情称号
     */
    public static void beginTimeCalculate(String key) {
        long currentTime = System.currentTimeMillis();
        sCalTimeMap.put(key, currentTime);
    }
    /**
     * 获取某个事情的运转时刻
     *
     * @param key 事情称号
     * @return 返回某个事情的运转时刻,调用这个办法之前没有调用 {@link #beginTimeCalculate(String)} 则返回-1
     */
    public static long getTimeCalculate(String key) {
        long currentTime = System.currentTimeMillis();
        Long beginTime = sCalTimeMap.get(key);
        if (beginTime == null) {
            return -1;
        } else {
            sCalTimeMap.remove(key);
            return currentTime - beginTime;
        }
    }
    /**
     * 铲除某个时刻运转时刻计时
     *
     * @param key 事情称号
     */
    public static void clearTimeCalculate(String key) {
        sCalTimeMap.remove(key);
    }
    /**
     * 铲除发动时刻计时
     */
    public static void clearStartTimeCalculate() {
        clearTimeCalculate(HOT_START);
        clearTimeCalculate(COLD_START);
        sColdStartTime = 0;
    }
}

然后在Application的attachBaseContext()办法中增加如下代码:

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (/**假如是主进程**/) {
        TimeUtils.beginTimeCalculate(TimeUtils.COLD_START);
    }
}

在第一个Activity的onCreate()办法中增加如下代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    calculateStartTime();
    ....
}
private void calculateStartTime() {
    long coldStartTime = TimeUtils.getTimeCalculate(TimeUtils.COLD_START);
    // 这儿记载的TimeUtils.coldStartTime是指Application发动的时刻,终究的冷发动时刻等于Application发动时刻+热发动时刻
    TimeUtils.sColdStartTime = coldStartTime > 0 ? coldStartTime : 0;
    TimeUtils.beginTimeCalculate(DictTimeUtil.HOT_START);
}

在真实的主页Activity的 onWindowFocusChanged()办法中增加如下代码:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    if (hasFocus && /**没有经过广告或许引导页**/) {
        long hotStartTime = TimeUtils.getTimeCalculate(TimeUtils.HOT_START);
        if (TimeUtils.sColdStartTime > 0 && hotStartTime > 0) {
            // 真实的冷发动时刻 = Application发动时刻 + 热发动时刻
            long coldStartTime = TimeUtils.sColdStartTime + hotStartTime;
            // 过滤掉反常发动时刻
            if (coldStartTime < 50000) {
                // 上传冷发动时刻coldStartTime 
            }
        } else if (hotStartTime > 0) {
            // 过滤掉反常发动时刻
            if (hotStartTime < 30000) {
                // 上传热发动时刻hotStartTime 
            }
        }
    }
}

###防止坑的Checklist   上面的剖析给了咱们开端的加log的起始和结束点,然而在实践的核算中会发现得到的数据有20%左右是不精确的,表现在计时数据非常大,有些甚至会显现冷发动时刻超过一天。经过剖析,在核算发动计时的时分需求留意一些问题。以下列举一下增加log时分需求留意的checklist。

  1. 运用在发动进程或许会有广告(咱们的事务是有道词典),第一次发动会有引导页,需求根据事务状况标记在没有广告、没有引导页的时分才核算。这种状况要留意在非正常发动的时分疏忽发动时刻核算。

  2. 由于词典主页之前还有几个Activity,在没到主页Activity之前假如过早的返回,会出现冷发动时刻过长的问题。这是由于词典返回的时分并没有杀掉进程,而时刻核算信息是保存在内存中的,而等下次再进入的时分由于是热发动不会重新开端冷发动计时。这导致了这次热发动实践上打log的时分发现有前次冷发动的开端时刻,算成了冷发动,而且由于发动时刻是上一次的,所以这次冷发动log的时刻比实践时刻长。这种状况要留意在主页Activity之前的其他ActivityonPause()办法中调用TimeUtils.clearStartTimeCalculate();铲除计时。

  3. 除了正常的发动流程,运用还有很多或许会导致Application的创立的入口,例如点击桌面小插件、系统账号同步、Deep Link跳转、直接进入设置了<action android:name="android.intent.action.PROCESS_TEXT" />的Activity、push达到等。咱们需求检查一切有或许引起Application创立,可是不是正常发动流程的当地,调用TimeUtils.clearStartTimeCalculate();铲除计时,防止引起冷发动时刻核算过长过错的问题。

##- 运用第三方东西   为了测试发动的进程中哪些办法比较耗时,咱们能够运用Android Studio中集成的Android Monitor提供的Method Tracering或许Systrace。不过在实践中发现,有另外一个nimbledroid东西运用愈加简便且能更明确指出耗时的当地。上传了运用之后会自动剖析情景如下图所示。其间会自动检测出主页的Activity而且给出冷发动的发动状况。

如何统计Android-App启动时间

  点击进入Cold Startup的情景能够看到主要耗时的办法如下图。

如何统计Android-App启动时间

至于为什么nimbledroid会知道那个是咱们主页的Activity,官网上解析如下:

We use a heuristic to tell when an app finishes startup by detecting when (1) the main Activity has been displayed and (2) things like animated progress bars in the main Activity have stopped. Based on our experiments, this heuristic works in most cases.

点击进入某个办法,能够看到这个办法具体是由于调用了哪个子办法导致了耗时的问题。

如何统计Android-App启动时间

  经过nimbledroid这个东西,咱们能够比较轻松地发现一些比较显着的问题,并能够辅导咱们进行发动优化。同时nimbledroid还支持Memory Leaks、网络监测以及成果共享等一些功能,更多的功能有待读者继续发现。

##- 后记

  核算和剖析发动时刻有利于辅导咱们优化发动时刻。以上介绍了有道词典在进行发动优化中的剖析进程。经过具体了解Android运用发动的流程,进行精确的log记载,而且结合第三方东西,咱们终究得到精确的发动时刻核算数据以及发动优化的一些条理。具体优化的办法参加下一篇文章《怎么优化Androd App发动速度》。

##- 参阅 【1】单刀土豆,2016.Android 开发之 App 发动时刻核算 【2】Android Developer,Launch-Time Performance 【3】./multi_core_dump,2010.Android Application Launch 【4】./multi_core_dump,2010.Android Application Launch Part 2 【5】罗升阳,2012.Android系统源代码情景剖析 【6】Colt McAnlis,2016.Android Performance Patterns Season 6