Skip to content
AI 文档索引 ↗

Android 基础 API ​

kotlin
val sdk = TrackBusinessSDK.createAsync(applicationContext, endpoint, projectKey)
sdk.track("checkout_started")
sdk.identify("user_1001")
sdk.screen("Checkout")

Android SDK 使用 Kotlin 实现,也提供 Java 宿主可调用的公开 API。

TIP

获取核心 AAR 和扩展模块的离线集成包,请前往 Android SDK 下载页。快速接入指南请参考 Android 快速起步。


常用接口 ​

模块类别核心入口 (Kotlin)核心入口 (Java)
初始化TrackBusinessSDK.createAsync(...)TrackBusinessSDK.createAsync(..., callback)
配置构建TrackBusinessConfig(...)TrackBusinessConfig.builder()...build()
业务埋点sdk.track(...) / sdk.group(...)sdk.track(...) / sdk.group(...)
位置快照trackWithLocation / grantLocationConsent / revokeLocationConsentMap + LocationSnapshot + 撤销回调;完整语义
手动 Flushsdk.flush()sdk.flush()
身份管理identify / clearUser / switchUser / signOut同左
身份快照val id = sdk.identityIdentityState id = sdk.getIdentity()
用户属性sdk.setUserProperties(...) / profileIncrement同左 (强类型 Map)
健康诊断sdk.getHealthSnapshot() / exportDiagnostics()同左

Kotlin 和 Java 均可使用公开事件接口。Java 宿主使用 Builder 配置和回调初始化。队列写入由 SDK 异步处理;track() 返回不代表事件已写入磁盘或被接收端确认。验证方法见数据校验。


1. SDK 初始化与生命周期 ​

基础初始化 (TrackBusinessSDK.createAsync) ​

在应用启动时异步创建 SDK,并在应用范围内复用实例:

kotlin
val sdk = TrackBusinessSDK.createAsync(
    context = applicationContext,
    endpoint = "https://collector.example.com/v1/events/batch",
    projectKey = "android-public-project-key",
)
sdk.track("checkout_started", mapOf("checkoutId" to "chk_01"))
java
TrackBusinessSDK.createAsync(
    getApplicationContext(),
    "https://collector.example.com/v1/events/batch",
    "android-public-project-key",
    TrackBusinessConfig.builder().build(),
    (sdk, error) -> {
        if (error != null) {
            android.util.Log.w("Analytics", "SDK initialization failed");
            return;
        }
        sdk.track("checkout_started", java.util.Collections.singletonMap("checkoutId", "chk_01"));
    }
);

初始化失败时,Kotlin 抛出异常,Java 回调返回错误。同步 create() 仍可用于后台线程。完整宿主示例见快速接入。

配置参数项 (TrackBusinessConfig) ​

下表列出常用配置;Kotlin 使用命名参数,Java 使用同名 Builder 方法。

字段类型默认值作用与范围
storageNamespaceString?null默认沿用单实例存储。多项目/环境共存时显式设置不同命名空间
initialConsentConsentDecisiongranted("MINIMAL")pending()、granted(level) 或 denied(),见授权与隐私
flushAtInt20达到队列条数阈值后调度发送;限制为 1–100
flushIntervalSecondsLong30前台发送周期,正值限制为 5–120 秒;非正值关闭定时器
flushOnBackgroundBooleantrue退后台时安排发送,系统可能延迟执行
flushOnNetworkReachableBooleantrue网络恢复时安排发送
flushOnIdentityChangeBooleantrue身份变化时安排发送
trackAppLifecycleBooleantrue注册应用生命周期采集,仍受事件授权门控
trackApplicationVersionBooleantrue授权允许时检测安装/更新版本变化
activityLifecycleOptionsActivityLifecycleIntegration.OptionsOptions()Activity 页面与冷启动深链接选项,按宿主场景配置
identitySyncTransportIdentitySyncTransport?null可选身份同步适配器,需配套服务
deviceIdentifierReaderDeviceIdentifierReader?null可选外部标识读取器,需宿主授权与依赖
beforeSendEventInterceptor?null入队前处理事件属性或拒绝事件
eventEnrichersList<EventEnricher>emptyList()命名业务属性增强;Java 使用 Builder 同名方法和回调
policySDKPolicySDKPolicy()插件准入及白名单;Java Builder 提供 pluginEnabled / pluginAllowlist 便捷方法
integrationWarningListenerIntegrationWarningListener?null接收事件拒绝原因等元数据,不包含属性、身份值或凭据

配置在创建实例时确定。队列上限、加密等设置见包内 TrackBusinessConfig。默认敏感字段保护始终执行;自定义业务字段仍须由宿主审查。

命名业务属性增强 ​

支持 event_enrichment 的 SDK 包可在普通及远程策略工厂的 TrackBusinessConfig 中安装增强器。Java 使用 EventEnricher.create(name, handler);回调接收只读业务字段和事件元数据,返回 EventEnrichmentPatch.properties(map) 或 EventEnrichmentPatch.of(context, properties)。返回 null 表示不修改,不会删除事件。

配置列表按顺序处理,同名增强器保留第一个,同名业务属性由后者覆盖。回调必须是短时同步操作;SDK 隔离异常和超限补丁,但不能抢占同步死循环。保留的身份、Session、eventId 和 SDK Context 不能改写。默认补丁预算 16,384 bytes,最终仍执行隐私和事件计划检查。

注册不提升 Consent;默认 MINIMAL 不满足示例中的 STANDARD。完整 Java/Kotlin 注册、授权、触发及释放流程见客户端插件与能力接入。单个拦截/过滤需求仍可使用 beforeSend。

异步远程策略工厂 (createWithRemotePolicy) ​

当服务端已经部署并验证 GET /v1/sdk/policy,且宿主主动选择远程控制面时,可使用异步工厂下发插件开关、采样率或 Tracking Plan(仅 Kotlin 协程)。它不是基础初始化或上传的前置条件;没有该接口时使用普通 create 和可选的本地 Tracking Plan。Android 只在 SDK 创建前加载远程策略,运行期间不会热刷新;策略变化需由宿主重新创建 SDK 后生效。

kotlin
val resolver = RemotePolicyResolver(
    provider = UrlConnectionRemotePolicyProvider(
        URL("https://collector.example.com/v1/events/batch"),
        "android-public-project-key",
    ),
    cache = hostPolicyCache,
    verifier = policyVerifier,
    requireSignature = true, // 生产环境强制验签
)

val sdk = TrackBusinessSDK.createWithRemotePolicy(
    context = applicationContext,
    endpoint = "https://collector.example.com/v1/events/batch",
    projectKey = "android-public-project-key",
    config = TrackBusinessConfig(initialConsent = ConsentDecision.granted("STANDARD")),
    resolver = resolver,
)

WARNING

远程策略请求涉及网络通信,调用前宿主必须确认已处于权威的 GRANTED 授权状态;若处于 PENDING 或 DENIED,工厂将直接以 CONSENT_REQUIRED_BEFORE_REMOTE_POLICY 失败中断。

实例释放 (close) ​

kotlin
sdk.close()
  • 停止实例资源:停止前台定时调度和 Activity 生命周期监听;已提交给 Android 系统的 WorkManager 任务仍按系统调度执行,close() 不表示取消这些任务;
  • 数据保留:不会清空本地 Durable Outbox 或加密 SharedPreferences 中的身份状态;使用相同存储作用域重新 create 后可恢复待处理状态。

2. 身份识别与生命周期 (Identity) ​

核心身份流转 API ​

kotlin
// 登录后绑定业务身份
sdk.identify("user_10086")

// 退出登录
sdk.signOut()

// 只清除当前用户,保留匿名身份
sdk.clearUser()

// 切换账号
sdk.switchUser("user_10087")

// 绑定其他业务标识
sdk.bindIdentity("crm_id", "crm_8899")

// 重置身份和会话
sdk.reset()

// 获取当前身份
val identity = sdk.identity // Java: sdk.getIdentity()

reset() 会重置用户与会话身份;安装身份按 SDK 语义保留。切换账号请使用 switchUser()。

CAUTION

当 SDK 当前已绑定某一 userId 时,直接调用 identify("other_user") 将抛出 IDENTITY_USER_SWITCH_REQUIRES_RESET 异常以防止身份串联。请在切换账号时明确使用 switchUser 或先执行 signOut。


3. 事件采集 ​

SDK 在事件属性进入隐私过滤前限制嵌套层数、值数量和文本总量。采集属性或事件拦截器返回的属性超过处理预算时,SDK 丢弃该事件,诊断记录 invalid_event(event_input_budget)。后续事件继续处理。

业务事件与群组 (track / group) ​

kotlin
// 1. 自定义事件(支持嵌套 Map 与标准类型)
sdk.track("checkout_started", mapOf(
    "checkoutId" to "chk_9981",
    "estimatedTotal" to 199.0,
    "isVip" to true,
))

// 2. B2B 群组归属关联
sdk.group("org_alibaba", mapOf(
    "role" to "admin",
    "department" to "tech",
))
java
Map<String, Object> props = new HashMap<>();
props.put("checkoutId", "chk_9981");
props.put("estimatedTotal", 199.0);
props.put("isVip", true);
sdk.track("checkout_started", props);

sdk.group("org_alibaba", Collections.singletonMap("role", "admin"));

设置全局属性或会话属性时,若更新后的总量超出 SDK 处理预算,本次更新会被拒绝,已有属性保持原值。健康快照记录 event_input_budget。

beforeSend、事件增强和动态公共属性回调在发起采集的线程上同步执行。回调应只做短小的内存操作,避免网络请求、磁盘读取和跨线程等待。回调执行期间,SDK 会拒收其他线程提交的事件,诊断记录 before_send_busy。回调结束后恢复正常准入。

4. 属性管理 (Super & User Properties) ​

SDK 属性合并优先级遵循:Global 超级属性 $\rightarrow$ Session 属性 $\rightarrow$ 动态属性 $\rightarrow$ 事件显式属性(后者覆盖前者)。

Session Properties ​

生命周期与当前会话(Session)绑定,会话过期或调用 signOut() / reset() 时自动清空:

kotlin
sdk.setSessionProperties(mapOf("campaign" to "summer_2026"))
sdk.removeSessionProperties(listOf("campaign"))

Dynamic Super Properties ​

在事件生成时刻动态求值(必须保持高执行效率与同步非阻塞):

kotlin
sdk.registerDynamicSuperProperties {
    mapOf(
        "currentTab" to currentSelectedTab,
        "cartCount" to getCartBadgeCount().toString(),
    )
}
sdk.clearDynamicSuperProperties()
java
sdk.registerDynamicSuperProperties(() -> {
    Map<String, String> dynamic = new HashMap<>();
    dynamic.put("currentTab", "home");
    return dynamic;
});

用户 Profile 操作 ​

Profile API 提交操作事件,本地缓存与服务端画像可能不同步。服务端是否执行操作及如何保证并发更新,由部署实现决定。

  • setUserPropertyOnce 由服务端判断目标属性是否已存在;profileAppend 不保证数组去重或并发原子性。
  • unsetUserProperties 可指定未出现在本地缓存中的属性名。clearUserProperties 按本地已知键产生 $profile_unset,针对本地已知属性,不表示删除所有服务端数据。
  • 未授权、opt-out 或关闭实例后不执行 Profile 修改,也不在恢复授权后补发。禁止持久存储时仅维护内存缓存。
  • 缓存读写问题通过诊断查看;操作返回不代表服务端更新完成。隐私擦除使用 denyConsent 或 clearLocalData,检查完成结果,清理失败时不能恢复采集。
kotlin
// 1. 覆盖设置属性
sdk.setUserProperties(mapOf("membership" to "gold", "level" to "5"))

// 2. 仅在首次设置(服务端已有时忽略)
sdk.setUserPropertyOnce(mapOf("firstChannel" to "google_play"))

// 3. 数值累加
sdk.profileIncrement(mapOf("loginCount" to 1.0, "points" to 50.0))

// 4. 数组追加(单次最多 100 项,每个字符串最多 256 字符)
sdk.profileAppend(mapOf("tags" to listOf("sports", "music")))

// 5. 删除指定属性,或清理本地已知的画像属性
sdk.unsetUserProperties(listOf("legacy_flag"))
sdk.clearUserProperties()

// 6. 获取本地 Profile 缓存快照
val profileMap = sdk.getUserPropertiesMap()

业务 Item 维度属性 ​

kotlin
// 设置商品/物料属性
sdk.itemSet("product", "sku_10086", mapOf("title" to "SDK 入门手册", "price" to "59.0"))

// 删除指定物料
sdk.itemDelete("product", "sku_10086")

5. 存储与发送 ​

当前策略关闭持久存储时,获授权事件使用当前进程的内存队列;进程结束后未发送事件无法恢复。

手动刷新调度 (flush) ​

kotlin
sdk.flush()
  • 非阻塞调用:立即触发内部调度器执行出队与网络上报,但不会在调用者线程阻塞等待 I/O;
  • 使用建议:SDK 按配置合批发送,默认阈值为 20 条、前台周期为 30 秒,并在切后台时尝试发送;无需在每次 track() 后调用 flush()。

6. 调试、诊断与监控 (Diagnostics) ​

kotlin
// 1. 获取完整健康快照
val health = sdk.getHealthSnapshot()

// 2. 导出诊断报告;分享前检查事件 ID、事件名等自定义关联字段
val diagnosticsReport = sdk.exportDiagnostics()

// 3. 获取有界诊断时间线事件(最近 50 条生命周期变更)
val timeline = sdk.getDiagnosticsTimeline(50)

// 4. 查看最近入队的事件元数据(不含属性明细)
val recentEvents = sdk.getRecentEvents()

// 5. 开发构建可在 TrackBusinessConfig 注册接入提示
val config = TrackBusinessConfig(
    integrationWarningListener = IntegrationWarningListener { warning ->
        Log.w("TrackBusiness", "${warning.code}: ${warning.eventName}")
    },
)

7. 屏幕浏览与原生交互 ​

页面停留与加载耗时 ​

kotlin
// 1. 显式声明页面浏览 ($screen)
sdk.screen("CheckoutScreen", mapOf("source" to "cart"))

// 2. 页面加载完成耗时 ($page_load)
sdk.recordPageLoad(
    pageViewId = "pv_home_01",
    durationMs = 380L,
    screenName = "HomeActivity",
)

控件点击与语义交互 (recordAppClick) ​

用于在 Native 点击监听回调中精确上报,无需插桩 View 树:

kotlin
sdk.recordAppClick(
    action = "checkout_submit",
    targetTag = "button",
    trackId = "btn-submit-order",
    captureKey = "android:view:checkout-submit", // 稳定的采集标识
    elementType = "Button",
    screenName = "CheckoutScreen",
)

8. 平台原生能力与生命周期 ​

通过 ActivityLifecycleIntegration.Options 精确配置:

kotlin
val config = TrackBusinessConfig(
    initialConsent = ConsentDecision.granted("STANDARD"),
    activityLifecycleOptions = ActivityLifecycleIntegration.Options(
        allowedActivityNames = setOf(MainActivity::class.java.name, CheckoutActivity::class.java.name),
        trackScreenLeaves = true,               // 开启页面停留自动统计 ($page_leave)
        screenNameResolver = { activity -> activity.localClassName },
        coldStartDeepLinkActivityNames = setOf(MainActivity::class.java.name),
        coldStartDeepLinkHosts = setOf("links.example.com"),
    ),
)
  • 推荐(ComponentActivity):引入 track-business-sdk-android-lifecycle 自动监听 onNewIntent:
    kotlin
    val deepLinkBinding = ComponentActivityDeepLinkIntegration.install(this, sdk)
  • 原生传统 Activity:在 onNewIntent 中显式转交:
    kotlin
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        sdk.recordDeepLinkIntent(intent)
    }

install() 将 ACTION_VIEW 热启动 Intent 转交 SDK,不应用冷启动 Host 白名单。需要限制目标时,应由宿主检查 scheme 与 host 后手工调用 recordDeepLinkIntent;两种转发方式选择其一。

渠道来源快照 ​

kotlin
sdk.recordAttribution(mapOf("utm_source" to "newsletter", "utm_campaign" to "spring"))
sdk.track("order_created", mapOf("orderType" to "subscription"))

仅需要已有首次/最近 UTM 来源时使用 recordAttribution。需要当前入口、会话入口和排除返回域时,见下方 recordAcquisition。

入口与营销触点(recordAcquisition,可选) ​

需要区分当前入口、会话入口和最近有效触点时使用。复用已初始化的 SDK,在宿主确认 STANDARD 授权及新入口事实之后调用:

kotlin
import com.trackbusiness.sdk.models.AcquisitionInput

sdk.recordAcquisition(
    AcquisitionInput.Builder(mapOf("utm_source" to "newsletter"))
        .landingUrl("https://shop.example.com/landing")
        .build()
)
sdk.track("entry_viewed", mapOf("surface" to "campaign_landing"))

新输入被本地接受后,后续事件可携带 context.campaign.entry、sessionEntry 和 latestTouch。它们表示入口事实,不计算订单归因。原接口 recordAttribution 继续维护已有 UTM 视图,不要为同一入口重复调用两个接口。

应用安装与版本更新 ​

kotlin
// 1. 显式记录安装事件(内部去重,重复调用不产生重复事件)
sdk.recordApplicationInstall(mapOf("installer" to "google_play"))

// 2. 显式记录版本升级
sdk.recordApplicationUpdate("2.1.0", mapOf("currentBuild" to "210"))

异常与性能诊断上报 ​

kotlin
// 1. 非致命业务异常;使用受控分类,不传原始异常内容
sdk.recordNonFatalError("PaymentGatewayTimeout")

// 2. 启动耗时与网络指标
sdk.recordStartupPerformance(durationMs = 450L)
sdk.recordNetworkPerformance(durationMs = 120L, resourceCount = 3, transferBytes = 8192L, failedCount = 0)

9. 元素曝光 (Exposure) ​

手动曝光 API (recordExposure) ​

记录 $sdk_exposure 事件:

kotlin
sdk.recordExposure(
    exposureId = "item_card_998",
    durationMs = 800L,
    visibleRatio = 0.75,
)
java
sdk.recordExposure("item_card_998", 800L, 0.75);

曝光事件支持附加业务属性;自定义属性名不要以 $ 开头,并遵循隐私过滤及事件大小限制。请确认接收服务支持对应事件契约。

原生 View 与 RecyclerView 绑定 ​

请由宿主在视图生命周期内持有返回的绑定对象,并在不再使用时调用 detach() 或 unbind()。使用列表绑定时,宿主模块还需声明自己的 androidx.recyclerview:recyclerview 依赖。

kotlin
// 1. 普通 View 扩展
val cardExposure = binding.recommendCard.trackBusinessExposure(
    sdk = sdk,
    exposureId = "card_recommend",
    minDurationMs = 500L,
    minVisibleRatio = 0.5,
)

// 2. RecyclerView 列表滚动曝光
val feedExposure = RecyclerViewExposureBinder(sdk) { position -> "feed_item_$position" }
    .minDurationMs(500L)
    .minVisibleRatio(0.5)
    .bind(binding.feedRecyclerView)
java
// Java 适配器
ViewExposureTracker cardExposure = ViewExposureTracker.attach(
    binding.recommendCard,
    sdk,
    "card_recommend",
    ViewExposureOptions.builder().minDurationMs(500L).minVisibleRatio(0.5).build()
);

10. 外部设备标识读取 (GAID / OAID) ​

外部标识用于辅助归因,SDK 默认不读取,也不将其作为业务主身份。

GMS 与 MSA 适配器注入 ​

按实际分发环境选择一个读取器;下例使用按顺序尝试的组合。宿主需自行提供相应第三方依赖、MSA 证书及适用授权。用户限制广告标识时,组合不会继续回退以绕过限制。

kotlin
val sdk = TrackBusinessSDK.create(
    context = applicationContext,
    endpoint = endpoint,
    projectKey = projectKey,
    config = TrackBusinessConfig.Builder()
        // 示例:Google Play Services 优先,MSA OAID 作为广告用途的后备。
        .deviceIdentifierReader(FallbackDeviceIdentifierReader(listOf(
            GooglePlayServicesDeviceIdentifierReader(applicationContext),
            MsaOaidDeviceIdentifierReader(applicationContext, "msa-cert.pem"),
        )))
        .build(),
)

// 从协程调用;选择与宿主授权一致的用途。
suspend fun readMeasurementIdentifier(sdk: TrackBusinessSDK) =
    sdk.readDeviceIdentifier(DeviceIdentifierPurpose.MEASUREMENT)

只使用 Google Play Services 时,可将上面的组合替换成 GooglePlayServicesDeviceIdentifierReader(applicationContext)。MSA OAID 只支持广告用途;读取失败或无可用标识时应允许业务继续,不要将标识原文写入日志。


11. 预置上下文规范 (NativeContextCollector) ​

获准采集的事件可包含以下环境上下文。具体字段取决于设备和系统是否提供相关信息:

字段命名空间包含属性采集规则与说明
context.devicemanufacturer, model, osVersion, screenWidth, screenHeight, screenScale, orientation, locale, timezone, type屏幕尺寸以 dp 呈现;设备类型归纳为 phone / tablet / tv;SDK 不读取 IMEI、MAC 或硬件序列号。
context.appid, name, version, build分别取宿主 App 的包名、应用展示名、versionName 与 versionCode。
context.networkonline, connectionType网络连通性与类型(wifi / cellular / ethernet / 5g / 4g 等);不采集运营商私有标识或 WiFi SSID。

事件的网络属性来自后台刷新的一秒内快照。快照未就绪或过期时,本次事件省略网络属性。

应用版本与构建观测 ​

kotlin
sdk.recordApplicationUpdate("2.1.0", mapOf("currentBuild" to "210"))

SDK 会读取应用版本及构建号。显式调用时可通过 currentBuild 提供构建号。事件上的 $previous_version、$current_version、$previous_build、$current_build 和 $change_type 由 SDK 确定,不能通过自定义属性覆盖。

首次观测只建立基线。后续版本变化生成 version_changed,同版本的已知构建号变化生成 build_changed。SDK 不通过版本字符串排序判断升级或降级;缺失构建号不填零。内存模式下的基线不保证跨进程保留。

网关探测 ​

持久实例可调用 probeGateway(timeoutMs)(Kotlin)或 probeGateway(timeoutMs, callback)(Java)探测已配置事件 Endpoint 所属 Gateway 的健康状态。静态 TrackBusinessSDK.DurableClient.probeEndpoint 用于指定事件 Endpoint;两者均请求同一 Gateway origin 下的 GET /health,不会对 POST /v1/events/batch 写入路由发送 GET。探测不采集事件,也不证明 Collector 已接受业务数据;事件是否被接受仍以批量上传的 ACK 为准。

GatewayProbeResult.errorMessage 使用固定的探测错误描述,不返回原始网络异常文本;宿主需要结合 httpStatusCode 判断结果。Java 静态探测最多通知一次,宿主回调异常会被隔离;协程取消保留取消语义,不伪装成网络故障。

实例所有权与控制记录提交 ​

同一进程中,相同 endpoint、projectKey 和 storageNamespace 只能持有一个活动 Runtime。重复创建会抛出 SDK_SCOPE_ALREADY_OWNED;宿主应复用已有实例,或等待 close() 完成后再创建。不同存储作用域可独立使用。SDK 不提供 SharedPreferences 跨进程事务保证。

SDK 计时器 startTimer/endTimer 的输出字段为 properties.$timer_duration_ms(非负毫秒数),宿主 durationMs 属性不被覆盖。计时事件的严格 Tracking Plan 需声明该测量字段。

有界投递验证与 Java 结构化属性 ​

kotlin
// 显式开发期操作;不替换原有 flush() 调度。
val report = sdk.verifyDelivery(timeoutMs = 3000)
java
Map<String, Object> properties = new HashMap<>();
properties.put("amount", 12.5);
properties.put("paid", true);
properties.put("items", Arrays.asList("one", "two"));
sdk.trackProperties("checkout", properties);
sdk.verifyDelivery(3000L, (report, error) -> {
    if (report != null && "acknowledged".equals(report.getStatus())) {
        // 本次非空固定范围均收到合法整批 ACK。
    }
});

trackProperties 接受 String、Boolean、Byte/Short/Integer/Long、有限 Float/Double、null、嵌套 Map 和 List;它不调用任意对象的 toString()。转换边界为最多 1000 个节点、8 层、键长 128、字符串长 65536;超限、循环引用、不支持的类型或非有限数值会本地拒收并记入 Diagnostics。属性仍通过现有事件、隐私和 Tracking Plan 校验,不绕过原有限制。原有 track(String, Map<String,String>) 保持兼容。

verifyDelivery 返回固定范围的准确确认/拒收/未知数量;空队列、超时、调度成功都不是 ACK。Java 回调不会阻塞调用线程;Kotlin suspend 入口对 Java 隐藏。详见可靠投递。