当前位置:首页 > 攻略

【applicationisinterrupted】Android开发月收入10k和月收入20k的差异——性能优化学习的Karton优化

前言:

APPCarton对用户体验影响很大,如何成为Android开发的性能优化高手,今天我们来看一下性能优化—— Carton优化。打开优化大师的道路。学习资料地址请发送“核心笔记”或“手册”直接收到!

卡顿现象:

如何定义Carton现象: APP的FPS平均值小于30,最小值小于24,表示应用程序发生Carton。

复制代码行很难再现,与发生场景密切相关。(所以我们要进行卡顿监控,收集现场信息。)需要CPU的知识。(阿尔伯特爱因斯坦)。

最新的主流机型使用多级高能效CPU体系结构(多核分层体系结构)(从CPU到GPU再到AI芯片NPU),移动CPU的整体性能得到了飞跃的提高,使您能够充分利用移动计算功能,降低高服务器成本。要评估CPU的性能,需要查看时钟速度、内核数、高速缓存等参数。这是每秒执行的浮点计算数和每秒执行的命令数。Carton的原因(包括代码、内存、绘图、IO、CPU等)反映在CPU时间中,CPU时间可以分为用户时间和系统时间。用户时间:执行基于用户的应用程序代码所用的时间。系统时间:执行内核状态系统调用所需的时间,包括I/|O、锁定、中断和其他系统调用时间。常用命令:ADB shell

//获取CPU核心数

cat/sys/devices/system/CPU/possible

//获取第一个CPU的最大频率

cat/sys/devices/system/CPU/CPU 0/CPU freq/CPU info _ max _ freq

//获取第二个CPU的最小频率

cat/sys/devices/system/CPU/CPU 1/CPU freq/CPU info _ min _ freq

//整个系统的CPU使用率

猫/proc/[PID]/状态

Top命令有助于确定CPU消耗较大的进程。

Vmstat命令可实时、动态地监控操作系统的虚拟内存和CPU活动。

Strace命令跟踪进程中的所有系统调用

可以通过Vmstat命令或/proc/[pid]/schedstat文件查看CPU上下文切换的次数

/proc/[pid]/stat //进程CPU使用率

/proc/[pid]/task/[tid]/stat //进程下每个线程的CPU使用率

/proc/[pid]/sched //与进程CPU时间表相关

/proc/loadavg //系统平均负载,uptime命令对应的文件

复制代码否存在优先级较低的线程,如优先级较高的线程null。例如,主要线程等待后台线程的锁定CPU的三个问题:CPU资源冗余使用:算法效率低下。不使用缓存计算时使用的默认类型不正确(例如,int足够,但long,计算压力增加4倍)。争用CPU资源:通过抢占主要线程的CPU资源,抢占音频视频的CPU资源。优化有两种方法。换句话说,尽量排除非核心业务的消费。优化自身性能消耗,并将CPU负载转换为GPU负载(例如,使用renderscript处理视频中的图像信息)。都是平等的,互相抢(三和尚不喝水)CPU资源利用率低的:磁盘和网络I/O、锁操作、睡眠等,为了锁的优化,往往会尽量减少锁的范围。卡顿故障诊断工具

Traceview和systrace都是调查我们比较熟悉的卡顿的工具,在实现中可以除以两个流派instrument:得到一段时间内所有函数的调用过程,在此期间可以分析函数调用过程,进一步分析要优化的点。Sample:选择性地采样或观察一些函数调用过程,在继续分析之前,可以推断过程的可疑之处。Traceview类型:instrument原则:使用Android Runtime函数调用的event事件,将执行函数所需的时间和调用关系写入trace文件。特征:可以确定整个过程中有哪些函数调用,但有时工具本身的性能开销太大,无法反映真实情况。Android 5.0之后添加了sta

rtMethodTracingSampling方法,可以使用基于样本的方式进行分析,以减少分析对运行时的性能影响。
  • 新增了 sample 类型后,就需要我们在开销和信息丰富度之间做好权衡。
  • 无论是哪种的 Traceview 对 release 包支持的都不太好,例如无法反混淆
  • Nanoscope
  • 类型:instrument
  • 原理:直接修改 Android 虚拟机源码,在ArtMethod执行入口和执行结束位置增加埋点代码,将所有的信息先写到内存,等到 trace 结束后才统一生成结果文件;
  • 特点:性能损耗较小,适合做启动耗时的自动化分析,但是 trace 结束生成结果文件这一步需要的时间比较长。另一方面它可以支持分析任意一个应用,可用于做竞品分析。但是它也有不少限制:
  • 需要自己刷 ROM,并且当前只支持 Nexus 6P,或者采用其提供的 x86 架构的模拟器;
  • 默认只支持主线程采集,其他线程需要[代码手动设置]
  • 考虑到内存大小的限制,每个线程的内存数组只能支持大约 20 秒左右的时间段。
  • 我们可以每天定期去跑自动化启动测试,查看是否存在新增的耗时点
  • systrace
  • 类型:sample
  • Android 4.1 新增的性能分析工具。我通常使用 systrace 跟踪系统的 I/O 操作、CPU 负载、Surface 渲染、GC 等事件。
  • 特点:只能监控特定系统调用的耗时情况,性能开销低,但不支持应用程序代码的耗时分析;但是系统预留了Trace.beginSection接口来监听应用程序的调用耗时,我们可以通过编译时给每个函数插桩的方式来实现在 systrace 基础上增加应用程序耗时的监控
  • Simpleperf
  • 类型:sample
  • 如果我们想分析 Native 函数的调用,上面的三个工具都不能满足这个需求,Android 5.0 新增了Simpleperf性能分析工具
  • 利用 CPU 的性能监控单元(PMU)提供的硬件 perf 事件,可以看到所有的 Native 代码的耗时,同时封装了 systrace 的监控功能
  • Android Studio 3.2 也在 Profiler 中直接支持 Simpleperf
    • 汇总一下
      • 如果需要分析 Native 代码的耗时,可以选择 Simpleperf;
      • 如果想分析系统调用,可以选择 systrace;
      • 如果想分析整个程序执行流程的耗时,可以选择 Traceview 或者插桩版本的 systrace。
    1. 可视化方法
    • Android Studio 3.2 的 Profiler 中直接集成了几种性能分析工具:
      • Sample Java Methods 的功能类似于 Traceview 的 sample 类型
      • Trace Java Methods 的功能类似于 Traceview 的 instrument 类型
      • Trace System Calls 的功能类似于 systrace
      • SampleNative (API Level 26+) 的功能类似于 Simpleperf
    • 虽然不够全面和强大,但大大降低了开发者的使用门槛
    • 分析结果的展示方式:这些分析工具都支持了 Call Chart 和 Flame Chart 两种展示方式
      • Call Chart 是 Traceview 和 systrace 默认使用的展示方式,按照应用程序的函数执行顺序来展示,适合用于分析整个流程的调用
      • Flame Chart 也就是大名鼎鼎的火焰图,以一个全局的视野来看待一段时间的调用分布,时间和空间两个维度上的信息融合在一张图上
    1. StrictMode
    2. 是Android 2.3引入的一个工具类,它被称为严苛模式,是Android提供的一种运行时检测机制,可以用来帮助开发人员用来检测代码中一些不规范的问题。
    3. 主要用来检测两大问题:
      1. 线程策略: 检测内容是一些自定义的耗时调用、磁盘读取操作以及网络请求等;
      2. 虚拟机策略: 检测内容包括Activity泄漏,SqLite对象泄漏,检测实例数量;
    4. 使用:在Application的onCreate方法中对StrictMode进行统一配置,在日志输出栏中注意使用“StrictMode”关键字过滤出对应的log即可
    5. private void initStrictMode() {
      // 1、设置Debug标志位,仅仅在线下环境才使用StrictMode
      if ) {
      // 2、设置线程策略
      S(new S()
      .detectCustomSlowCalls() //API等级11,使用S
      .detectDiskReads()
      .detectDiskWrites()
      .detectNetwork() // or .detectAll() for all detectable problems
      .penaltyLog() //在Logcat 中打印违规异常信息
      //.penaltyDialog() //也可以直接跳出警报dialog
      //.penaltyDeath() //或者直接崩溃
      .build());
      // 3、设置虚拟机策略
      S(new S()
      .detectLeakedSqlLiteObjects()
      // 给Person对象的实例数量限制为1
      .setClassInstanceLimi, 1)
      .detectLeakedClosableObjects() //API等级11
      .penaltyLog()
      .build());
      }
      }
      复制代码

    卡顿监控

    1. 消息队列
    2. 方式1:通过替换 Looper 的 Printer 实现;
      1. 首先,我们需要使用Loo().setMessageLogging()去设置我们自己的Printer 实现类去打印输出logging。这样,在每个message执行的之前和之后都会调用我们设置的这个Printer实现类。
      2. 如果我们匹配到">>>>> Dispatching to "之后,我们就可以执行一行代码:也就是在指定的时间阈值之后, 我们在子线程去执行一个任务,这个任务就是去获取当前主线程的堆栈信息以及当前的一些场景信息,比如:内存大小、电脑、网络状态等。
      3. 如果在指定的阈值之内匹配到了"<<<<< Finished to ",那么说明message就被执行完成了, 则表明此时没有产生我们认为的卡顿效果,那我们就可以将这个子线程任务取消掉
    3. 方式2:通过一个监控线程,每隔 1 秒向主线程消息队列的头部插入一条空消息; 如果我们需要监控 3 秒卡顿,那在第 4 次轮询中头部消息依然没有被消费的话,就可以确定主线程出现了一次 3 秒以上的卡顿;
    4. 插装
    5. 基于消息队列的卡顿监控并不准确,正在运行的函数有可能并不是真正耗时的函数;
    6. 假设一个消息循环里面顺序执行了 A、B、C 三个函数,当整个消息执行超过 3 秒时,因为函数 A 和 B 已经执行完毕,
      我们只能得到的正在执行的函数 C 的堆栈,事实上它可能并不耗时,不过对于线上大数据来说,因为函数 A 和 B 相对
      比较耗时,所以抓取到它们的概率会更大一些,通过后台聚合后捕获到函数 A 和 B 的卡顿日志会更多一些;
      如果跟 Traceview 一样,可以拿到整个卡顿过程所有运行函数的耗时,就可以明确知道其实函数 A 和 B 才是造成卡顿的主要原因;
      那能否利用 Android Runtime 函数调用的回调事件,做一个自定义的 Traceview++ 呢?
      复制代码
    7. 需要使用 Inline Hook 技术。我们可以实现类似 Nanoscope 先写内存的方案;需要注意两点:
    8. 避免方法数暴增
    9. 过滤简单的函数
    10. 实现参考:微信的Matrix
    11. 虽然插桩方案对性能的影响总体还可以接受,但只会在灰度包使用;
    12. 短板:只能监控应用内自身的函数耗时,无法监控系统的函数调用,整个堆栈看起来好像“缺失了”一部分
    13. Profilo
    14. 参考了JVM的 AsyncGetCallTrace 思路,然后适配 Android Runtime 的实现
    15. Facebook 开源库,它收集了各大方案的优点
      1. 集成 atrace 功能
      2. ftrace 所有性能埋点数据都会通过 trace_marker 文件写入内核缓冲区,Profilo 通过
        PLT Hook 拦截了写入操作,选择部分关心的事件做分析。这样所有 systrace 的探针我们
        都可以拿到,例如四大组件生命周期、锁等待时间、类校验、GC 时间等。
        复制代码
      3. 快速获取 Java 堆栈
      4. 获取堆栈的代价是巨大的,它要暂停主线程的运行,Profilo 的实现非常精妙,它实现类似
      5. Native 崩溃捕捉的方式快速获取 Java 堆栈,通过间隔发送 SIGPROF 信号;
    16. AndroidPerformanceMonitor
    17. 一个非侵入式的性能监控组件,可以通过通知的形式弹出卡顿信息。
    18. 优势:非侵入式,方便精准,能够定位到代码的某一行代码。
    19. 使用:
    20. //1. build.gradle下配置它的依赖
      api 'com.gi;
      // 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用
      debugApi 'com.gi;
      //2. Application的onCreate方法中开启卡顿监控
      BlockCanary.install(this, new AppBlockCanaryContext()).start();
      //3.继承BlockCanaryContext类去实现自己的监控配置上下文类
      /**
      * @Author: LiuJinYang
      * @CreateDate: 2020/12/9
      */
      public class AppBlockCanaryContext extends BlockCanaryContext {
      // 实现各种上下文,包括应用标识符,用户uid,网络类型,卡顿判断阙值,Log保存位置等等

      /**
      * 提供应用的标识符
      *
      * @return 标识符能够在安装的时候被指定,建议为 version + flavor.
      */
      @Override
      public String provideQualifier() {
      return "unknown";
      }

      /**
      * 提供用户uid,以便在上报时能够将对应的
      * 用户信息上报至服务器
      *
      * @return user id
      */
      @Override
      public String provideUid() {
      return "uid";
      }

      /**
      * 提供当前的网络类型
      *
      * @return {@link String} like 2G, 3G, 4G, wifi, etc.
      */
      @Override
      public String provideNetworkType() {
      return "unknown";
      }

      /**
      * 配置监控的时间区间,超过这个时间区间 ,BlockCanary将会停止, use
      * with {@code BlockCanary}'s isMonitorDurationEnd
      *
      * @return monitor last duration (in hour)
      */
      @Override
      public int provideMonitorDuration() {
      return -1;
      }

      /**
      * 指定判定为卡顿的阈值threshold (in millis),
      * 你可以根据不同设备的性能去指定不同的阈值
      *
      * @return threshold in mills
      */
      @Override
      public int provideBlockThreshold() {
      return 1000;
      }

      /**
      * 设置线程堆栈dump的间隔, 当阻塞发生的时候使用, BlockCanary 将会根据
      * 当前的循环周期在主线程去dump堆栈信息
      * <p>
      * 由于依赖于Looper的实现机制, 真实的dump周期
      * 将会比设定的dump间隔要长(尤其是当CPU很繁忙的时候).
      * </p>
      *
      * @return dump interval (in millis)
      */
      @Override
      public int provideDumpInterval() {
      return provideBlockThreshold();
      }

      /**
      * 保存log的路径, 比如 "/blockcanary/", 如果权限允许的话,
      * 会保存在本地sd卡中
      *
      * @return path of log files
      */
      @Override
      public String providePath() {
      return "/blockcanary/";
      }

      /**
      * 是否需要通知去通知用户发生阻塞
      *
      * @return true if need, else if not need.
      */
      @Override
      public boolean displayNotification() {
      return true;
      }

      /**
      * 用于将多个文件压缩为一个.zip文件
      *
      * @param src files before compress
      * @param dest files compressed
      * @return true if compression is successful
      */
      @Override
      public boolean zip(File[] src, File dest) {
      return false;
      }

      /**
      * 用于将已经被压缩好的.zip log文件上传至
      * APM后台
      *
      * @param zippedFile zipped file
      */
      @Override
      public void upload(File zippedFile) {
      throw new UnsupportedOperationException();
      }

      /**
      * 用于设定包名, 默认使用进程名,
      *
      * @return null if simply concern only package with process name.
      */
      @Override
      public List<String> concernPackages() {
      return null;
      }

      /**
      * 使用 @{code concernPackages}方法指定过滤的堆栈信息
      *
      * @return true if filter, false it not.
      */
      @Override
      public boolean filterNonConcernStack() {
      return false;
      }

      /**
      * 指定一个白名单, 在白名单的条目将不会出现在展示阻塞信息的UI中
      *
      * @return return null if you don't need white-list filter.
      */
      @Override
      public List<String> provideWhiteList() {
      LinkedList<String> whiteList = new LinkedList<>();
      w("org.chromium");
      return whiteList;
      }

      /**
      * 使用白名单的时候,是否去删除堆栈在白名单中的文件
      *
      * @return true if delete, false it not.
      */
      @Override
      public boolean deleteFilesInWhiteList() {
      return true;
      }

      /**
      * 阻塞拦截器, 我们可以指定发生阻塞时应该做的工作
      */
      @Override
      public void onBlock(Context context, BlockInfo blockInfo) {

      }
      }
      复制代码

    其他监控

    • 除了主线程的耗时过长之外,我们还有哪些卡顿问题需要关注呢?
    • Android Vitals 是 Google Play 官方的性能监控服务,涉及卡顿相关的监控有 ANR、启动、帧率三个
    1. 帧率
    2. 业界都使用 Choreographer 来监控应用的帧率;
    3. 需要排除掉页面没有操作的情况,应该只在界面存在绘制的时候才做统计;
    4. // 监听界面是否存在绘制行为
      getWindow().getDecorView().getViewTreeObserver().addOnDrawListener
      复制代码
    5. 平均帧率: 衡量界面流畅度;
    6. 冻帧率:计算发生冻帧时间在所有时间的占比;
    7. 冻帧:Android Vitals 将连续丢帧超过 700 毫秒定义为冻帧,也就是连续丢帧 42 帧以上;
    8. 出现丢帧的时候,我们可以获取当前的页面信息、View 信息和操作路径上报后台,降低二次排查的难度
    9. 生命周期监控
    10. Activity、Service、Receiver 组件生命周期的耗时和调用次数也是我们重点关注的性能问题;
    11. 如:Activity 的 onCreate() 不应该超过 1 秒,不然会影响用户看到页面的时间
    12. 对于组件生命周期应采用更严格地监控,全量上报,在后台查看各个组件各个生命周期的启动时间和启动次数;
    13. 除了四大组件的生命周期,我们还需要监控各个进程生命周期的启动次数和耗时;
    14. 生命周期监控推荐使用编译时插桩的方式,如 Aspect、ASM 和 ReDex 三种插桩技术;
    15. 线程监控
    16. Java 线程管理是很多应用非常头痛的事情,应用启动过程就已经创建了几十上百个线程。而且大部分的线程都没有经过线程池管理,都在自由自在地狂奔着;
    17. 另外一方面某些线程优先级或者活跃度比较高,占用了过多的 CPU。这会降低主线程 UI 响应能力,我们需要特别针对这些线程做重点的优化。
    18. 对于线程需要监控两点
      1. 线程数量,以及创建线程的方式:可以通过 got hook 线程的 nativeCreate() 函数,主要用于进行线程收敛,也就是减少线程数量。
      2. 监控线程的用户时间 utime、系统时间 stime 和优先级
    • 导致卡顿的原因会有很多,比如函数非常耗时、I/O 非常慢、线程间的竞争或者锁等。其实很多时候卡顿问题并不难解决,相较解决来说,更困难的是如何快速发现这些卡顿点,以及通过更多的辅助信息找到真正的卡顿原因。

    卡顿现场

    • 以A函数耗时为例进行分析
    1. 方案一: java实现:
    2. 通过源码可以发现,AssetManager 内部有大量的 synchronized 锁;
    3. 步骤1: 获得 Java 线程状态:通过T获取 , 证实当时主线程是 BLOCKED 状态;
    4. //WAITING、TIME_WAITING 和 BLOCKED 都是需要特别注意的状态;
      //BLOCKED 是指线程正在等待获取锁,对应的是下面代码中的情况一;
      //WAITING 是指线程正在等待其他线程的“唤醒动作”,对应的是代码中的情况二;
      synchronized (object) { // 情况一:在这里卡住 --> BLOCKED
      doSomething();
      object.wait(); // 情况二:在这里卡住 --> WAITING
      }
      //不过当一个线程进入 WAITING 状态时,它不仅会释放 CPU 资源,还会将持有的 object 锁也同时释放。
      复制代码
    5. 步骤2:获得所有线程堆栈:通过T()获得
    6. 注意:在 Android 7.0,getAllStackTraces 是不会返回主线程的堆栈的
    7. 通过分析收集上来的卡顿日志,发现跟 AssetManager 相关的线程是BackgroundHandler
    8. "BackgroundHandler" RUNNABLE
      at android.con
      at com.
      //通过查看A的确发现是使用了同一个 synchronized 锁,而 list 函数需要遍历整个目录,耗时会比较久
      public String[] list(String path) throws IOException {
      synchronized (this) {
      ensureValidLocked();
      return nativeList(mObject, path);
      }
      }
      //另外一方面,“BackgroundHandler”线程属于低优先级后台线程,这也是我们前面文章提到的不良现象,也就是主线程等待低优先级的后台线程
      复制代码
    9. 方案2:ANR日志实现(SIGQUIT信号)
    10. 上面java实现方案还不错,不过貌似ANR 日志的信息更加丰富,如果直接用 ANR 日志呢?
    11. // 线程名称; 优先级; 线程id; 线程状态
      "main" prio=5 tid=1 Suspended
      // 线程组; 线程suspend计数; 线程debug suspend计数;
      | group="main" sCount=1 dsCount=0 obj=0x74746000 self=0xf4827400
      // 线程native id; 进程优先级; 调度者优先级;
      | sysTid=28661 nice=-4 cgrp=default sched=0/0 handle=0xf72cbbec
      // native线程状态; 调度者状态; 用户时间utime; 系统时间stime; 调度的CPU
      | state=D schedstat=( 3137222937 94427228 5819 ) utm=218 stm=95 core=2 HZ=100
      // stack相关信息
      | stack=0xff717000-0xff719000 stackSize=8MB

      复制代码
    12. Native 线程状态
    13. 上面的 ANR 日志中“main”线程的状态是 Suspended,Java 线程中的 6 种状态中并不存在 Suspended 状态啊?
      事实上,Suspended 代表的是 Native 线程状态。怎么理解呢?在 Android 里面 Java 线程的运行都委托于一个
      Linux 标准线程 pthread 来运行,而 Android 里运行的线程可以分成两种,一种是 Attach 到虚拟机的,一种是
      没有 Attach 到虚拟机的,在虚拟机管理的线程都是托管的线程,所以本质上 Java 线程的状态其实是 Native 线程
      的一种映射。不同的 Android 版本 Native 线程的状态不太一样,例如 Android 9.0 就定义了 27 种线程状态,
      它能更加明确地区分线程当前所处的情况。
      复制代码
    14. 如何拿到卡顿时的 ANR 日志?
    15. 第一步:当监控到主线程卡顿时,主动向系统发送 SIGQUIT 信号。
    16. 第二步:等待 /data/anr 文件生成。
    17. 第三步:文件生成以后进行上报
    18. 通过 ANR 日志,我们可以直接看到主线程的锁是由“BackgroundHandler”线程持有。相比之下通过 getAllStackTraces 方法,我们只能通过一个一个线程进行猜测。
    19. // 堆栈相关信息
      at android.con(AssetManager.java:311)
      - waiting to lock <0x41ddc798> ) held by tid=66 (BackgroundHandler)
      at android.con(AssetManager.java:289)
      复制代码
    20. 存在的问题:
    21. 可行性:很多高版本系统已经没有权限读取 /data/anr 文件,需要刷ROM;
    22. 性能:获取所有线程堆栈以及各种信息非常耗时,对于卡顿场景不一定合适,它可能会进一步加剧用户的卡顿;
    23. 方案3:Hook实现
    24. 通过 Hook 方式我们实现了一套“无损”获取所有 Java 线程堆栈与详细信息的方法:
      1. 通过 fork 子进程方式实现,这样即使子进程崩溃了也不会影响我们主进程的运行,而且获取所有线程堆栈这个过程可以做到完全不卡我们主进程;
      2. 通过libart.so、dlsym调用ThreadList::ForEach方法,拿到所有的 Native 线程对象。
      3. 遍历线程对象列表,调用Thread::DumpState方法;
    25. 线上ANR监控方式:
    26. ANR的几种常见的类型:
      1. KeyDispatchTimeout:按键事件在5s的时间内没有处理完成;
      2. BroadcastTimeout:广播接收器在前台10s,后台60s的时间内没有响应完成;
      3. ServiceTimeout:服务在前台20s,后台200s的时间内没有处理完成;
    27. 之前的崩溃优化中说了“怎么去发现应用中的 ANR 异常”,那么,有没有更好的实现方式呢?
    28. ANR-WatchDog:一种非侵入式的ANR监控组件,可以用于线上ANR的监控
    29. //1. build.gradle下配置它的依赖
      implementation 'com.gi;
      //2. Application的onCreate方法中初始化ANR-WatchDog
      new ANRWatchDog().start();
      //3.源码:ANRWatchDog实际上是继承了Thread类,也就是它是一个线程,对于线程来说,最重要的就是其run方法
      private static final int DEFAULT_ANR_TIMEOUT = 5000;
      private volatile long _tick = 0;
      private volatile boolean _reported = false;

      private final Runnable _ticker = new Runnable() {
      @Override public void run() {
      _tick = 0;
      _reported = false;
      }
      };

      @Override
      public void run() {
      // 1、首先,将线程命名为|ANR-WatchDog|。
      setName("|ANR-WatchDog|");
      // 2、接着,声明了一个默认的超时间隔时间,默认的值为5000ms。
      long interval = _timeoutInterval;
      // 3、然后,在while循环中通过_uiHandler去post一个_ticker Runnable。
      while (!isInterrupted()) {
      // 3.1 这里的_tick默认是0,所以needPost即为true。
      boolean needPost = _tick == 0;
      // 这里的_tick加上了默认的5000ms
      _tick += interval;
      if (needPost) {
      _uiHandler.post(_ticker);
      }
      // 接下来,线程会sleep一段时间,默认值为5000ms。
      try {
      T(interval);
      } catch (InterruptedException e) {
      _in(e);
      return ;
      }
      // 4、如果主线程没有处理Runnable,即_tick的值没有被赋值为0,则说明发生了ANR,第二个_reported标志位是为了避免重复报道已经处理过的ANR。
      if (_tick != 0 && !_reported) {
      //noinspection ConstantConditions
      if (!_ignoreDebugger && () || Debug.waitingForDebugger())) {
      Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
      _reported = true;
      continue ;
      }
      interval = _anrIn(_tick);
      if (interval > 0) {
      continue;
      }
      final ANRError error;
      if (_namePrefix != null) {
      error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
      } else {
      // 5、如果没有主动给ANR_Watchdog设置线程名,则会默认会使用ANRError的NewMainOnly方法去处理ANR。
      error = ANRError.NewMainOnly(_tick);
      }
      // 6、最后会通过ANRListener调用它的onAppNotResponding方法,其默认的处理会直接抛出当前的ANRError,导致程序崩溃。 _anrLi(error);
      interval = _timeoutInterval;
      _reported = true;
      }
      }
      }
      //但是在Java层去获取所有线程堆栈以及各种信息非常耗时,对于卡顿场景不一定合适,它可能会进一步加剧用户的卡顿。
      如果是对性能要求比较高的应用,可以通过Hook Native层的方式去获得所有线程的堆栈信息,参考上面“方案3:Hook实现”
      复制代码
    • 现场信息:
      • 能不能进一步让卡顿的“现场信息”的比系统 ANR 日志更加丰富?我们可以进一步增加这些信息:
        • CPU 使用率和调度信息:参考下面的课后作业1;
        • 内存相关信息:可以添加系统总内存、可用内存以及应用各个进程的内存等信息。如果开启了 Debug.startAllocCounting 或者 atrace,还可以增加 GC 相关的信息。
        • I/O 和网络相关: 还可以把卡顿期间所有的 I/O 和网络操作的详细信息也一并收集
    • Android 8.0 后,Android 虚拟机终于支持了 JVM 的JVMTI机制。Profiler 中内存采集等很多模块也切换到这个机制中实现,

    卡顿单点问题检测方案

    • 常见的单点问题有主线程IPC(进程间通信)、DB操作等等
    • IPC单点问题检测方案:
    • 在IPC的前后加上埋点。但是,这种方式不够优雅
    • 线下可以通过adb命令监测
    • // 1、对IPC操作开始监控
      adb shell am trace-ipc start
      // 2、结束IPC操作的监控,同时,将监控到的信息存放到指定的文件
      adb shell am trace-ipc stop -dump-file /data/local/tm
      // 3、将监控到的ipc-trace导出到电脑查看
      adb pull /data/local/tm
      复制代码
    • ARTHook
      • AspectJ只能针对于那些非系统方法,也就是我们App自己的源码,或者是我们所引用到的一些jar、aar包;
      • ARTHook可以用来Hook系统的一些方法,因为对于系统代码来说,我们无法对它进行更改,但是我们可以Hook住它的一个方法,在它的方法体里面去加上自己的一些代码;
    • //通过PackageManager去拿到我们应用的一些信息,或者去拿到设备的DeviceId这样的信息以及AMS相关的信息等,最终会调用到android.os.BinderProxy
      //在项目中的Application的onCreate方法中使用ARTHook对android.os.BinderProxy类的transact方法进行Hook
      try {
      Dex("android.os.BinderProxy"), "transact",
      int.class, Parcel.class, Parcel.class, int.class, new XC_MethodHook() {
      @Override
      protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
      LogHel( "BinderProxy beforeHookedMethod " + ().getSimpleName()
      + "\n" + Log.getStackTraceString(new Throwable()));
      (param);
      }
      });
      } catch (ClassNotFoundException e) {
      e.printStackTrace();
      }
      复制代码
      • 除了IPC调用的问题之外,还有IO、DB、View绘制等一系列单点问题需要去建立与之对应的检测方案
      • 对于卡顿问题检测方案的建设,主要是利用ARTHook去完善线下的检测工具,尽可能地去Hook相对应的操作,以暴露、分析问题。

    使用Lancet统计界面耗时

    • Lancet 是一个轻量级Android AOP框架,编译速度快, 并且支持增量编译.
    • 使用Demo如下
    //1. 在根目录的 build.gradle 添加: dependencies { classpath 'me.ele:lancet-plugin:1.0.6' } //2. 在 app 目录的'build.gradle' 添加 apply plugin: 'me.ele.lancet' dependencies { provided 'me.ele:lancet-base:1.0.6' } //3. 基础API使用 /** * @Author: LiuJinYang * @CreateDate: 2020/12/10 */ public class LancetUtil { //@Proxy 指定了将要被织入代码目标方法i, 织入方式为Proxy(将使用新的方法替换代码里存在的原有的目标方法) @Proxy("i") //TargetClass指定了将要被织入代码目标类 android.u @TargetClass("android.u") public static int anyName(String tag, String msg){ msg = "LJY_LOG: "+msg ; () 代表了 Log.i() 这个目标方法 return (int) Origin.call(); } } //4. 统计界面耗时 /** * @Author: LiuJinYang * @CreateDate: 2020/12/10 */ public class LancetUtil { public static ActivityRecord sActivityRecord; static { sActivityRecord = new ActivityRecord(); } @Insert(value = "onCreate",mayCreateSuper = true) @TargetClass(value = "android.;,scope = Sco) protected void onCreate(Bundle savedInstanceState) { = Sy(); // 调用当前Hook类方法中原先的逻辑 Origin.callVoid(); } @Insert(value = "onWindowFocusChanged",mayCreateSuper = true) @TargetClass(value = "android.;,scope = Sco) public void onWindowFocusChanged(boolean hasFocus) { = Sy(); LjyLogU(getClass().getCanonicalName() + " onWindowFocusChanged cost "+( - )); Origin.callVoid(); } public static class ActivityRecord { /** * 避免没有仅执行onResume就去统计界面打开速度的情况,如息屏、亮屏等等 */ public boolean isNewCreate; public long mOnCreateTime; public long mOnWindowsFocusChangedTime; } } 复制代码

    卡顿分析

    • 在客户端捕获卡顿之后,最后数据需要上传到后台统一分析
    • 卡顿率
      • 评估卡顿的影响面:UV 卡顿率 = 发生过卡顿 UV / 开启卡顿采集 UV,一个用户如果命中采集,那么在一天内都会持续的采集数据
      • 评估卡顿的严重度:PV 卡顿率 = 发生过卡顿 PV / 启动采集 PV,对于命中采集 PV 卡顿率的用户,每次启动都需要上报作为分母

    结语:

    卡顿优化是Android开发高手之路,这篇文章可能有些地方讲的不够详细的,或关于更多的性能优化(启动优化、崩溃优化、卡顿优化、弱网优化、内存优化)等一系列优化。资料获取:请私信我发送暗号“核心笔记”或“手册”直接领取哟!

    希望共同进步,能帮助到各位Android的小伙伴。

    1.《【applicationisinterrupted】Android开发月收入10k和月收入20k的差异——性能优化学习的Karton优化》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

    2.《【applicationisinterrupted】Android开发月收入10k和月收入20k的差异——性能优化学习的Karton优化》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

    3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/gl/2516146.html

    上一篇

    【GRF代表正式辞职】谁说男人不会玩“宫心计”?英雄联盟GRF真的是罚1亿的事吗?

    下一篇

    【怪物猎人ol大剑】《奇怪的狩猎冰原》PC全套武器组装系列:大刀骑士

    【applicationisinterrupted】《Alibaba微服务组件Nacos配置中心用户指南》

    • 【applicationisinterrupted】《Alibaba微服务组件Nacos配置中心用户指南》
    • 【applicationisinterrupted】《Alibaba微服务组件Nacos配置中心用户指南》
    • 【applicationisinterrupted】《Alibaba微服务组件Nacos配置中心用户指南》
    【applicationisinterrupted】Mybatis框架深入分析

    【applicationisinterrupted】Mybatis框架深入分析

    applicationisinterrupted相关介绍,Mybatis框架深入分析 学习Spring后,您学习了如何使用Bean在IoC容器中管理类。换句话说,现在可以以更方便的方式使用Mybatis框架。可以将SqlSessionFactory、Mapp...

    【applicationisinterrupted】不要小看“弹簧过滤器”。这些知识点必须掌握。

    • 【applicationisinterrupted】不要小看“弹簧过滤器”。这些知识点必须掌握。
    • 【applicationisinterrupted】不要小看“弹簧过滤器”。这些知识点必须掌握。
    • 【applicationisinterrupted】不要小看“弹簧过滤器”。这些知识点必须掌握。

    【applicationisinterrupted】G1 GC官方文件|最佳实践(翻译)

    • 【applicationisinterrupted】G1 GC官方文件|最佳实践(翻译)
    • 【applicationisinterrupted】G1 GC官方文件|最佳实践(翻译)
    • 【applicationisinterrupted】G1 GC官方文件|最佳实践(翻译)

    【applicationisinterrupted】异常日志,不能解决问题吗?

    • 【applicationisinterrupted】异常日志,不能解决问题吗?
    • 【applicationisinterrupted】异常日志,不能解决问题吗?
    • 【applicationisinterrupted】异常日志,不能解决问题吗?
    【applicationisinterrupted】高考题目作文一般使用词汇记忆和范文4。

    【applicationisinterrupted】高考题目作文一般使用词汇记忆和范文4。

    applicationisinterrupted相关介绍,31.主题:人际关系 常用词汇: harmonious和谐的friendly友好的civilized文明的honest真诚的credible诚实的be public-spirited有公德心的be i...

    【applicationisinterrupted】服务首次参与

    • 【applicationisinterrupted】服务首次参与
    • 【applicationisinterrupted】服务首次参与
    • 【applicationisinterrupted】服务首次参与