BTrace和Perfetto如何分析性能

一、背景

相信很多小伙伴在app性能方面都有一些需求,这篇文章主要是BTrace和Perfetto结合使用分析性能

btrace(又名 RheaTrace) 是抖音基础技术团队自研的一款高性能 Android Trace 工具,它基于 Systrace 实现,并针对 Systrace 不足之处加以改进,核心改进点如下。

  1. 效率提升:编译期间为 App 方法自动注入自定义事件,并提供高效、灵活配置规则。
  2. 性能提升:改进 Systrace 文件实时写 atrace 数据方式,性能提升最大 400 % 以上。
  3. 实用性提升:额外提供更详细 IO 等数据,大幅提升方法耗时归因效率;使用独创方案彻底来解决方法因执行异常引起 trace 数据不闭合问题。

Perfetto:是一个新的、低开销的 Trace 采集工具,旨在优化 Systrace 的性能表现。Perfetto 的目标是提供比 Systrace 更快、更细粒度的 Trace 采集,并支持与其他跨平台工具集成。Perfetto 采用二进制格式记录 Trace 数据,并使用基于 ProtoBuf 的数据交换格式进行数据导出,可与 Grafana、SQLite、BigQuery 等其他分析和可视化工具集成。Perfetto 采集的数据种类非常广泛,包括 CPU 使用情况、网络字节流、触摸输入、渲染等等。与 Systrace 相比,Perfetto 在性能和可定制性方面更为出色。

btrace:github.com/bytedance/b...

perfetto:ui.perfetto.dev/

二、BTrace的作用

1、渲染监控

在RenderThread中可以直观分析是具体影响渲染问题的业务代码

2、Binder监控

btrace的Binder增强目标是将Binder调用的接口名称与方法名称进行解析与展示

3. 阻塞监控

btrace可以检索到wait/park等原因导致线程等待状态

4. 线程创建监控

btrace可以检索到线程创建时机

三、BTrace接入

1. 接入BTrace

在您项目的根目录下build.gradle文件中增加 rhea-gradle-plugin 作为依赖

gradle 复制代码
buildscript {
    dependencies {
        classpath 'com.bytedance.btrace:rhea-gradle-plugin:2.0.3-rc02'
    }
}

接着,在app/build.gradle文件中应用如下所示插件和依赖

gradle 复制代码
dependencies {
    // rheatrace core lib
    implementation "com.bytedance.btrace:rhea-core:2.0.3-rc02"
}
...
apply plugin: 'com.bytedance.rhea-trace'
...
rheaTrace {
   compilation {
      // 
      traceFilterFilePath = "${project.rootDir}/trace-filter/preciseTraceFilter.txt"
      needPackageWithMethodMap = true
      applyMethodMappingFilePath = ""
   }
}

preciseTraceFilter.txt配置请参考 github.com/bytedance/b...

2. 执行

  1. 确保电脑已集成 adb 与 Java 环境。
  2. 连接手机至电脑,并保证可被 adb devices 识别。
  3. 安装集成 RheaTrace 2.0 的 APK 至手机。
  4. 下载"脚本管理rhea-trace-shell-2.0.0.jar" 最新脚本至电脑。
  5. 在电脑脚本所在目录执行如下命令。
java 复制代码
java -jar rhea-trace-shell.jar -a ${your app package name} -t 10 -o output.pb -r rhea.all sched -fullClassName
  1. trace 产物使用 ui.perfetto.dev/ 即可打开分析。 将output.pb使用perfetto打开,选择Open trace file

四、手把手教性能分析

1、渲染监控

xml 复制代码
<androidx.constraintlayout.widget.ConstraintLayout                                 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fl1"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/ll2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <FrameLayout
                android:id="@+id/fl3"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <LinearLayout
                    android:id="@+id/ll4"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Hello World! One"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Hello World! Two"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />
                </LinearLayout>
            </FrameLayout>
        </LinearLayout>
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

这个xml是MainActivity的布局文件,从代码中可以看出层级嵌套5层,布局层级过深,会导致性能降低,那看下在Perfetto中表现如何

在RenderThread中找到对应的activity_main,可以明显看到有5层布局

2、Binder监控

如上图,我的demo中binder通信比较少,不过可以看见binder调用的

3、阻塞监控

java 复制代码
protected void attachBaseContext(Context base) {
   super.attachBaseContext(base);
   attachTimeStart = SystemClock.uptimeMillis();
   SystemClock.sleep(2_000);
   attachTimeEnd = SystemClock.uptimeMillis();
   Log.d(TAG, "attachBaseContext time = " + (attachTimeEnd - attachTimeStart));
}

在demo里,Application的函数attachBaseContext调用sleep将主线程睡2000,可以在图片中看到Thread#Sleep

xml 复制代码
<provider
    android:authorities="com.test.one"
    android:name=".Test.Test1FileProvider"
    android:grantUriPermissions="true"
    android:exported="false" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>>

<provider
    android:authorities="com.test.two"
    android:name=".Test.Test2FileProvider"
    android:grantUriPermissions="true"
    android:exported="false" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

<provider
    android:authorities="com.test.three"
    android:name=".Test.Test3FileProvider"
    android:grantUriPermissions="true"
    android:exported="false" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

<provider
    android:authorities="com.test.four"
    android:name=".Test.Test4FileProvider"
    android:grantUriPermissions="true"
    android:exported="false" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

<provider
    android:authorities="com.test.five"
    android:name=".Test.Test5FileProvider"
    android:grantUriPermissions="true"
    android:exported="false" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
......

在demo里,清单文档声明了10个FileProvider,FileProvider继承了ContentProvider,在启动阶段会执行FileProvider的attach,在attach中调用getPathStrategy加载xml,在图片中可以看到getPathStrategy执行耗时

java 复制代码
@Override
public void onCreate() {
   super.onCreate();
   createTimeStart = SystemClock.uptimeMillis();
   Log.d(TAG, "contentProvider time = " + (createTimeStart - attachTimeEnd));
   new Thread("timlione") {
      @Override
      public void run() {
         super.run();
         readAssetFile(getAssets(), "woed.txt");
      }
   }.start();
   new Thread("timlitwo") {
      @Override
      public void run() {
         super.run();
         readAssetFile(getAssets(), "woed.txt");
      }
   }.start();
   new Thread("timlithree") {
      @Override
      public void run() {
         super.run();
         readAssetFile(getAssets(), "woed.txt");
      }
   }.start();
   new Thread("timlifore") {
      @Override
      public void run() {
         super.run();
         readAssetFile(getAssets(), "woed.txt");
      }
   }.start();
   new Thread("timlifive") {
      @Override
      public void run() {
         super.run();
         readAssetFile(getAssets(), "woed.txt");
      }
   }.start();
   readAssetFile(getAssets(), "woed.txt");
   createTimeEnd = SystemClock.uptimeMillis();
   Log.d(TAG, "onCreate time = " + (createTimeEnd - createTimeStart));
}

public static String readAssetFile(AssetManager assetManager, String fileName) {
   synchronized (MyApplication.class) {
      // 打开资产文件
      try (BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open(fileName)))) {
         String line;
         // 逐行读取文件内容并拼接成字符串
         StringBuilder content = new StringBuilder();
         while ((line = reader.readLine())!= null) {
            content.append(line).append("\n");
         }
         // 返回读取的内容
         return content.toString();
      } catch (Exception e) {
         e.printStackTrace();
         return null;
      }
   }
}

在demo里,Application中的onCreate开了5个线程加上主线程一共6个线程,调用readAssetFile函数,在readAssetFile函数中使用synchronized关键字加了锁,在图片中我们可以看到主线程被锁住了,矩形的长度就是被锁住的时间,每个线程中矩形的长度不同,代码锁住时间不同,长度短的在下一轮锁竞争中获取到了锁

4、线程创建

如图在主线程中创建25352线程

至此结束,赶快结合项目分析下吧

相关推荐
H10033 分钟前
重构(二)
android·重构
拓端研究室44 分钟前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil2 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744642 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.2 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt
不爱学习的啊Biao2 小时前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee3 小时前
PHP伪协议总结
android·开发语言·php
mmsx9 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人12 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌13 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习