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线程

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

相关推荐
.生产的驴3 小时前
Docker 部署Nacos 单机部署 MYSQL数据持久化
android·运维·spring boot·sql·mysql·docker·容器
找藉口是失败者的习惯4 小时前
Android adb 指令大全
android·adb
初学者-Study4 小时前
Android Osmdroid + 天地图 (二)
android·osmdroid地图点击·定位监听·marker配置
喜欢踢足球的老罗4 小时前
RN开发搬砖经验之—React Native(RN)应用转原生化-Android 平台
android·react native·react.js
红米饭配南瓜汤4 小时前
Android Binder通信02 - 驱动分析 - 架构介绍
android·架构·binder
️ 邪神5 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】启动页
android·flutter·ios·鸿蒙·reactnative
zhangphil5 小时前
Android从Drawable资源Id直接生成Bitmap,Kotlin
android·kotlin
HenCoder5 小时前
【泛型 Plus】Kotlin 的加强版类型推断:@BuilderInference
android·java·开发语言·kotlin
虾球xz6 小时前
游戏引擎学习第12天
android·学习·游戏引擎
nnloveswc6 小时前
PET-文件包含-FINISHED
android