手套箱 05 · 埋点体系
本篇回答:开箱相关埋点有哪些、每个字段的语义与来源代码行、物理按键(Meego 5458412)如何扩展 source 维度。
基线声明:行号默认以 dev 基线工作区
/home/zbc/car/fuben4_micarsettings/MiCarSettings-5458412(origin/dev @930be9145)为准;出货对照/home/zbc/car/pack_a17_cn_20260923;平台源码/home/zbc/pangu/xcd_xring_a17_fermi/vendor/micar。本文全部 file:line 均已按基线逐个打开核实。埋点引入的权威参考 change 研究笔记:参考change-368053.md(本库外,路径以 Meego 目录为准)。
1. 事件清单与字段语义
手套箱相关埋点共 3 个,全部封装在 VehicleControlReporter(base/settingsBaseLib/src/main/java/com/android/micar/settings/track/VehicleControlReporter.kt),事件 ID(TIP)与参数 key 常量在 TrackConstants.java。
1.1 打开手套箱 TIP_OPEN_GLOVE_BOX
| 维度 | 值 | 来源 |
|---|---|---|
| 事件 ID | 1147.1.0.1.54662 | TrackConstants.java:308-309 |
| 事件类型 | Event.CLICK(“click”) | VehicleControlReporter.kt:498;枚举定义 Event.kt:9 |
| 参数 1 | type(key=KEY_GLOVE_BOX_OPEN_TYPE) | key 定义 TrackConstants.java:557-558 |
| 参数 2 | tab_instance_id(key=KEY_TAB_INSTANCE_ID) | key 定义 TrackConstants.java:535-536 |
| 唯一调用点 | GloveBoxController.kt:237(reportGloveBoxOpenTrack 内) | 全仓 grep 实证,无第二调用点 |
type 取值(VehicleControlReporter.kt:104-108,TrackValues 内):
/** 打开方式-无密码 */
const val GLOVE_BOX_OPEN_NO_PASSWORD = 1
/** 打开方式-输密码 */
const val GLOVE_BOX_OPEN_WITH_PASSWORD = 2语义:type 是”密码维度”——本次开箱是否经过密码/账号验证,不是”触发来源”。reportOpenGloveBox(type) 自身不推断来源,只把调用者传入的 type 原样写入参数(VehicleControlReporter.kt:495-501)。
调用侧的三条业务路径分别写入:
| 业务路径 | 传入 type | 代码位置 |
|---|---|---|
| 无密码直开 | GLOVE_BOX_OPEN_NO_PASSWORD(默认参数) | GloveBoxController.kt:222(默认形参)+ :268(无密码分支调 openGloveBox()) |
| 密码验证通过 | GLOVE_BOX_OPEN_WITH_PASSWORD | GloveBoxController.kt:201 |
| 密码错误达上限→车主账号验证通过 | GLOVE_BOX_OPEN_WITH_PASSWORD | GloveBoxController.kt:215 |
注意:账号验证兜底路径开箱也记为 type=2——type=2 的准确含义是”经过某种验证后打开”,不区分密码验证还是账号验证。
1.2 成功忘记手套箱密码 TIP_GLOVE_BOX_FORGOT_PASSWORD_SUCCESS
| 维度 | 值 | 来源 |
|---|---|---|
| 事件 ID | 1147.0.0.0.54663 | TrackConstants.java:310-311 |
| 事件类型 | Event.EXECUTE(“execute”) | VehicleControlReporter.kt:513;枚举定义 Event.kt:12 |
| 参数 | 仅 tab_instance_id,无业务参数 | VehicleControlReporter.kt:514 |
| 调用点 1 | GloveBoxController.kt:248 → 触发于 :210-219 showAccountVerifyPage 成功回调(锁定→账号验证→清零错误次数并开箱) | — |
| 调用点 2 | GloveBoxSettingsDialogActivity.kt:262 → 触发于 :193-205 showAccountVerify 成功回调(设置页内锁定→账号验证→清零+清除密码) | — |
两个调用点是两条不同业务路径各报一次,不是同一流程的重复上报:Controller 侧验证成功后直接开箱,DialogActivity 侧验证成功后清除密码留在设置页。368053 评审(PS4 Skill Review)曾要求确认此处不重复,结论是流程分叉、各自一次(见 参考change-368053.md「评审关注点」第 2 条)。
1.3 附:密码操作 TIP_GLOVE_BOX_PASSWORD_OPERATION
与开箱不同事件,但同属手套箱埋点族,防止与开箱 type 混用:
| 维度 | 值 | 来源 |
|---|---|---|
| 事件 ID | 1147.0.0.0.54664 | TrackConstants.java:312-313 |
| 事件类型 | Event.EXECUTE | VehicleControlReporter.kt:528 |
| 参数 | type(key=KEY_GLOVE_BOX_PASSWORD_TYPE):1=设置密码成功 / 2=关闭密码成功 / 3=修改密码成功 | key TrackConstants.java:559-560;枚举 VehicleControlReporter.kt:109-114 |
| 调用点 | GloveBoxSettingsDialogActivity.kt:160(SET)/:179(CLEAR)/:130(CHANGE) | — |
key 冲突警示:开箱的 KEY_GLOVE_BOX_OPEN_TYPE 与密码操作的 KEY_GLOVE_BOX_PASSWORD_TYPE 字符串值都是 "type"(TrackConstants.java:557-560),后视镜/天幕等事件也复用 "type"/"status"。每个 TIP 的参数 HashMap 独立、上报时不会互相覆盖,但数据分析侧按 key 聚合时语义会混合,新增字段时优先选不复用的 key 名。
1.4 口径边界:MIS 语音/远程开箱不产生 54662
GloveBoxNavigationHandler.openGloveBox()(GloveBoxNavigationHandler.kt:125-131)直接调 setCarProperty 写开箱属性,没有任何埋点调用。即语音/远程入口开箱不上报 TIP_OPEN_GLOVE_BOX,数据分析口径上 54662 只覆盖设置页软按键路径。做物理按键埋点验收时同样要明确:新来源是挂进 Controller 统一上报点,还是另起炉灶——前者才能复用现有口径。
2. type 与 source 的正交语义
现状:手套箱事件当前不带 source 字段。reportOpenGloveBox 只写 type + tab_instance_id(VehicleControlReporter.kt:499-500)。
但 source 维度的基础设施已经存在:
KEY_SOURCE = "source"常量:TrackConstants.java:533-534(VehicleControlKey内,注释”访问来源”);- 取值枚举:
SOURCE_SETTINGS = 1(设置点击进入)/SOURCE_OTHER = 2(其他),VehicleControlReporter.kt:38-42; - 使用惯例样例:
reportEnterMirrorAdjust(source)(VehicleControlReporter.kt:143-149)与reportEnterSteeringWheelAdjust(source)(:191-201)——都是”进入调节页面”类事件带 source。
如实声明协议待定状态:后视镜/方向盘的 source 惯例只能证明”Reporter 层支持 source 参数、常量已定义”,不能证明数据平台侧手套箱事件已注册 source 枚举。368053 研究笔记的可复用设计要点第 1 条明确:若 PRD 要复用 type 表达物理来源,必须先取得数据平台枚举定义,不能自行造 type=3;若正交扩展,则新增独立 source 参数。两种路线都需要数据需求方书面确认协议后再动手。
3. 上报链路与时机
3.1 链路
reportOpenGloveBox 内部(VehicleControlReporter.kt:495-505):
fun reportOpenGloveBox(type: Int) {
try {
MLog.i("$TAG reportOpenGloveBox type=$type")
TrackEvent.create(Event.CLICK, VehicleControlTips.TIP_OPEN_GLOVE_BOX)
.addParam(VehicleControlKey.KEY_GLOVE_BOX_OPEN_TYPE, type)
.addParam(VehicleControlKey.KEY_TAB_INSTANCE_ID, getTabInstanceId(TabConstants.VEHICLE_CONTROL))
.report()
} catch (e: Exception) { ... }
}逐层去向:
TrackEvent.report()(TrackEvent.kt:32-37):把 TIP 放进mParams[KEY_TIP](KEY_TIP="tip",TrackConstants.java:379),调SettingsTrack.track(event.eventName, mParams);SettingsTrack.track()(SettingsTrack.kt:72-74):直接转发mOneTrack.track(event, params);- 终点是小米 OneTrack 埋点 SDK(
com.xiaomi.onetrack.OneTrack),实例参数:APP_ID=31000401514(SettingsTrack.kt:12)、PROJECT_ID=xiaomiev-cockpit-intl(:16)、Mode.APP(:35)。即埋点数据进 OneTrack 管道,不在本地落盘。
3.2 时机:写完属性即报,不等回执
统一开箱点(GloveBoxController.kt:222-229):
private fun openGloveBox(openType: Int = TrackValues.GLOVE_BOX_OPEN_NO_PASSWORD) {
MLog.tag(TAG).d("openGloveBox openType=$openType")
setVehicleProperty(
MiCarPropertyIds.DrivingCtrl.GLOVE_BOX,
CommonParams.OpenClose.OPEN
)
reportGloveBoxOpenTrack(openType)
}setVehicleProperty() 之后立即上报,没有等待开箱成功结果或属性回读。所以 54662 的语义是”用户开箱操作/意图已受理”,不能据此宣称”手套箱已物理打开成功”。物理按键需求验收埋点时应按此既有语义对齐,或先与数据需求方确认是否要改为结果事件。
3.3 异常隔离
reportGloveBoxOpenTrack(GloveBoxController.kt:235-241)与 reportOpenGloveBox(VehicleControlReporter.kt:496-504)双层 try-catch,失败只记日志不打断车辆控制。但**“没崩”≠“生效”**——验收必须看 reportOpenGloveBox type=... 日志与 OneTrack 实际数据,不能只验证主流程正常。
4. 演进史(三笔参考 change)
| change | 状态 | 一句话定位 |
|---|---|---|
| 161208(Meego 2350608) | MERGED | 软按键开箱与密码流程主参考:建立”已开启提示→无密码直开→密码校验→错误上限转车主验证→统一 setVehicleProperty 开箱”业务链,物理按键应在此链上游汇入而非旁路。 |
| 165854(N801PROB-34263) | MERGED | 不可用原因 Toast 映射参考:建立 Peripheral.GLOVE_BOX_UNAVL_REASON 读取与”未解防/防玩/代客”文案映射,物理按键不可用提示直接复用(现 GloveBoxController.kt:288-302)。 |
| 368053(Meego 3860784) | MERGED(PS14) | 开箱埋点引入 change:新增 VehicleControlReporter 与 25 个车辆控制事件,reportOpenGloveBox 自此存在;确立”type=密码维度、Reporter 不推断来源、tabInstanceId 由 Reporter 内部取”三原则。 |
三笔均在当前基线祖先链中(各研究笔记已用 git merge-base --is-ancestor 实证)。
5. 物理按键扩展设计(source 穿透)
前置:协议路线先按第 2 节与数据方确认。以下为采用”独立 source 参数”路线时的工程约束,详细方案见 07-物理按键扩展设计.md。
5.1 扩展点选择
唯一合规挂点是 GloveBoxController.openGloveBox(:222-229)——三条软按键路径已在此收敛,物理按键汇入后同样走这里,最终开箱点只报一次。禁止在信号回调里单独打一次”物理按键按下”开箱事件再在开箱点打一次,那会重复计数。
5.2 source 必须不可变捕获穿透异步回调
软按键路径有两段异步:密码验证回调(:198-202)与账号验证回调(:212-218),Kotlin 闭包捕获的是当时局部 val。物理按键扩展时,source 应以同样方式作为不可变参数逐层传递:
- 入口(软按键点击 /
GlvBoxBtnSts物理信号回调)确定 source 后,作为val传入showPasswordCheckDialog/showAccountVerifyPage的回调闭包; - 回调内调
openGloveBox(openType, source); - 禁止在 Controller 上挂
private var currentSource之类共享可变状态:软/物理并发时,后到的触发会覆盖先到的 source,先触发那笔的异步回调完成后会拿错来源上报。
5.3 并发口径:后请求为准
软按键与物理按键几乎同时触发时,业务上以后到的请求覆盖前者(属性写是幂等的 OPEN,埋点以后请求携带的 source 为准),这正是 5.2 禁止共享可变 source 的原因——共享变量天然实现”后请求覆盖”,但代价是先请求的回调跑完后又拿新 source 补报,产生错单。不可变捕获下,后请求只影响自己的回调链,先请求的链若最终也走到 openGloveBox,报的是自己入口的 source;若产品要求严格”只认后请求”,需在入口侧加请求序号/时间戳仲裁,这属于 07 篇的设计内容。
5.4 验收矩阵
| 路径 | 预期 type | 预期 source | 备注 |
|---|---|---|---|
| 软按键×无密码 | 1 | 设置 | 现有行为回归 |
| 软按键×密码/账号验证 | 2 | 设置 | 现有行为回归 |
| 物理×无密码 | 1 | 物理(枚举值待数据方定义) | 新增 |
| 物理×密码/账号验证 | 2 | 物理 | 来源必须穿透两段异步回调 |
| MIS 语音/远程 | (不上报) | — | 口径边界,见 1.4 |
验收以 OneTrack 数据 + reportOpenGloveBox type=... 日志双凭证为准。
6. tabInstanceId 陷阱:物理入口的 UUID 可能是历史值
机制(TabSwitchUuidMap.kt):
put(tabId)(:21-27):每次 tab 切换生成新随机 UUID 存入 map,重复切换覆盖;get(tabId)(:35-38):不存在返回null,存在则返回最近一次切换的 UUID;- Reporter 侧:
getTabInstanceId(TabConstants.VEHICLE_CONTROL)(VehicleControlReporter.kt:21-22),VEHICLE_CONTROL=1(TabConstants.kt:10)。
陷阱:物理按键可发生在非设置页。那一刻 VEHICLE_CONTROL 的 tabInstanceId 有三种可能:从未切换过→null;N 分钟前看过车辆控制页→历史 UUID;刚从该页离开→看似”当前”的 UUID。无论哪种,它都不代表”本次物理触发会话”。
368053 评审曾把”Controller 传空 tabInstanceId”改为”Reporter 内部按 tab 取 UUID”,但那是为页面内触发设计的,物理入口不在该假设内。验收原则:只记录实际值,不解释语义——测试时如实记录每次物理触发上报的 tab_instance_id 是 null 还是某 UUID,交给数据需求方决定口径(接受/置空/另设物理会话 ID),开发侧不擅自”修正”。
7. 埋点调用链
两个入口(软按键点击 / 物理按键信号)汇入共享密码流程,收敛到统一上报点:
flowchart TD A["软按键点击<br/>GloveBoxController.showGloveBoxSwitchClick :261"] --> B{密码状态} P["物理按键信号 GlvBoxBtnSts(待接入)<br/>携带 source=物理,不可变捕获"] --> B B -->|"已打开"| C["Toast 提示,不上报 :263"] B -->|"无密码"| D["openGloveBox(type=1) :222"] B -->|"错误达上限"| E["showAccountVerifyPage :210<br/>账号验证回调捕获 source"] B -->|"已设密码"| F["showPasswordCheckDialog :196<br/>密码回调捕获 source"] F -->|"验证通过"| G["openGloveBox(type=2) :201"] E -->|"验证通过"| H["openGloveBox(type=2) :215<br/>+ reportGloveBoxForgotPasswordTrack :216"] D --> I["setVehicleProperty(GLOVE_BOX, OPEN)<br/>:224-227"] G --> I H --> I I --> J["reportGloveBoxOpenTrack :235<br/>统一上报点,只报一次"] J --> K["VehicleControlReporter.reportOpenGloveBox :495<br/>CLICK + TIP 54662 + type + tab_instance_id"] K --> L["TrackEvent.report → SettingsTrack.track<br/>→ OneTrack SDK(APP_ID 31000401514)"] H -.->|"并行"| M["TIP 54663 EXECUTE<br/>忘记密码成功"]
证据清单
| 文件 | 行号区间 | 说明 |
|---|---|---|
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/VehicleControlReporter.kt | 38-42 | SOURCE_SETTINGS=1/SOURCE_OTHER=2 来源枚举(手套箱未用) |
| 同上 | 104-114 | 手套箱 type 枚举:开箱 1/2、密码操作 1/2/3 |
| 同上 | 143-149, 191-201 | 后视镜/方向盘进入事件带 KEY_SOURCE 的既有惯例 |
| 同上 | 489-505 | reportOpenGloveBox(type):CLICK+54662,原样写 type |
| 同上 | 507-519 | reportGloveBoxForgotPasswordSuccess():EXECUTE+54663,仅 tab_instance_id |
| 同上 | 521-535 | reportGloveBoxPasswordOperation(type):EXECUTE+54664 |
| 同上 | 21-22 | getTabInstanceId 委托 TabSwitchUuidMap.get |
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/TrackConstants.java | 308-313 | 三个手套箱 TIP 事件 ID |
| 同上 | 379 | KEY_TIP="tip" |
| 同上 | 532-536 | VehicleControlKey.KEY_SOURCE/KEY_TAB_INSTANCE_ID |
| 同上 | 557-560 | KEY_GLOVE_BOX_OPEN_TYPE/KEY_GLOVE_BOX_PASSWORD_TYPE 均为 "type" |
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/TrackEvent.kt | 32-37 | report() 写入 tip 后转 SettingsTrack.track |
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/SettingsTrack.kt | 12, 16, 30-38, 72-74 | OneTrack 实例参数(APP_ID/项目/Mode.APP)与 track() 转发 |
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/Event.kt | 7-21 | 事件类型枚举(CLICK/EXECUTE 等) |
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/TabSwitchUuidMap.kt | 21-38 | UUID put 覆盖 / get 返回历史值或 null |
base/settingsBaseLib/src/main/java/com/android/micar/settings/track/TabConstants.kt | 10 | VEHICLE_CONTROL = 1 |
settingsPage/VehicleBodyControl/.../glovebox/GloveBoxController.kt | 196-203 | 密码验证成功回调,openGloveBox(WITH_PASSWORD) |
| 同上 | 210-219 | 账号验证成功回调:清零+开箱+忘记密码埋点 |
| 同上 | 222-229 | 统一开箱点:先写属性即上报,不等回执 |
| 同上 | 235-252 | 两个埋点包装方法,双层 try-catch 异常隔离 |
| 同上 | 261-278 | 软按键点击入口三分支 |
| 同上 | 288-302 | 不可用原因→Toast(165854 遗产,物理按键复用) |
settingsPage/VehicleBodyControl/.../glovebox/GloveBoxSettingsDialogActivity.kt | 130, 160, 179 | 密码操作埋点三调用点(CHANGE/SET/CLEAR) |
| 同上 | 193-205, 249-266 | 设置页内忘记密码成功路径与埋点包装 |
settingsPage/VehicleBodyControl/.../glovebox/GloveBoxNavigationHandler.kt | 125-131 | MIS 语音/远程开箱直写属性,无埋点(口径边界) |
下载/test/车机Feature/Meego 5458412/kb/参考change-368053.md | 全文 | 开箱埋点演进与评审关注点(重复上报/key 冲突/tabInstanceId) |
同目录 参考change-161208.md / 参考change-165854.md | 篇头 | 软按键流程 / 原因 Toast 两笔前序 change 定位 |