测试小妹反馈说,我们的相册Gallery和友商的相比,启动时间慢了不少,相册是消费者使用最多的App之一,重要性不言而喻,因此boss要求我们对相册的启动时间做一个优化,既然提到了优化,就不能光做启动时间的优化了,连根带土把布局优化,内存优化等一起做了。我就用几篇文章把之前对相册优化过程记录一下。

App启动方式

Android App的启动方式一般来说分为两种:冷启动和热启动。
1、冷启动:当应用程序第一次启动的时候,后台还没有该应用的进程,这时候系统会重新创建至少一个进程给该应用(应用可能有不止一个进程)。所以会先创建和初始化Application类,然后才进入Activity(包括一系列的测量、布局、绘制),最后显示在界面上;
2、热启热启动的开销比冷启动少得很多,因为此时应用的进程已经常驻在内存中,不在需要初始化Application类,避免了许多重复对象的初始化,一系列的测量、布局和绘制,热启动产生的场景有很多,比如:用户点击Back键退出应用,然后马上又重新启动。

启动时间统计

Android为我们测量App启动时间提供了一些工具,从Android KitKat版本开始,Logcat中会输出从程序启动到某个Activity显示到画面上所花费的时间,如下图所示

iOS APP启动时序图 app启动时间优化_启动时间

adb里面 也有查看启动时间的命令:adb shell am start -W 包名/入口类全路径名

iOS APP启动时序图 app启动时间优化_启动时间_02

WaitTime就是总的耗时,包括前一个应用Activity pause的时间和新应用启动的时间;ThisTime表示一连串启动Activity的最后一个Activity的启动耗时;TotalTime表示新应用启动的耗时,包括新进程的启动和Activity的启动,但不包括前一个应用Activity pause的耗时。也就是说,我们只需要关注TotalTime即可,这个时间才是自己应用真正启动的耗时,这也正是我们需要优化的启动时间。

Method Tracer分析启动过程

靠人力从Application到Activity一行一行地检查代码不仅效率低而且不容易发现问题,其实Android Studio为我们提供了一系列的性能检测工具,对于Gallery的启动优化,我是利用trace文件来分析启动过程。如下图所示,在Android Studio中,点击开始,结束,Android Studio就自动生成了此段时间内应用函数的执行情况,这个操作很方便,但是不好控制,很难抓到应用的整个启动过程,不过不用担心,还有另外一个办法,我们只需在需要分析的代码的开头和结尾分别加上Debug.startMethodTracing()和Debug.stopMethodTracing()这两个方法,然后重新开启运用,在这个默认目录下生成.trace文件(也可以指定存储路径):
data/packagename/files/XXX.trace

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        ......
        Debug.startMethodTracing();
        //Debug.startMethodTracing(String tracePath);
        ......
    }

    @Override
    protected void onResume() {
        super.onResume();
        Debug.stopMethodTracing();
        ......
    }

然后可以通过Android Studio中打开trace文件

iOS APP启动时序图 app启动时间优化_启动时间优化_03

窗口的上半部分是时间轴面图,线宽度代表执行该函数本身操作所用的时间, 将鼠标放在线条上面会显现该函数的具体信息;窗口的下半部分是分析面板,各列含义
Name 表示函数名
Inclusive Time 表示函数本身运行花费时间 + 函数调用其他函数时间,
Exclusive Time 表示函数本身运行的时间
Invocation Count 表示函数调用的次数
大概知道这些参数的意思就可以开始干活了。点击Inclusive Time,将函数按照耗时从高到低排列,先排除系统自身的函数,发现我们自己自己定义的startDefaultAction()这个函数耗时比较多。

iOS APP启动时序图 app启动时间优化_启动时间_04

private void startDefaultAction() {
        setContentView(R.layout.gallery_main);
        initActionBar();
        initViewPager();
        initBottomBar();
        resetToolBarPosition();
    }

从以上代码基本可以分析出,耗时比较长的估计就是initViewPager()这个函数,因为其他几个函数就是标题栏的初始化工作,进入initViewPager()这个函数:

private void initViewPager() {
        mViewPager = (CustomViewPager) findViewById(R.id.viewpager);
        mFragmentList = new ArrayList<>();

        if (GalleryUtils.isCreatePageVisible(this)) {
            mPageNumber++;
        }
        for (int i = 0; i < mPageNumber; i++) {
            initPages(i);
        }
        mAdapter = new MyPagerAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mAdapter);
        mViewPager.addOnPageChangeListener((OnPageChangeListener) mAdapter);
        mViewPager.setCurrentItem(PAGE_MOMENTS);
        switchTab(PAGE_MOMENTS);
        mViewPager.setOffscreenPageLimit(2);
        ......
   }

在Gallery中,我们采用的是ViewPager+Fragment的架构来展示相册的三个界面,而每个界面涉及到大量照片的加载,非常耗时,从上代码看出,在初始化的时候就将三个界面全部预加载,这个虽然有助于提升界面切换的流畅度,但是大量的界面初始化肯定会影响到冷启动的时间,况且,显示第一个界面时,没有必要讲第三个界面也加载进来,完全可以等滑动到第二个界面时再预加载。为了平衡启动时间和界面切换的流畅度,除了主界面,只能预加载一个界面,把mViewPager.setOffscreenPageLimit(2)这行代码注释掉即可(不设置setOffscreenPageLimit()时,Viewpager默认预加载左右两边各一个项).
首先看下没有修改前的启动时间:

iOS APP启动时序图 app启动时间优化_性能优化_05

修改后的启动时间:

iOS APP启动时序图 app启动时间优化_iOS APP启动时序图_06

可以看到,在相同的条件先,只预加载一个界面时,启动的时间提升了30%左右,效果非常明显,而且界面之间的切换并未受到明显的影响。到此,启动时间优化基本完成,但是还有可以优化的空间,那就是界面布局的优化,不过这需要放到后面的文章更详细地解析。利用Method Tracer对trace文件分析App启动过程,算是比较基础的了,不过这样也已经能满足我们的需求了。