Skip to content
AI 文档索引 ↗

Compose 与 SwiftUI ​

以下代码示意如何在已安装原生 SDK 与可选 UI 模块的宿主中绑定按钮和页面事件。按钮的 action、targetTag 应使用稳定业务标识;页面生命周期绑定按需使用。完整依赖和实例注入见下文。

kotlin
Button(onClick = trackBusinessAppClickAction(sdk = analytics, action = "checkout_submit", targetTag = "button", perform = { submitOrder() })) { Text("提交订单") }
CheckoutScreen(modifier = Modifier.trackBusinessScreenLifecycle(sdk = analytics, screenName = "checkout"))
swift
Button(action: trackBusinessAppClickAction(
    using: sdk, action: "checkout_submit", targetTag: "button", perform: submitOrder
)) { Text("提交订单") }
CheckoutView().trackBusinessScreenLifecycle("checkout", sdk: sdk)
可选关联标识

仅在宿主已有稳定逻辑键时传入 captureKey。不要读取按钮文本或页面私有内容来生成标识。

kotlin
Modifier.trackBusinessSemanticInteraction(action = "open_help", targetTag = "navigation_affordance", captureKey = "android:compose:help-entry")

页面、普通点击、语义交互和曝光事件支持附加业务属性;自定义属性名不要以 $ 开头,并遵循隐私过滤及事件大小限制。此能力需要接收服务支持对应事件契约;接入前请与服务提供方确认部署版本。

这些可选模块只处理显式绑定的 UI 事件,不扫描 UI 树或读取控件文本。事件的授权、队列和发送由核心 SDK 处理。

模块矩阵与依赖 ​

可选模块必须与同版本的核心 SDK 配套使用,事件使用核心 SDK 当前可用的存储后端;降级为内存时不具备进程重启恢复能力。

平台可选模块最低系统支持能力
Androidcom.trackbusiness:track-business-sdk-composeAndroid API 23+Compose 点击、页面生命周期、元素与 Lazy 列表曝光
iOSTrackBusinessSDKSwiftUIiOS 13+SwiftUI 点击、页面生命周期、元素曝光
iOS UIKitTrackBusinessSDKUIKitiOS 13+UIKit 显式控件 Binder、ViewController 生命周期、列表曝光

1. Android Jetpack Compose ​

依赖配置 ​

从客户端 SDK 下载页取得包含 Compose 扩展的离线集成包。按Android 安装步骤配置包内本地 Maven 仓库,再声明核心与 Compose 扩展依赖:

kotlin
dependencies {
    implementation("com.trackbusiness:track-business-sdk:1.0.0")
    implementation("com.trackbusiness:track-business-sdk-compose:1.0.0")
}

若组织已将包导入内部 Maven 仓库,可使用 com.trackbusiness:track-business-sdk-compose:1.0.0。该坐标不表示公共 Maven 已发布;模块版本以下载清单为准。

子树注入与按钮点击 ​

使用 TrackBusinessSDKProvider 为页面子树提供 SDK。标准 Button、IconButton 等控件把采集包装在 onClick 中:只有控件实际执行 action 时才记录事件,触摸、键盘和无障碍激活共用同一路径。禁用或取消的操作不会执行 action。

kotlin
import com.trackbusiness.sdk.compose.TrackBusinessSDKProvider
import com.trackbusiness.sdk.compose.trackBusinessAppClickAction

TrackBusinessSDKProvider(sdk = analytics) {
    Button(
        onClick = trackBusinessAppClickAction(
            action = "checkout_submit",
            targetTag = "checkout.submit",
            captureKey = "android:compose:checkout-submit",
            perform = { submitOrder() },
        ),
    ) {
        Text("提交订单")
    }
}

也可以显式传入 sdk = analytics,不使用子树注入。未注入 SDK 的 Preview 中,action 包装仍执行 perform,只跳过采集。perform 是宿主业务回调,SDK 不改变其异常语义。

点击与语义交互 ​

入口事件用途
trackBusinessAppClickAction$app_click标准控件真实激活,作为控件的 onClick
trackBusinessSemanticInteractionAction$sdk_interaction宿主选择的语义动作,作为控件的 onClick
Modifier.trackBusinessAppClick / trackBusinessSemanticInteraction对应的点击或语义事件自定义非控件节点的未消费触摸;不代表 Button 的 enabled 或完整激活语义

Modifier 只观察未取消的触摸,不提供控件业务动作或自动的键盘/无障碍激活。已有 Button + Modifier 接入应把元数据移入 action 包装,并将原 onClick 业务回调移到 perform;移除同一按钮的观察 Modifier 和 onAccessibilityActivate 配置。

同一个交互只选择一条记录路径。不要同时使用 action 包装、交互 Modifier 和手动 recordAppClick 记录同一次点击。点击与订单提交、支付结果属于不同事实,可以分别记录;SDK 不会因为它们发生时间接近而自动删除其中一个。captureKey 是稳定目标标识,不是每次操作的唯一 ID。

标识符使用静态名称,例如 checkout-submit;不要填手机号、账号、订单详情或凭据。

页面与生命周期 ​

  • 单纯进入页面:使用 trackBusinessScreenView,在节点进入 Composition 时上报一次 $screen;
  • 页面停留统计:使用 trackBusinessScreenLifecycle,在进入 Composition 时记录 $screen,在离开 Composition 时自动配对发送 $page_leave,并通过系统单调时钟计算 durationMs。
kotlin
CheckoutScreen(
    modifier = Modifier.trackBusinessScreenLifecycle(
        sdk = analytics,
        screenName = "checkout",
    ),
)

IMPORTANT

trackBusinessScreenLifecycle 已配对记录 $screen 与 $page_leave。请勿对同一 Composable 节点重复调用 trackBusinessScreenView 或手动 recordScreenLeave。

元素曝光与 Lazy 列表 ​

kotlin
// 单个组件曝光
PromoCard(
    modifier = Modifier.trackBusinessExposure(
        sdk = analytics,
        exposureId = "home_banner_01",
        minDurationMs = 500L,       // 有效停留时长(默认 500ms)
        minVisibleRatio = 0.5f,     // 最小可见比例(默认 50%)
    ),
)

// LazyColumn / LazyRow 列表项曝光
FeedItem(
    modifier = Modifier.trackBusinessListItemExposure(
        sdk = analytics,
        exposureId = "feed_item_$itemId",
    ),
)

NOTE

声明式曝光修饰符属于自动采集源(source=auto),需要策略显式启用 android-exposure 插件;手动调用 sdk.recordExposure(...) 属于 source=manual,不受该插件开关限制。


2. iOS SwiftUI ​

依赖与环境注入 ​

本地二进制 Swift Package 中添加 TrackBusinessSDKKit 与 TrackBusinessSDKSwiftUI 产品。在根视图使用 .trackBusinessSDK(_:) 注入环境值:

swift
import SwiftUI
import TrackBusinessSDKKit
import TrackBusinessSDKSwiftUI

struct AppRootView: View {
    let sdk: TrackBusinessSDK

    var body: some View {
        CheckoutView()
            .trackBusinessSDK(sdk)
    }
}

点击与语义交互 ​

下例的语义交互修饰符从 SwiftUI 环境中读取 SDK,未提供时不采集;Button action 包装器显式传入实例。页面生命周期和曝光修饰符也需要显式传入实例。

swift
// 1. Button 通过自己的 action 路径记录标准控件点击 ($app_click)
Button(action: trackBusinessAppClickAction(
    using: sdk,
    action: "checkout_submit",
    targetTag: "button",
    captureKey: "ios:swiftui:checkout-submit",
    perform: submitOrder
)) {
    Text("提交订单")
}

// 2. 非 Button 的显式语义目标可使用 View Modifier ($sdk_interaction)
Image(systemName: "arrow.forward")
    .trackBusinessSemanticInteraction(
        action: "open_help",
        targetTag: "navigation_affordance",
        captureKey: "ios:swiftui:help-entry"
    )

Button 应通过 trackBusinessAppClickAction 或 trackBusinessSemanticInteractionAction 包装其 action 闭包。SwiftUI Button 的 action 是触摸、辅助功能和 UI 自动化共享的激活入口;通用 View 手势 Modifier 只用于非 Button 目标。包装器先尝试记录 SDK 事件,再执行宿主 action;SDK 尚未就绪时仍执行宿主 action。

页面生命周期与曝光 ​

swift
// 页面进入与离开闭环 ($screen + $page_leave)
CheckoutView()
    .trackBusinessScreenLifecycle(
        "checkout",
        sdk: sdk
    )

// 组件几何可见性曝光 ($sdk_exposure)
PromoCard()
    .trackBusinessExposure(
        "home_banner_01",
        sdk: sdk,
        minDurationMs: 500,
        minVisibleRatio: 0.5
    )

SwiftUI 曝光按视图与窗口中的可见矩形估算,不识别其他视图的遮挡或任意形状遮罩。滚动列表和自定义容器请在实际界面验证。建议在授权并启用插件后绑定;授权前已可见的视图不保证自动补记曝光。


3. iOS UIKit 兼容适配 (显式 Binder) ​

在 UIKit 应用中,引入 TrackBusinessSDKUIKit 模块,使用 Binder 记录生命周期与交互事件。

控件点击绑定 (InteractionBinder) ​

由宿主持有 Binder 实例,显式监听目标 UIControl:

swift
import TrackBusinessSDKUIKit

// 定义在 ViewController 中,确保绑定器在控件可用期间被持有。
private var payBinder: TrackBusinessUIKitInteractionBinder?

// 在控件和 SDK 都准备好后调用一次。
payBinder = TrackBusinessUIKitInteractionBinder(
    control: payButton,
    sdk: sdk,
    action: "submit",
    targetTag: "checkout_pay",
    trackId: "checkout-pay",
    captureKey: "ios:uikit:checkout-pay",
    elementType: "UIButton",
    screenName: "Checkout"
)

// 视图销毁或解绑时
payBinder?.close()
payBinder = nil

ViewController 页面生命周期 ​

在 viewDidAppear 与 viewDidDisappear 中显式转发事件,由单调时钟自动配对计算时长:

swift
class CheckoutViewController: UIViewController {
    // 展示该控制器前由宿主注入已初始化的 SDK 实例。
    var sdk: TrackBusinessSDK?
    private var screenBinder: TrackBusinessUIKitScreenLifecycleBinder?

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let sdk else { return }
        screenBinder = TrackBusinessUIKitScreenLifecycleBinder(
            sdk: sdk,
            screenName: "Checkout"
        )
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        screenBinder?.appear()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        screenBinder?.disappear(reason: "screen_change")
    }
}

视图与列表曝光 ​

UIKit 可使用 ViewExposureTracker 绑定单个 View,使用 UICollectionViewExposureBinder 或 UITableViewExposureBinder 绑定列表。将监听器作为控制器属性持有,在视图退出或复用时解除绑定;完整示例见 iOS API 的元素曝光说明。

UIKit 曝光会考虑窗口和父视图的裁剪,但不识别其他视图的遮挡或任意形状遮罩。视图移动到新的父容器后请重新绑定;父视图显隐变化可调用 evaluate() 重新检查。


4. 行为准则与安全边界 ​

  1. action、targetTag、exposureId 和页面名称使用稳定、非敏感的标识。
  2. 同一点击、页面进入或离开只选择一种记录方式,避免显式绑定与手工调用重复。
  3. UI 扩展只记录已绑定的目标;授权、入队和上传仍由核心 SDK 处理。

5. 验收清单 ​

集成后检查以下内容:

  • [ ] 版本对齐:核心 SDK 与 UI 扩展库使用相同的对应平台版本;
  • [ ] 隐私授权前置:在用户未授予对应 Consent 等级(如未同意隐私协议)时,修饰符保持静默,不产生事件;
  • [ ] 插件开关验证:通过宿主配置禁用对应插件后,已绑定目标不再产生该插件事件;
  • [ ] 身份隔离验证:调用 reset() 或 switchUser() 后,后续产生的 UI 事件正确附着新身份上下文;
  • [ ] 离线可靠性:处于飞行模式或弱网环境下触发的 UI 事件,应检查实际存储后端和事件接纳结果;持久后端可用时验证恢复,网络恢复后核对有效 ACK;
  • [ ] 诊断数据合规:sdk.exportDiagnostics() 导出运行元数据与处理后的诊断;分享前检查宿主自定义标识和错误摘要。

iOS 采集排查 ​

触发一次已绑定的操作后,按顺序检查:

  1. 确认 SDK 实例已创建、自动采集策略允许该操作,且 Consent 已达到对应能力要求。UIKit 还需确认 Binder 已被宿主持有,且没有提前调用 close()。
  2. 查看 sdk.getDiagnosticsTimeline(limit: 50) 中该操作的近期记录。若使用了稳定的 captureKey,可用它定位;dropped 的 code 提供准入失败原因。没有记录并不能证明已经采集:绑定可能未触发、插件可能未通过准入,或记录已超出有界时间线。曝光还可用 sdk.getPluginHealth() 检查 ios-exposure 状态及 blockedReason。
  3. 查看 sdk.getHealthSnapshot() 的 queueSize、pendingWrites、lastDropReason、uploadFailures 和 lastError。这些是实例级状态,并非某个操作的专属结果;异步写入尚未完成时,队列大小可能尚未变化。
  4. 联调时调用 try await sdk.flush(),并在接收端按事件及批次核对接收结果。时间线中的本地接纳和调用 flush() 都不能单独证明业务事件已被接收端处理。

SwiftUI 曝光的 onExposed 表示可见性阈值已触发,并尝试向 SDK 提交;它不表示事件已入队或被接收端确认。captureKey、事件名和自定义属性应使用非敏感值;分享诊断报告前请检查其中的宿主自定义元数据。诊断接口示例见 iOS API。