作者:海象

前言

最近在处理项目中的拍摄视频后上传界面卡顿的问题,找到 BlockCanary 这个工具来定位,由于不支持高版本 Android,当时在定位卡顿时先将项目的 targetSdk 版本降下来,当然这不是个长久的办法,打算花一点时间适配下高版本,先过一遍源码流程

网上很多博客只提到适配分区存储和通知栏,好像忽略了一个细节,CPU 的采样"proc" 在高版本 Android 被禁用,原因是系统防止旁路攻击,只允许系统应用访问

初始化

BlockCanary 跟随 App 启动,内部的 BlockCanaryInternal 有添加拦截器的操作,这里应该是控制的核心逻辑

install () 大概做了这些:

  1. BlockCanaryContext 实现了 BlockInterceptor 接口 进行基础设置,比如文件夹名,判断时间等,用于给开发者自定义的
  2. 启动组件,显示图标到桌面

BlockCanary 构造方法中做了:

  1. 创建 BlockCanaryInternal
  2. 添加拦截器到 BlockCanaryInternal

这里主要是为子线程监测做初始化

启动

    public void start() {// 往主线程的 looper 里设置 printerLooper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);}

这里就是 BlockCanary 检测的位置,原理是 Looper.loop() 中会在 Looper.dispatchMessage() 执行前后做打印,刚好可以利用这个做执行时长的处理,通过判断是否超过时间,来判断是否发生了卡顿

检测执行时长

检测时长的逻辑位于 LooperMonitor,它实现了 Printer 接口

    @Overridepublic void println(String x) {// 执行前if (!mPrintingStarted) {// 获取当前时间mStartTimestamp = System.currentTimeMillis();mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();mPrintingStarted = true;startDump();} else {// 执行后// 获取当前时间final long endTime = System.currentTimeMillis();mPrintingStarted = false;// 计算是否卡顿,如果发生了则通知if (isBlock(endTime)) {notifyBlockEvent(endTime);}stopDump();}}// 根据时间差来判断是否卡顿private boolean isBlock(long endTime) {return endTime - mStartTimestamp > mBlockThresholdMillis;}

先来思考下,如果卡顿已经发生了,我们想要获取哪些信息来定位问题:

  1. 哪个位置发生了卡顿,我觉得这是最最重要的
  2. 发生卡顿的原因,是内存不够导致的,其他地方导致的,这对定位问题比较重要

那这些信息应该是在卡顿后,再去获取吗,还能拿到现场信息嘛? 带着这些问题,来看看 BlockCanary 是怎么做的

来看看 startDump() 中做了什么

    private void startDump() {// 分别调用了 StackSampler/CpuSampler 的 start()   BlockCanaryInternals.getInstance().stackSampler.start()BlockCanaryInternals.getInstance().cpuSampler.start();}

这两个 start 都是在子线程中执行的,原因是基类内部有个 HandlerThread ,在这个子线程执行方法 doSample()

获取当前执行的内存堆栈的逻辑就在这里,也是定位卡顿位置的关键

    // StackSampler@Overrideprotected void doSample() {StringBuilder stringBuilder = new StringBuilder();// 遍历当前线程(主线程)的所有堆栈,放到 String 里for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {stringBuilder.append(stackTraceElement.toString()).append(BlockInfo.SEPARATOR);}synchronized (sStackMap) {if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {sStackMap.remove(sStackMap.keySet().iterator().next());}// 保存到 Map 中,最多保存 100 个sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());}}// CpuSamplerprotected void doSample() {BufferedReader cpuReader = null;BufferedReader pidReader = null;// 通过 /proc/stat 读取 cpu 参数,这个 Android 高版本中已经被禁用了cpuReader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/stat")), BUFFER_SIZE);String cpuRate = cpuReader.readLine();if (mPid == 0) {mPid = android.os.Process.myPid();}pidReader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);String pidCpuRate = pidReader.readLine();if (pidCpuRate == null) {pidCpuRate = "";}parse(cpuRate, pidCpuRate);}

继续来看是怎样通知的,都通知了谁

    private void notifyBlockEvent(final long endTime) {// 触发 onBlockEvent,在子线程中执行HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {@Overridepublic void run() {mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);}});}

        @Overridepublic void onBlockEvent(long realTimeStart, long realTimeEnd,long threadTimeStart, long threadTimeEnd) {// Get recent thread-stack entries and cpu usage// 获取之前在内存中保存的堆栈ArrayList<String> threadStackEntries = stackSampler.getThreadStackEntries(realTimeStart, realTimeEnd);if (!threadStackEntries.isEmpty()) {// 组合一个阻塞信息的对象BlockInfo blockInfo = BlockInfo.newInstance().setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd).setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd)).setRecentCpuRate(cpuSampler.getCpuRateInfo()).setThreadStackEntries(threadStackEntries).flushString();// 写到硬盘中去  LogWriter.save(blockInfo.toString());// 如果拦截器里还有,就依次去执行,就是责任揽模式的一种实现if (mInterceptorChain.size() != 0) {for (BlockInterceptor interceptor : mInterceptorChain) {//在 BlockCanary 的构造函数里,添加了拦截器到队列中,主要是来打开 DisplayActivity,像开发者展示卡顿信息 interceptor.onBlock(getContext().provideContext(), blockInfo);}}}}

小结

BlockCanary 核心是通过 Looper 中分发 Message 前后会执行的打印,在这个判断执行时长是否过长,如果判断为阻塞,就马上将执行前就开始收集的程序堆栈/CPU 内存信息在一个页面中展示出来,这里的收集都是在子线程中进行的

既然在高版本上 “/proc/stat” 已经不能用了,我们能不能做个版本判断,在高版本上不去其获取 CPU 信息了呢?

这样还是不太好,如果没有 CPU 使用频率这些信息,我们判断卡顿时就没法排查是 CPU 跑满了,分不到足够的时间片

高版本获取 CPU 使用率

不过“/proc/stat”由于在 API 26 以上,只有系统应用才能使用,这也让 BlockCanary 的 CPU 监测部分在高版本上不可使用了

原因是会被利用来对系统旁路攻击,Android 禁止了非系统应用的访问

思考

  1. 除了使用 Printer , Android 10 以上支持 Looper Observer,不过由于是 Hidden API,需要绕过限制
  2. Printer 是 Looper 中的成员变量,会不会存在被替换的风险,如果有其他库也使用了 Printer 会导致无法开始采样吧,个人疑问,有不同看法欢迎讨论

相关知识

  • StackTrace 当前线程的堆栈信息,当时一个方法执行时,会对应创建一个栈帧,通过 StackTraceElement ,可以获取如下数据:
  1. 声明的类名
  2. 方法名
  3. 行号
  4. 文件名(不知道是什么)
  • 组件开关 通过 PackageManager.setComponentEnabledSetting,能够对组件的开关进行控制,BlockCanary 在 Launcher 的显示和隐藏就是通过这个设置的,关闭之后 DisplayActivity 的桌面图片也不再显示了

除了卡顿监控之外,Android 还有以下几种性能监控方式:

  1. 内存监控:通过监控应用程序的内存使用情况,诊断内存溢出、内存泄漏等问题,并定位内存泄漏的位置。
  2. CPU 监控:通过监控应用程序的 CPU 使用率,诊断频繁占用 CPU 的代码段,并定位其所在位置。
  3. 网络监控:通过监控应用程序的网络请求和响应信息,分析网络请求频率、请求/响应时间、网络异常等问题,找出网络请求性能瓶颈点。
  4. 能耗监控:通过监控应用程序的能耗数据,诊断应用程序的能耗情况,找到耗电严重的代码片段,并对其进行优化。
  5. 稳定性监控:通过跟踪应用程序的崩溃日志,诊断应用程序的稳定性问题,包括 ANR、Crash 等,并找出引起崩溃的代码片段。

以上是 Android 中常用的性能监控方式,开发者可以根据自己的需求选择不同的监控工具和技术手段,以达到优化应用程序性能的目的。

Android 中常用的性能监控工具如下

  1. 内存监控:Android Studio 自带的 DDMS 工具、MAT(Memory Analyzer Tool)、LeakCanary 等。
  2. CPU 监控:Android Studio 自带的 Profiler 工具、Systrace、TraceView 等。
  3. 网络监控:Charles、Wireshark、tcpdump 等。
  4. 能耗监控:Battery Historian、Battery Usage 等。
  5. 稳定性监控:ACRA、Bugsnag、Firebase Crashlytics 等。

除了上述工具外,还有一些第三方性能监控集成平台,如 AppDynamics、New Relic、Dynatrace 等。这些平台可以对应用程序进行全面的性能监控,提供各种监控数据和分析报告,帮助开发者诊断和解决性能问题。

根据不同的性能监控问题,我们需要采用不同的性能优化手段,而目前还是有些人群对于性能优化中间的一些优化手段掌握的不是很熟练,因此针对性能优化中间的所有不同类型的优化手段进行了归类整理,有启动优化、内存优化、网络优化、卡顿优化、存储优化……等,整合成名为《Android 性能优化核心知识点手册》,大家可以参考下:

《APP 性能调优进阶手册》:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能调优核心笔记汇总》:https://qr18.cn/FVlo89

《Android 性能监控框架》:https://qr18.cn/FVlo89

BlockCanary 卡顿监测相关推荐

  1. 卡顿监测 · 方案篇 · Android卡顿监测指导原则

    一.引言 Hello,我是小木箱,欢迎来到小木箱成长营系列教程,今天将分享卡顿监测 · 方案篇 · Android卡顿监测指导原则.小木箱从七个维度将Android卡顿监测技术方案解释清楚. 第一个维 ...

  2. iOS主线程卡顿监测

    iOS开发过程中,我们有时需要监控主线程的卡顿情况,本文介绍主线程卡顿的实现方法. 新建MainThreadMonitor类,并且在.h中声明方法. #import <Foundation/Fo ...

  3. Android卡顿优化

    一. Android渲染知识 1.1 绘制原理 Android系统要求每一帧都要在 16ms 内绘制完成,平滑的完成一帧意味着任何特殊的帧需要执行所有的渲染代码(包括 framework 发送给 GP ...

  4. sourcetree 卡顿_Android卡顿性能监测方案对比

    前言 近期在研究关于 Android 卡顿性能监控,分别验证了两种相对有效的监测方案: Looper 字符串匹配方案 Choreographer 帧率检测方案 这两种方案都可以监控到应用的卡顿现象,但 ...

  5. Android 性能监测工具,优化内存、卡顿、耗电、APK的方法

    导语     安卓大军浩浩荡荡,发展已近十个年头,技术优化月新日异,如今 Android 9.0 代号P  都发布了,Android系统性能已经非常流畅了.但是,到了各大厂商手里,改源码自定系统,使得 ...

  6. 找到卡顿来源,BlockCanary源码精简分析

    /   今日科技快讯   / 近日,美国约翰霍普金斯大学宣布,NASA哈勃太空望远镜发现了史上最遥远的单颗恒星,距离地球129亿光年.这颗恒星名为Earendel(或称WHL0137-LS),源自古英 ...

  7. 虚拟大师 卡android界面,找出造成Android App界面卡顿的原因- BlockCanary

    BlockCanar介绍 BlockCanary对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析.定位到问题所在,迅速优化应用.其特点有: 非侵入式,简单的两行就打开监控,不需要到处 ...

  8. Android App卡顿分析,以及使用Choreographer进行帧率统计监测

    1.背景: 卡顿是最影响App用户体验的原因之一.卡顿造成的原因多种多样,简单列举一下   1.布局层级过多,设置无用的背景色,布局中添加了多种不必要的背景色,导致view绘制的时候多次绘制,引起卡顿 ...

  9. 深入探索Android卡顿优化

    由于卡顿优化这一主题包含的内容太多,为了更详细地进行讲解,因此,笔者将它分为了上.下两篇.本篇,即为<深入探索Android卡顿优化>的上篇. 本篇包含的主要内容如下所示: 卡顿优化分析方 ...

最新文章

  1. 开源如何占领软件世界?
  2. GNU make 和 makefile
  3. html显示隐藏密码,Web前端,登录密码显示隐藏眼睛
  4. flutter 常用网址
  5. 20175316 盛茂淞 实验一 Java开发环境的熟悉
  6. 机器学习中的群论方法
  7. CSS padding margin border属性讲解
  8. sqlserver 更新 datetime 数据_SqlServer 关于 datetime 的更新引发的思考
  9. arcgis 批量计算几何_ArcGIS数据统计
  10. v9更新系统后为何显示服务器连接,V9服务器
  11. CSS-四种引入方式
  12. CarMaker快速入门第四课开发48V P1混动系统
  13. 教你玩转ACDSEE
  14. 手机ncm转mp3工具_一款手机、电脑都能用的文字转语音工具,够高能! - 橘子世界...
  15. MySQL:Can't create test file XXX.lowe-test
  16. 用户态创建socket来控制arp报文的收发,含编码
  17. 美国 android手机号码,格式编辑文本为美国电话号码1(xxx)-xxxx你输入android?
  18. JSP基础 mcv规范 EL
  19. 十月,你好。余杭,巴比特来了!
  20. 极速办公(word)如何添加文字水印

热门文章

  1. 生活随记-回不去的故乡
  2. python删除空文件夹脚本
  3. Chrome学习整理
  4. 小马哥----高仿红米note 主板m8207 201509.2刷机拆机主板图与开机识别图示展示
  5. path.resolve() 通俗解释、实例
  6. 朋友圈第五条广告精准投放 腾讯社交广告推广形式如何收费
  7. 网站被刷流量简单处理的一次
  8. 500内降噪蓝牙耳机,南卡和VIVO降噪蓝牙耳机谁家好用?
  9. python excel数据处理 空格替换_Python/Excel/SPSS/SQL数据处理方法比较之5 - 空格清理...
  10. 数据结构与算法(第一章 数据结构的基本概念 )