You need to enable JavaScript to run this app.
文档中心
应用性能监控全链路版

应用性能监控全链路版

复制全文
下载 pdf
最佳实践
APMPlus Flutter 桥接原生 SDK 集成指南
复制全文
下载 pdf
APMPlus Flutter 桥接原生 SDK 集成指南
本文为您介绍如何利用 APMPlus 的 Android 或 iOS 原生 SDK,上报与监控 Flutter 应用相关自定义错误、自定义事件、自定义日志等监控数据。
背景信息
APMPlus 官方不再提供 Flutter SDK,为了方便 Flutter 侧的统一调用,开发者设计了 APMPlusBridge 类,将原生平台通信的所有细节进行封装,允许通过 Flutter 与 Native 桥接的方式上报监控数据。
  • APMPlusBridge.dart 的完整代码如下:
import 'dart:async';
import 'package:flutter/services.dart';
// APMPlus 日志级别
enum APMLogLevel {
debug,
info,
warn,
error,
}
// 自定义错误严重等级
enum APMErrorSeverity {
fatal,
error,
warn,
info,
}
// APMPlus Flutter 桥接核心类
class APMPlusBridge {
static const MethodChannel _channel = MethodChannel('apmplus_flutter_bridge');
// 初始化原生 APMPlus SDK
//
// [config] 必须同时包含 'appID' 与 'appToken',其余参数按平台可选。
// Android 和 iOS 所需的参数不同,请参考各自平台的集成文档。
//
// 示例:
// {
// // --- 通用参数 ---
// 'appID': 'YOUR_APP_ID', // 替换为您的 App ID
// 'appToken': 'YOUR_APP_TOKEN', // 替换为您的 App Token
// 'channel': 'App Store',
// 'enableDebug': true, // Debug 模式,线上环境务必配置为 false
//
// // --- Android 特定参数 ---
// 'apm_insight_plugin_enable': true, // 是否启用插桩插件
//
// // --- iOS 特定参数 ---
// // (如有)
// }
static Future<void> initialize(Map<String, dynamic> config) async {
try {
await _channel.invokeMethod('initialize', config);
} on PlatformException catch (e) {
print("Failed to initialize APMPlus: '${e.message}'.");
}
}
// 设置用户 ID
//
// [userId] 用户唯一标识符
static Future<void> setUserID(String userId) async {
try {
await _channel.invokeMethod('setUserID', {'userId': userId});
} on PlatformException catch (e) {
print("Failed to set user ID: '${e.message}'.");
}
}
// 上报自定义错误
//
// [type] 错误类型/名称,如 `payment_error`
// [message] 错误信息
// [stack] 错误堆栈信息
// [severity] 错误严重等级
// [tags] 维度标签,用于分类和筛选
// [extras] 附加信息,用于问题排查
static Future<void> reportCustomError({
required String type,
required String message,
String? stack,
APMErrorSeverity severity = APMErrorSeverity.error,
Map<String, String>? tags,
Map<String, dynamic>? extras,
}) async {
final Map<String, dynamic> params = {
'type': type,
'message': message,
'stack': stack ?? '',
'severity': severity.toString().split('.').last,
'tags': tags ?? {},
'extras': extras ?? {},
};
try {
await _channel.invokeMethod('reportCustomError', params);
} on PlatformException catch (e) {
print("Failed to report custom error: '${e.message}'.");
}
}
// 上报自定义事件
//
// [eventName] 事件名称
// [attributes] 事件维度属性 (可枚举)
// [metrics] 事件指标 (不可枚举的数值)
static Future<void> reportCustomEvent({
required String eventName,
Map<String, String>? attributes,
Map<String, double>? metrics,
}) async {
final Map<String, dynamic> params = {
'eventName': eventName,
'attributes': attributes ?? {},
'metrics': metrics ?? {},
};
try {
await _channel.invokeMethod('reportCustomEvent', params);
} on PlatformException catch (e) {
print("Failed to report custom event: '${e.message}'.");
}
}
// 记录自定义日志 (Alog/VLog)
//
// [level] 日志级别
// [tag] 日志标签
// [message] 日志内容
static Future<void> log({
required APMLogLevel level,
required String tag,
required String message,
}) async {
final Map<String, dynamic> params = {
'level': level.toString().split('.').last,
'tag': tag,
'message': message,
};
try {
await _channel.invokeMethod('log', params);
} on PlatformException catch (e) {
print("Failed to log: '${e.message}'.");
}
}
}
  • Flutter 调用示例如下:
void main() {
runApp(MyApp());
// 应用启动时初始化 APMPlus
_initAPMPlus();
// 示例:上报一个自定义错误
APMPlusBridge.reportCustomError(
type: 'FlutterLoginError',
message: 'User failed to login with error code 500',
stack: '...', // 可以传入 try-catch 捕获的堆栈信息
severity: APMErrorSeverity.fatal,
tags: {'login_method': 'email'},
extras: {'user_email': 'test@example.com'},
);
// 示例:上报一个自定义事件
APMPlusBridge.reportCustomEvent(
eventName: 'addToCart',
attributes: {'item_category': 'electronics'},
metrics: {'item_price': 1999.99},
);
// 示例:记录一条日志
APMPlusBridge.log(
level: APMLogLevel.info,
tag: 'ShoppingCart',
message: 'Item successfully added to cart.',
);
}
void _initAPMPlus() {
// 根据平台准备不同的配置
final Map<String, dynamic> config = {
// --- 通用参数 ---
'appID': 'YOUR_APP_ID', // 替换为您的 App ID
'appToken': 'YOUR_APP_TOKEN', // 替换为您的 App Token
'channel': 'flutter_channel',
'enableDebug': true, // Debug 模式,线上环境务必配置为 false
// --- Android 特定参数 ---
'blockDetect': true,
'seriousBlockDetect': true,
'fpsMonitor': true,
'memoryMonitor': true,
'batteryMonitor': true,
'cpuMonitor': true,
'diskMonitor': true,
'trafficMonitor': true,
'operateMonitor': true,
'startMonitor': true,
'pageMonitor': true,
'netMonitor': true,
'enableLogRecovery': true,
// --- iOS 特定参数 ---
'defaultMonitors': 0x1 | 0x2 | 0x4, // 示例:开启崩溃、网络、启动监控
};
APMPlusBridge.initialize(config);
APMPlusBridge.setUserID('user_12345');
}
前提条件
调用 initializesetUserID 前,务必确保已获得用户的隐私授权。
使用限制
  • 此方案提及的所有桥接调用默认均在主线程执行。虽然 APMPlus 原生 SDK 大部分线程安全,但保险起见,建议所有来自 Flutter 的调用均在主线程发起。
  • 自定义错误上报时,务必严格使用官方接口。其中,Android 使用 MonitorCrash.reportCustomErr,iOS 使用 UserException 模块接口。
  • 原生桥接代码中应添加必要的参数校验,防止因 Flutter 传递非法参数而导致原生代码崩溃,例如:null 或类型错误。
  • enableDebug 配置用于开关 Debug 调试模式,开启此配置将打印 APMPlus 详细日志,便于开发阶段验证数据上报。发布线上版本时,务必将其设置为 false。
  • 务必在 App 启动后尽早调用 APMPlusBridge.initialize,以确保启动性能等数据的采集准确性。原生 SDK 初始化应在 Application#onCreate (Android) 或 AppDelegate#didFinishLaunchingWithOptions (iOS) 中由 Flutter 首次调用时触发。原生侧可以增加一个静态布尔值 isInitialized 来防止重复初始化。
操作步骤
Android 集成指南
iOS 集成指南
  1. 添加 Maven 仓库。在项目根目录的 build.gradle 文件中,添加平台和 byteX 的 Maven 仓库地址。示例代码如下:
// project/build.gradle
buildscript {
repositories {
// ... 其他仓库
maven {
url "https://artifact.bytedance.com/repository/Volcengine/"
}
}
}
allprojects {
repositories {
// ... 其他仓库
maven {
url "https://artifact.bytedance.com/repository/Volcengine/"
}
}
}
  1. 添加 Gradle 依赖。在 app module 的 build.gradle 文件中,添加 APMPlus 相关依赖。示例代码如下:
注意
若使用海外应用,请将 .cn 后缀替换为 .oversea
// app/build.gradle
dependencies {
// ... 其他依赖
// APMPlus 核心依赖 (国内版本)
implementation 'com.volcengine:apm_insight:1.5.25.cn'
// APMPlus 崩溃监控模块
implementation 'com.volcengine:apm_insight_crash:1.5.18'
}
  1. 桥接代码实现。在 Flutter Plugin 或主工程的 Android 部分,创建一个 Kotlin 类,用于实现桥接逻辑。APMPlusFlutterBridgePlugin.kt 示例代码如下:
package com.example.your_project_name // 替换为您的包名
import android.app.Application
import android.content.Context
import com.apm.insight.MonitorCrash
import com.bytedance.apm.insight.ApmInsight
import com.bytedance.apm.insight.ApmInsightAgent
import com.bytedance.apm.insight.ApmInsightInitConfig
import com.bytedance.apm.insight.IDynamicParams
import com.apm.insight.log.VLog
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import org.json.JSONObject
class APMPlusFlutterBridgePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
private lateinit var mMonitorCrash: MonitorCrash
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "apmplus_flutter_bridge")
channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initialize" -> {
val config = call.arguments as? Map<String, Any> ?: return
initializeApm(config)
result.success(null)
}
"setUserID" -> {
val userId = (call.arguments as? Map<String, Any>)?.get("userId") as? String
if (userId != null) {
mMonitorCrash?.config()?.setUID(userId)
// 通过修改ApmInsight初始化参数IDynamicParams的getUserId回调修改用户id
// ApmInsight.getInstance().setUserId(userId)
}
result.success(null)
}
"reportCustomError" -> {
val args = call.arguments as? Map<String, Any>
reportCustomError(args)
result.success(null)
}
"reportCustomEvent" -> {
val args = call.arguments as? Map<String, Any>
reportCustomEvent(args)
result.success(null)
}
"log" -> {
val args = call.arguments as? Map<String, Any>
log(args)
result.success(null)
}
else -> result.notImplemented()
}
}
private fun initializeApm(config: Map<String, Any>) {
val appID = config["appID"] as? String ?: return
val appToken = config["appToken"] as? String ?: return
val channel = config["channel"] as? String ?: "unknown"
val enableDebug = config["enableDebug"] as? Boolean ?: false
// 1. 初始化崩溃监控
val crashConfig = MonitorCrash.Config.app(appID)
.token(appToken)
.channel(channel)
.debugMode(enableDebug)
.build()
mMonitorCrash = MonitorCrash.init(context as? Application, crashConfig)
// 2. 初始化性能监控
ApmInsight.getInstance().init(context as? Application)
VLog.init(context, 20) // 初始化自定义日志,最大磁盘占用20MB
val builder = ApmInsightInitConfig.builder()
.aid(appID)
.token(appToken)
.channel(channel)
.debugMode(enableDebug)
.blockDetect(config["blockDetect"] as? Boolean ?: false)
.seriousBlockDetect(config["seriousBlockDetect"] as? Boolean ?: false)
.fpsMonitor(config["fpsMonitor"] as? Boolean ?: false)
.memoryMonitor(config["memoryMonitor"] as? Boolean ?: false)
.batteryMonitor(config["batteryMonitor"] as? Boolean ?: false)
.cpuMonitor(config["cpuMonitor"] as? Boolean ?: false)
.diskMonitor(config["diskMonitor"] as? Boolean ?: false)
.trafficMonitor(config["trafficMonitor"] as? Boolean ?: false)
.operateMonitor(config["operateMonitor"] as? Boolean ?: false)
.startMonitor(config["startMonitor"] as? Boolean ?: false)
.pageMonitor(config["pageMonitor"] as? Boolean ?: false)
.netMonitor(config["netMonitor"] as? Boolean ?: false)
.enableLogRecovery(config["enableLogRecovery"] as? Boolean ?: false)
ApmInsight.getInstance().start(builder.build())
}
private fun reportCustomError(args: Map<String, Any>?) {
if (args == null) return
val type = args["type"] as? String ?: "unknown_error"
val message = args["message"] as? String ?: ""
val stack = args["stack"] as? String
val severity = args["severity"] as? String ?: "error"
val tags = args["tags"] as? Map<String, String> ?: emptyMap()
val extrasAny = args["extras"] as? Map<String, Any> ?: emptyMap()
// 合并 extras/tags 为 data(String->String)
val data = mutableMapOf<String, String>()
data.putAll(tags)
data["severity"] = severity
extrasAny.forEach { (k, v) ->
if (v != null) data[k] = v.toString()
}
// throwable:优先使用堆栈字符串构造;无堆栈传 null
val throwable: Throwable? = if (!stack.isNullOrBlank()) RuntimeException(stack) else null
mMonitorCrash?.reportCustomErr(message, type, throwable, data)
}
private fun reportCustomEvent(args: Map<String, Any>?) {
if (args == null) return
val eventName = args["eventName"] as? String ?: return
val attributes = args["attributes"] as? Map<String, String>
val metrics = args["metrics"] as? Map<String, Double>
ApmInsightAgent.monitorEvent("event1", dimension, metric);
}
private fun log(args: Map<String, Any>?) {
if (args == null) return
val level = args["level"] as? String ?: "info"
val tag = args["tag"] as? String ?: "FlutterLog"
val message = args["message"] as? String ?: ""
when (level) {
"debug" -> VLog.d(tag, message)
"info" -> VLog.i(tag, message)
"warn" -> VLog.w(tag, message)
"error" -> VLog.e(tag, message)
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
参数对齐详细说明如下:
Dart APMPlusBridge 方法
参数
Android 原生 API
映射关系
initialize
config
MonitorCrash.Config, ApmInsightInitConfig
将 Map 中的 appID, appToken, channel 等字段逐一设置到原生 Config 对象中。
说明
初始化时必填 appID 和 appToken。
setUserID
userId
MonitorCrash.setUserUniqueID, ApmInsight.getInstance().setUserId
直接将 userId 字符串传递给原生接口。
reportCustomError
type
MonitorCrash.reportCustomErr 的 type
直接映射。
message
MonitorCrash.reportCustomErr 的 message
直接映射。
stack
MonitorCrash.reportCustomErr 的 throwable
当 stack 非空时使用 new RuntimeException(stack),否则传 null。
tags, extras, severity
MonitorCrash.reportCustomErr 的 data
合并为 Map<String, String>,非字符串值安全转为 String。
reportCustomEvent
eventName
monitorEvent 的 serviceName
直接映射。
attributes
monitorEvent 的 dimension
直接映射。
metrics
monitorEvent 的 metric
直接映射。
log
level, tag, message
VLog.d, VLog.i, VLog.w, VLog.e
根据 level 选择对应的 VLog 方法,并传入 tag 和 message。
最近更新时间:2026.03.09 14:09:43
这个页面对您有帮助吗?
有用
有用
无用
无用