Android App秒开的奥秘

什么是秒开

Android App秒开,狭义的讲是指你的App的Activity从启动到显示所花费的时间在1秒之内,广义的讲是指这个过程所花费的时间越少越好。这个时间越短,你的App给用户的感受就是响应越快,使用越流畅,用户体验更好。秒开是Android App的一个很重要的性能指标。须要咱们持续的给予关注和优化。 #如何优化秒开 Google提供了不少性能优化的建议和官方的工具,网上也有很是多的关于Android App性能优化的文章和工具,能够帮助你解决大部分卡顿的问题。可是现实却多是即便你付出了不少精力去作优化,你的App仍是在启动新Activity的时候花费过多的时间。特别是随着需求的不断增加,你的App会变得复杂而庞大,要作优化首先要定位须要优化的点,而这会变得愈发困难。同时大型App在启动新Activity的时间花费过多状况出现的可能性反而会愈来愈大。html

在众多的优化建议中,有一条比较基本的原则是尽可能避免在主线程(或者说UI线程)中进行耗时操做。例如文件读写操做、网络请求、大量计算、循环等等。直观的理解是由于启动新Activity须要在主线程执行不少代码,例如onCreate()等生命周期的回调。若是此时有耗时操做的代码在主线程被执行,到新Activity展现出来所须要的时间就会延长。要优化秒开,首先要能监测主线程的运行状态,那么问题来了,主线程究竟是怎样在运行呢?你的代码又是何时,如何在主线程被执行的呢?java

深刻主线程

要了解主线程的工做过程,首先要了解Android的消息机制。android

消息机制

先看一下现实生活中的一个例子,虽然如今都是移动支付了,但相信你们都去银行取过钱。当你到达银行的时候,若是你是第一个,那恭喜你,你能够立刻到柜员那里办理你的业务;若是你前面还有人,那就比较惨了,你须要排队,得等到你前面的人都办完业务才会轮到你;更可怕的是若是你前面有几位须要办理的业务花费的时间比较长,那你须要等更长的时间;后面来的人则会按顺序排在你身后,和你同样不耐烦的琢磨何时才能轮到本身。git

抽象一下,消息机制其实和这个例子十分相似。每一个人都看作是个消息,何时到的银行是不肯定的。柜员能够看作一个消息处理器,他帮你办业务就至关于在处理你的消息;而人们按照前后顺序排起来的队伍能够看作是个消息队列。因此这个过程能够抽象为有个消息处理器,他有个消息队列,随机来到的消息按照必定顺序排列在这个队列里,消息处理器不停的从队列头部获取消息而后处理之,周而复始的循环重复这个过程。以下图所示: github

消息机制
那么Android是怎样怎样实现这个消息机制的呢?

Android的消息机制

消息机制首先得有消息,在Android中就是Message。怎样能肯定一个消息呢?消息要有来源或者目标,也就是target;消息要代表本身要作什么,也就是what或者callback;消息要代表本身但愿在何时执行,也就是when。有了这几个要素,基本上这个消息就是个完备的消息了,能够被加入到消息队列中了。Android中的消息队列是MessageQueue。消息处理循环是Looper。Looper是个死循环,不停的从MessageQueue中获取消息而后处理之,具体的执行是在Handler里面进行的。另外消息加入消息队列也须要Handler来操做。Message,MessageQueue,Looper,Handler组合在一块儿,就构成了整个Android的消息机制。性能优化

Android的主线程就运行着这样一个消息机制。bash

Android的主线程

主线程是在ActivityThread中建立的,能够看到在main函数中网络

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        Looper.loop();
    }
复制代码

主线程实现了一个消息机制。因此Android的主线程就是个消息处理的循环。它所作的工做就是在不停的从消息队列获取消息,处理消息,周而复始。你的App全部的在UI上的操做,例如点击事件的处理、页面动画、显示更新页面、View绘制、启动新Activity等操做都是在给主线程发消息,主线程而后挨个处理这些消息。app

主线程如何影响秒开

咱们了解了主线程的工做机制后,就要看看主线程中的消息处理是如何影响Activity秒开的。 当咱们要启动一个新的Activity的时候,从调用startActivity开始到新Activity显示出来,Android系统会发送一系列的消息给主线程。这一系列的消息处理所花费的总时间会影响页面的秒开,若是执行时间过长,用户就会有响应很是慢的感受。此外,除了Android系统会给主线程发消息,App自身也会给主线程发消息,若是在启动新Activity的过程当中,这些App本身的消息正好插入这一系列的Android系统消息中,那也会致使总的处理时间延长,形成不能秒开。 ide

秒开示意
上图表明了启动新Activity的主线程的三种状况,每一个矩形表明主线程处理一个消息所花的时间,越宽表明处理的时间越长。绿色填充的表明这是一个Android系统发过来的消息;蓝色填充的表明这是一个App本身发过来的消息。最下方的向右箭头表明时间,起点是startActivity被调用的时刻。

  • 第一种情况表明正常的情形,主线程中只有和startActivity相关的系统消息被处理,并且处理每一个消息所花费的时间都在合理范围内。因此这个页面能够知足秒开。
  • 第二种状况表明一个异常的情形,虽然主线程处理的消息都是系统消息,可是某一个或某几个消息的处理时间超出了合理值,致使页面不能秒开。
  • 第三种状况表明另外一种异常的情形,在系统消息中混入了App本身的消息,主线程不只要处理系统消息,还要处理App本身的消息,结果就是总的启动时间要额外加上App消息的处理时间,致使页面不能秒开。 实际状况中还有可能会出现既有系统消息处理时间过长同时也混有App本身的消息的情形.

秒开优化

了解了影响秒开的因素以后,咱们只要有办法能监测主线程中每一个消息处理时间,咱们就能定位到形成页面卡慢的缘由,而后再作优化。 幸亏Android工程师为咱们在Looper中预留了打log的位置。

public static void loop() {
        final Looper me = myLooper();
        ...
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            Message msg = queue.next(); // might block
           ...
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
           ...
            msg.recycleUnchecked();
        }
    }

public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }
复制代码

可见在消息被处理的开始和处理结束以后都会打印log。 你只须要在代码中调用Looper.setMessageLogging()设置一下就好。

Looper.getMainLooper().setMessageLogging(new Printer() {
                @Override
                public void println(String s) {   
                    Log.v("debug", s);
                }
            });
复制代码

编译运行你的程序,你会在logcat输出看到相似这样的log:

Message logging
每行 “>>>>> Dispatching to”开头的log表明一个消息即将开始被处理;紧接着下一行“<<<<< Finished to”开头的log表明这一消息处理完毕。经过这些log你能够知道全部被主线程处理的消息,并能够根据开始结束的时间差知道每一个消息消耗的时间。有了这些信息你能够找到致使你的app卡慢的消息,而后进一步去debug问题。

在你启动一个新的Activity的时候你能够观测这样的log输出,看看里面有没有处理时间比较长的消息,或者看看里面有没有App本身的消息被处理,若是有的话,这些都是须要优化的点。

然而直接看log的缺点是这样的log会比较多,并且并不容易定位启动Activity的开始和结束时间点,另外每一个消息处理的时间也要本身计算,并非十分直观。

StallBuster

为了方便的进行秒开优化,我作了个工具叫StallBuster来协助定位Activity秒开失败的缘由。

集成StallBuster很是简单,只须要两步就能够了

  1. 添加对StallBuster的依赖
dependencies {
    compile 'com.github.zhangjianli:stallbuster:1.1'
}
复制代码
  1. 在你的App的Application中添加如下代码
public class YourApplication extends Application {
    @Override
    public void onCreate() {
        StallBuster.getInstance().init(this);
        super.onCreate();
    }
}
复制代码

这样就能够了,编译运行你的App。在你的App中打开新的Activity,StallBuster会发出一个Notification。告诉你刚启动这个Activity花了多少毫秒

notification
点击这个Notification就会打开StallBuster的历史记录页面。
records
这个页面按照时间顺序列出了你的App启动每一个Activity的历史记录。每条记录最左边是启动所花费的时间。绿色表明所费时间符合秒开要求;红色表明时间太长。须要关注。右边是这条记录对应的Activity名称。点击某条记录就会进入详情页。

详情页
在详情页里你能够看到启动这个Activity的过程当中主线程处理过的消息。上方的复选框能够过滤执行时间比较短的消息,方便定位问题。

对于每条记录,首先显示的是这条消息开始被处理的时间戳。而后是cost字段,表示处理这条消息花了多长时间。正常状况下是字体是黑色的;若是处理时间过长,则显示为红色。代表这里多是咱们须要优化的地方。

接下来是target字段,对应的是这个消息是被哪一个Handler处理的。Android系统的Handler会显示为黑色;App本身的Handler会显示为红色,代表这个消息不该该在启动Activity的时候出现,这里也多是须要优化的地方。

例如上图中第一条记录,.MainActivity$StallHandler处理这个消息花费了142ms。这会使启动SubActivity的时间至少延长了142ms。而这个Handler是App本身的Handler。咱们须要调试代码使得在启动这个Activity的时候确保不会有来自这个Handler的消息,142ms的时间就会节省下来。

最后一个字段是message或者callback。对应的是Message中的what或callback。有了这些信息咱们就能很方便的定位主线程中影响秒开的消息,进而优化咱们的App。

StallBuster就给你们介绍到这里,但愿StallBuster能帮到你。若是你们有任何建议或者问题请给我留言。

总结

App秒开是是一项很是重要的性能指标。秒开的优化是个复杂的工做,有不少因素会影响App秒开。其中比较重要的一个因素是启动Activity的时候主线程的消息处理状况。在启动Activity过程当中须要避免消息处理时间过长,也要避免在此期间有App本身的消息须要处理。优化的关键点是要定位到主线程中的耗时操做,咱们能够经过打印分析主线程的消息处理log来定位,但这种方式并非很直观方便。这时可使用StallBuster帮助你快速定位秒开问题点,让秒开优化变的更加简单。

相关文章
相关标签/搜索