前言

卡顿意味着咱们的App发生了掉帧,被运用者所感知。 而导致App卡顿的原因很多:UI制作慢、内存运用不当(内存颤动)等等情况都会导致程序呈现卡顿,而这些卡顿又分为:可重现与不行重现。

可重现的卡顿

有一部分的卡顿是可本地复现的,关于这种简单重现的场景,一般咱们在开发及体会测验阶段简单注意得到,而定位卡顿的根源,咱们常用的办法是经过TraceView、Systrace东西来抓取卡顿过程中函数的履行情况(仓库,耗时,调用次数等)。 经过 TraceView 的可视化界面,咱们能够具体知道某个过程中的调用栈信息及各个函数的履行次数与耗时,能比较直观的找到严重耗时的函数,协助咱们快速处理卡顿问题。

目前Traceview 已弃用。假如运用 Android Studio 3.2 或更高版别,则应改为运用CPU Profiler

不行重现的卡顿

但往往大部分卡顿是很难及时发现的,不行重现的卡顿,常常呈现在线上用户的真实运用过程中,这种卡顿往往跟机器功能,手机环境,乃至是操作偏好等要素息息相关。一般也是从用户反馈中得到,通常表述为“你们APP好卡”,咱们很难在这种描述中,直接洞察到卡顿的根源,乃至有些连卡顿的场景都不知道,很难精确重现,所以这种卡顿简单让人摸不着头脑。 当然作为开发者,我更希望用户反馈的是,“某某函数耗时666ms,请处理它。” 那么面临这种卡顿,咱们怎样办呢?

处理计划

形成卡顿的直接原因通常是,主线程履行深重的UI制作、大量的核算或IO等耗时操作。

业界有几种常见处理计划,都能够从必定程度上,协助开发者快速定位到卡顿的仓库,如 BlockCanary、ArgusAPM、LogMonitor 。这些计划的首要思想是,监控主线程履行耗时,当超过阈值时,dump出当时主线程的履行仓库,经过仓库剖析找到卡顿原因。

从监控主线程的实现原理上,首要分为两种:

1、依靠主线程 Looper,监控每次 dispatchMessage 的履行耗时。(BlockCanary)

2、依靠 Choreographer 模块,监控相邻两次 Vsync 事情告诉的时间差。(ArgusAPM、LogMonitor)

第一种计划,看下 Looper#loop 代码片段:

public static void loop() {
   ...
  for (;;) {
     ...
    // This must be in a local variable, in case a UI event sets the logger
    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);
     }
     ...
   }
}

主线程一切履行的任务都在 dispatchMessage 办法中派发履行完成,咱们经过 setMessageLogging 的方式给主线程的 Looper 设置一个 Printer ,因为dispatchMessage 履行前后都会打印对应信息,在履行前运用别的一条线程,经过Thread#getStackTrace 接口,以轮询的方式获取主线程履行仓库信息并记载起来,一起计算每次 dispatchMessage 办法履行耗时,当超出阈值时,将该次获取的仓库进行剖析上报,从而来捕捉卡顿信息,不然丢弃此次记载的仓库信息。

第二种计划,运用体系 Choreographer 模块,向该模块注册一个 FrameCallback 监听目标,一起经过别的一条线程循环记载主线程仓库信息,并在每次 Vsync 事情 doFrame 告诉回来时,循环注册该监听目标,间接计算两次 Vsync 事情的时间间隔,当超出阈值时,取出记载的仓库进行剖析上报。

简单代码实现如下:

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
  @Override  
  public void doFrame(long frameTimeNanos) {
    if(frameTimeNanos - mLastFrameNanos > 100) {
       ...
     }
    mLastFrameNanos = frameTimeNanos;
    Choreographer.getInstance().postFrameCallback(this);
   }
});

这两种计划,能够较便利的捕捉到卡顿的仓库,但其最大的不足在于,无法获取到各个函数的履行耗时,关于略微复杂一点的仓库,很难找出或许耗时的函数,也就很难找到卡顿的原因。别的,经过其他线程循环获取主线程的仓库,假如略微处理不及时,很简单导致获取的仓库有所偏移,不够精确,加上没有耗时信息,卡顿也就欠好定位。

所以怎样更精确地捕捉卡顿仓库,又能核算出各个函数履行耗时的计划。 而要核算函数的履行耗时,最关键的点在于如何对履行过程中的函数进行打点监控。

1、字节码插桩,修正字节码,在编译期修正一切 class 文件中的函数字节码,对一切函数前后进行打点插桩。

2、运用JVMTI监听函数进入与退出,JVMTI全称是Java Virtual Machine Tool Interface,Java虚拟机东西接口,在Android 8.0及以上可用,能够让咱们监控与控制虚拟机的某种行为。

还有一种方式:zhuanli.tianyancha.com/502d748aa97…

在应用启动时,默认翻开 Trace 功用(Debug.startMethodTracing),应用内一切函数在履行前后将会经过alvik 上 dvmMethodTraceAdd 函数 或 art 上 Trace::LogMethodTraceEvent 函数, 经过hack手法代理该函数,在每个履行办法前后进行打点记载。

今日分享到此结束,对你有协助的话,点个赞再走呗,下期更精彩~

重视公众号:Android老皮
解锁 《Android十大板块文档》 ,让学习更靠近未来实战。已形成PDF版

内容如下

1.Android车载应用开发体系学习指南(附项目实战)
2.Android Framework学习指南,助力成为体系级开发高手
3.2023最新Android中高档面试题汇总+解析,离别零offer
4.企业级Android音视频开发学习道路+项目实战(附源码)
5.Android Jetpack从入门到通晓,构建高质量UI界面
6.Flutter技能解析与实战,跨渠道首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高档Android插件化与组件化(含实战教程和源码)
9.Android 功能优化实战+360全方面功能调优
10.Android零基础入门到通晓,高手进阶之路

敲代码不易,重视一下吧。ღ( ・ᴗ・` )