什么是秒开

Android App秒开,广义的讲是指你的App的Activity从启动到显示所破费的工夫在1秒以内,狭义的讲是指这个过程所破费的工夫越少越好。这个工夫越短,你的App给用户的感觉就是响应越快,应用越晦涩,用户体验更好。秒开是Android App的一个很重要的性能指标。须要咱们继续的给予关注和优化。

如何优化秒开

Google提供了很多性能优化的倡议和官网的工具,网上也有十分多的对于Android App性能优化的文章和工具,能够帮忙你解决大部分卡顿的问题。然而事实却可能是即便你付出了很多精力去做优化,你的App还是在启动新Activity的时候破费过多的工夫。特地是随着需要的一直增长,你的App会变得复杂而宏大,要做优化首先要定位须要优化的点,而这会变得愈发艰难。同时大型App在启动新Activity的工夫破费过多状况呈现的可能性反而会越来越大。

在泛滥的优化倡议中,有一条比拟根本的准则是尽量避免在主线程(或者说UI线程)中进行耗时操作。例如文件读写操作、网络申请、大量计算、循环等等。直观的了解是因为启动新Activity须要在主线程执行很多代码,例如onCreate()等生命周期的回调。如果此时有耗时操作的代码在主线程被执行,到新Activity展现进去所须要的工夫就会缩短。要优化秒开,首先要能监测主线程的运行状态,那么问题来了,主线程到底是怎么在运行呢?你的代码又是什么时候,如何在主线程被执行的呢?

深刻主线程

要理解主线程的工作过程,首先要理解Android的音讯机制。

音讯机制

先看一下现实生活中的一个例子,尽管当初都是挪动领取了,但置信大家都去银行取过钱。当你达到银行的时候,如果你是第一个,那祝贺你,你能够马上到柜员那里办理你的业务;如果你后面还有人,那就比拟惨了,你须要排队,得等到你后面的人都办完业务才会轮到你;更可怕的是如果你后面有几位须要办理的业务破费的工夫比拟长,那你须要等更长的工夫;前面来的人则会按程序排在你身后,和你一样不耐烦的推敲什么时候能力轮到本人。

形象一下,音讯机制其实和这个例子非常相似。每个人都看做是个音讯,什么时候到的银行是不确定的。柜员能够看做一个音讯处理器,他帮你办业务就相当于在解决你的音讯;而人们依照先后顺序排起来的队伍能够看做是个音讯队列。所以这个过程能够形象为有个音讯处理器,他有个音讯队列,随机来到的音讯依照肯定顺序排列在这个队列里,音讯处理器不停的从队列头部获取音讯而后解决之,周而复始的循环反复这个过程。如下图所示:

那么Android是怎么实现这个音讯机制的呢?

Android的音讯机制

音讯机制首先得有音讯,在Android中就是Message。怎么能确定一个音讯呢?音讯要有起源或者指标,也就是target;音讯要表明本人要做什么,也就是what或者callback;音讯要表明本人心愿在什么时候执行,也就是when。有了这几个因素,基本上这个音讯就是个齐备的音讯了,能够被退出到音讯队列中了。Android中的音讯队列是MessageQueue。音讯解决循环是Looper。Looper是个死循环,不停的从MessageQueue中获取音讯而后解决之,具体的执行是在Handler外面进行的。另外音讯退出音讯队列也须要Handler来操作。Message,MessageQueue,Looper,Handler组合在一起,就形成了整个Android的音讯机制。

Android的主线程就运行着这样一个音讯机制。

Android的主线程

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

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

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

主线程如何影响秒开

咱们理解了主线程的工作机制后,就要看看主线程中的音讯解决是如何影响Activity秒开的。
当咱们要启动一个新的Activity的时候,从调用startActivity开始到新Activity显示进去,Android零碎会发送一系列的音讯给主线程。这一系列的音讯解决所破费的总工夫会影响页面的秒开,如果执行工夫过长,用户就会有响应十分慢的感觉。此外,除了Android零碎会给主线程发消息,App本身也会给主线程发消息,如果在启动新Activity的过程中,这些App本人的音讯正好插入这一系列的Android零碎音讯中,那也会导致总的解决工夫缩短,造成不能秒开。

上图代表了启动新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:

每行 “>>>>> 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就会关上StallBuster的历史记录页面。

这个页面依照工夫程序列出了你的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帮忙你疾速定位秒开问题点,让秒开优化变的更加简略。

文章转自 https://www.jianshu.com/p/e1c... 如有侵权,请分割删除。

相干视频:

【2021最新版】Android studio装置教程+Android(安卓)零基础教程视频(适宜Android 0根底,Android初学入门)含音视频_哔哩哔哩_bilibili

Android 性能优化学习【二】:APP启动速度优化_哔哩哔哩_bilibili

Android 性能优化学习【三】:如何解决OOM问题_哔哩哔哩_bilibili