乐趣区

关于android:Android-App秒开的奥秘

什么是秒开

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

退出移动版