什么是秒开
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 非常简单,只须要两步就能够了
- 增加对 StallBuster 的依赖
dependencies {compile 'com.github.zhangjianli:stallbuster: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