【Android性能优化】线上性能监控

本文介绍了线上性能监控工具Bugly的介绍和使用。以及滑动卡顿的实时检测。
腾讯的 Bugly 是一款专注于移动端和 PC 端应用的 线上崩溃监控、性能监控与异常分析 的一站式工具,广泛应用于移动应用(Android/iOS)以及小游戏、小程序等平台。它帮助开发者实时发现、定位和修复线上应用中的崩溃、卡顿、ANR(Application Not Responding)、内存泄漏等问题,从而提升应用稳定性与用户体验。
一、Bugly 数据采集原理
Bugly 的核心功能依赖于对应用运行时数据的 实时采集与上报。
当应用发生 未捕获的异常(如 Java 的 Throwable、Objective-C 的 NSException、C++ 的 Signal/Exception) 时,Bugly SDK 会通过 全局异常捕获机制 拦截这些异常。
捕获到的异常信息包括:
- 崩溃堆栈(Call Stack)
- 崩溃类型(Java Crash / Native Crash / ANR / OOM 等)
- 崩溃线程信息
- 设备信息(型号、系统版本、CPU 架构等)
- 应用信息(版本号、渠道、包名等)
- 用户信息(可选,如用户 ID、登录态)
在 Android 上,Bugly 通过监听 /data/anr/traces.txt 文件或使用 FileObserver 监听 ANR 日志文件变化,或者通过 Looper 监听主线程卡顿超时 来检测 ANR。在 iOS 上,通过监控主线程 RunLoop 状态,判断是否长时间未响应。
开发者也可以手动调用 Bugly SDK 接口,上报自定义的异常、业务错误或关键日志,便于排查特定业务问题。
2. 数据传输与上报机制
- 本地缓存 + 批量上报:SDK 会将采集到的数据先缓存在本地(如 SQLite 或文件),在网络可用时(如 Wi-Fi 或移动网络)进行批量压缩加密后上传到 Bugly 服务器。
- 断点续传 & 异常重试:如果上传失败,数据会在下次启动或网络恢复时自动重试,确保数据不丢失。
- 实时性:大部分崩溃数据可在 几分钟内 展示在 Bugly 后台,供开发者及时查看与分析。
3. 符号化(Symbolication)
对于 Native 崩溃(C/C++),崩溃堆栈通常是经过编译器优化的地址,无法直接阅读。Bugly 通过上传 符号表文件(如 dSYM / SO 符号文件),在服务端进行 符号还原(Symbolication),将地址转换为具体的函数名、文件名与行号,极大地方便定位问题。
- 开发者需要在每次发布新版本时,上传对应的符号表,否则 Native 崩溃堆栈可能难以解析。
二、Bugly 设计架构
Bugly 的整体架构可以分为 客户端 SDK、数据传输层、服务端平台 三大部分:
1. 客户端 SDK(集成在 App 中)
负责在用户设备上 实时监控、采集各种异常和性能数据,包括:
- 崩溃捕获模块
- ANR 监控模块
- 卡顿检测模块
- 内存监控模块
- 数据本地存储与上报模块
- 用户行为与自定义事件上报接口
SDK 具有如下特点:
- 轻量级、低侵入、高性能:对应用本身的性能影响极小,启动速度快,运行时 CPU/内存开销低。
- 多平台支持:支持 Android、iOS、微信小程序、Unity、Cocos 等平台。
- 灵活配置:开发者可以控制监控粒度,比如是否开启卡顿监控、ANR 监控,设置卡顿阈值等。
2. 数据传输层
- 负责将客户端采集的数据安全、可靠地传输到 Bugly 云端服务器。
- 包括数据加密、压缩、断点续传、重试机制等。
- 支持离线缓存,在网络恢复后自动同步。
3. 服务端平台(Bugly 控制台)

这是开发者日常使用 Bugly 的主要后台入口,提供以下功能:
- 崩溃分析
- 实时展示 Crash 数量、影响用户数、崩溃率等关键指标。
- 提供详细的崩溃堆栈、设备信息、用户信息、出现趋势图等。
- 支持按版本、时间、设备等维度筛选与分析。
- 支持 Native 崩溃符号化展示,精准定位问题代码。
- ANR 分析
- 展示 ANR 发生次数、影响用户、堆栈信息等。
- 支持 ANR 日志下载与分析。
- 报警与通知
- 支持设置崩溃率、ANR 阈值等报警策略,通过邮件、企业微信、钉钉等方式通知开发者。
- 多团队协作与权限管理
- 支持不同团队成员拥有不同权限,如只读、上传符号表、管理项目等。
三、Bugly 接入方式
Bugly 提供了非常便捷的 SDK 接入流程,支持主流开发语言与平台。
(1) Android 接入
Android 平台的接入,首先需要在项目的 build.gradle 中引入 Bugly SDK 依赖。
下载Bugly SDK:
添加Bugly SDK依赖:
dependencies {
implementation files('libs/Bugly_sdk.aar')
}
腾讯也提供了 Maven 仓库的在线接入方式,具体查看 Bugly SDK Andoid 接入文档
在代码中初始化 Bugly(一般在 Application 的 onCreate 方法中):
CrashReport.initCrashReport(getApplicationContext(), "你的AppID", false);
第三个参数 false 表示是否开启 Debug 模式,开启后会在 Logcat 中打印详细的日志,方便调试。
(2)IOS 接入
聚焦于Swift项目,使用Framework包管理接入Bugly SDK。
下载Bugly SDK Framework包:
拖拽Bugly.framework文件到Xcode工程内(请勾选Copy items if needed选项)
然后在项目的 build phase 板块的 Link Binary With Libraries 中添加 Bugly.framework。还有:
- SystemConfiguration.framework
- Security.framework
- libz.dylib 或 libz.tbd
- libc++.dylib 或 libc++.tbd

然后右键创建一个空的OC文件,XCode会提示自动创建一个对应的 Bridge-Header.h 文件,创建后在这个头文件中添加以下代码:
#import <Bugly/Bugly.h>
注意在项目配置中添加这个头文件的相对路径。
在工程 AppDelegate.m 的 application:didFinishLaunchingWithOptions: 方法中初始化:
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 应用程序启动完成后调用
print("✅ 应用已启动")
// 1. 创建一个 BuglyConfig 实例
let config = BuglyConfig()
// 2. 设置一些自定义配置(可选)
config.debugMode = true // 开启 Debug 模式,查看 SDK 内部日志
config.channel = "AppStore" // 自定义渠道,比如 "AppStore", "Test", "Internal"
config.version = "1.0.0" // 自定义版本号(如果和 Xcode 的不一致)
config.blockMonitorEnable = true // 开启卡顿监控
config.blockMonitorTimeout = 2.0 // 卡顿超时时间(秒)
config.unexpectedTerminatingDetectionEnable = true // 开启非正常退出检测
config.viewControllerTrackingEnable = true // 开启页面信息记录(默认开启)
// 如果你想接收回调,比如崩溃时带上额外信息,可以实现 BuglyDelegate
// config.delegate = self // (需遵循 BuglyDelegate 协议)
// 3. 使用配置启动 Bugly
Bugly.start(withAppId: "5875922631",
developmentDevice:true,
config: config)
return true
}
注意在新项目中,一般都直接使用App作为项目入口了:
@main
struct iOSApp: App {
// 注入 UIKit 的 AppDelegate,使其仍然可以响应系统事件
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
需要去掉AppDelegate的main注解,使用 UIApplicationDelegateAdaptor 来注入 AppDelegate。
崩溃上报测试:
struct ContentView: View {
let greet = Greeting().greet()
var body: some View {
Text(greet)
.onTapGesture {
print("文字被点击了!")
fatalError("这是一个手动的error测试")
// BugCreate_iosKt.createACrash()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
在文字被点击时,会触发fatalError,从而上报一个崩溃。也可以使用KMP内部实现的 BugCreate_iosKt.createACrash() 方法来手动上报一个崩溃。
(3)其他平台
- Unity、Cocos、微信小程序、H5 等平台均有对应的接入文档与 SDK。
2. 符号表上传
- Android:需要上传 dSYM 文件(或对应映射文件),通常从构建系统中自动上传(如 Jenkins 插件、Gradle 脚本)。
- iOS:必须上传 dSYM 文件,每次 App 发布新版本后都要上传,否则 Native 崩溃无法解析。
- Bugly 提供了 自动符号表上传脚本 / 插件,也支持手动上传。
3. 控制台使用
在报错,ANR,崩溃发生之后,过一段时间才能在 Bugly 控制台看到。
登录 Bugly 官方网站:腾讯Bugly 一种愉悦的开发方式
- 选择对应的项目(App)。
- 查看 崩溃、ANR、卡顿、性能 等核心监控数据。
- 点击某个崩溃,可查看:
- 崩溃概览(影响用户数、次数、版本分布等)
- 堆栈详情(支持符号化)
- 设备信息、用户信息
- 时间趋势图
- 使用筛选、搜索功能快速定位问题。
- 配置报警、用户反馈、自定义监控等高级功能。
滑动掉帧监控
另一个比较影响体验的性能问题就是滑动卡顿。滑动的卡顿一般都在长列表进行交互时发生,要监控应用内部滑动组件(如 RecyclerView 等)的卡顿,主要有以下几种方案:
方案 1:基于 Choreographer 帧回调监控主线程卡顿
这是目前最常用、性能开销较低、可控性高的方式,可以用来监控全局或指定区域的主线程卡顿(包括滑动卡顿)。
Android 系统通过 Choreographer 类调度每一帧的 UI 渲染。它提供了一个回调:Choreographer.FrameCallback,在每一帧即将渲染时触发。我们可以记录每帧的时间戳,如果发现两次回调之间的时间间隔超过 16ms(或者自定义阈值,比如 30ms),则认为发生了卡顿。
只需要注册一个 Choreographer.FrameCallback,计算相邻两帧的时间差。如果时间差超过设定的阈值(如 32ms 表示掉帧 2 帧),则认为发生卡顿。在卡顿发生时,可以收集如下信息:
- 卡顿时长
- 当前主线程堆栈(找出耗时操作)
- 当前 Activity / Fragment / View 信息
- 是否在滑动状态(如 RecyclerView 是否正在滚动)
示例:
Choreographer.getInstance().postFrameCallback(object : FrameCallback {
private var lastFrameTimeNanos: Long = 0
override fun doFrame(frameTimeNanos: Long) {
if (lastFrameTimeNanos != 0L) {
val elapsedNanos = frameTimeNanos - lastFrameTimeNanos
val elapsedMillis: Long = TimeUnit.NANOSECONDS.toMillis(elapsedNanos)
// 设定卡顿阈值为 32ms(约两帧丢失)
if (elapsedMillis > 32) {
// 发生了卡顿,可以打印堆栈或上报
Log.e("卡顿监控", "检测到卡顿: " + elapsedMillis + "ms")
BuglyLog.e("卡顿监控", "检测到卡顿: " + elapsedMillis + "ms")
}
}
lastFrameTimeNanos = frameTimeNanos
Choreographer.getInstance().postFrameCallback(this)
}
})
方案 2:基于 Looper 日志打印
BlockCanary 是一个著名的开源卡顿检测库,其核心原理是在主线程的 Looper 处理消息前后打点,计算每个 Message 的处理耗时。
如果某个消息处理时间超过阈值(如 16ms 或 500ms),则认为发生卡顿,此时 dump 线程堆栈等信息用于分析。
通过替换 Looper.getMainLooper().setMessageLogging(),监听主线程消息的执行。记录每条消息的开始和结束时间,如果执行时间过长,就触发卡顿分析。
可以能精准捕捉主线程耗时操作。输出卡顿时的堆栈、线程状态、内存等信息,方便定位问题。
但是对性能有一定影响(但通常可接受,尤其只在调试时使用)。
下面的这段代码实现了两个方法,一个是开启检测,一个是关闭检测。
以LazyColumn为例,在 scrollState.isScrollInProgress 为true时开启检测,在 scrollState.isScrollInProgress 为 false 时关闭检测。
object LooperMsgListener {
private var lastFrameTime: Long = 0
fun startCheckFrameTime() {
Log.i("卡顿监控", "startCheckFrameTime")
lastFrameTime = System.currentTimeMillis()
Looper.getMainLooper().setMessageLogging { msg ->
val currentTime = System.currentTimeMillis()
if (lastFrameTime != 0L) {
val diffTime = currentTime - lastFrameTime
if (diffTime > 16) {
Log.d("卡顿监控", "卡顿检测: $diffTime ms")
}
}
lastFrameTime = currentTime
}
}
fun stopCheckFrameTime() {
Log.i("卡顿监控", "stopCheckFrameTime")
Looper.getMainLooper().setMessageLogging(null)
}
}
方案 3: 基于trace文件
可以先采集trace文件,再上传到服务器上再分析。
可以 查找 Choreographer#doFrame 事件 ,在 Trace 的时间轴上,重点关注 Choreographer#doFrame 事件。这是衡量 UI 渲染性能的核心指标。
- 正常情况: 对于 60fps,
Choreographer#doFrame的持续时间应该接近 16.67 毫秒。 - 卡顿迹象: 如果你看到
Choreographer#doFrame事件的持续时间远超 16.67 毫秒(例如 30ms, 50ms 甚至 100ms+),这表明发生了一帧的渲染超时,即掉帧,用户就会感觉到卡顿。
还要确定卡顿发生的时间点,在时间轴上找到滑动操作开始和结束的区域。在滑动过程中,特别留意那些持续时间异常长的 Choreographer#doFrame 事件。这些就是卡顿发生的精确时刻。
Google官方的分析网站地址为: https://ui.perfetto.dev/