90 · 蓝牙主页启动全流程 · 总览

从”桌面点蓝牙图标”到”页面完整展示”的端到端全景。先整体,后分章: 90-总览(本篇) → 91-Activity与Fragment启动骨架 → 92-XML解析与Controller装配 → 93-首屏数据:开关与已配对列表 → 94-扫描发现与可用设备上屏 行号基线:fuben_micarsettings dev HEAD(2026-09-13)。

0. 三十秒版本

点图标 → Activity建容器+标题栏 → Fragment commit(异步) → onAttach: 反射建Controller+建蓝牙单例+同步读已配对
→ onCreate: inflate Preference树+绑定 → onCreateView: 装RecyclerView+空态图 → onViewCreated: 挂语音监听
→ onStart: 注册蓝牙回调 → onResume: ★闸门放开,首刷开始 → 开关/已配对走200ms延迟 → 后台取数 → 主线程上屏
→ 触发扫描 → 设备逐台批处理上屏(500ms×10台) → 角标补齐 → 页面可交互

三条核心设计哲学贯穿全程:

哲学体现
首帧感知优先首开 200ms 延迟(BLUETOOTH_DATA_LAZY_TIME)让重数据给首帧让路;UI 先行(preHandleBtOff/enableBluetooth 先置态后等广播校准)
事件驱动 + 闸门生命周期观察者驱动 Controller;mIsViewInitialized 挡首刷;扫描闸门统一管控
主线程保护settingslib 广播全在主线程(持锁 dispatch!)→ App 侧重活全挪后台(取数/过滤/binder),UI 批处理限流(500ms×10台)

1. 全景时序图(点击 → 首帧 → 可交互)

sequenceDiagram
    autonumber
    participant U as 用户(桌面/Dock)
    participant Act as BluetoothSettingsActivity
    participant FM as FragmentManager
    participant Frag as BluetoothMainEntrySettingsFragment
    participant Ctrl as 3个Controller
    participant LBM as LocalBluetoothManager
    participant BG as 后台线程
    participant Stack as 蓝牙协议栈

    U->>Act: startActivity → onCreate
    Note over Act: ROM校验→setContentView(仅dialog_container)<br/>→标题栏→UxRestrictions启动
    Act->>FM: replace+addToBackStack+commit()<br/>⚠️异步,onCreate继续走
    Act->>Act: resetScanFlag / 导航接管 / 注册关闭广播

    FM->>Frag: onAttach
    Frag->>Frag: ★解析XML元数据→反射建3个Controller<br/>注册lifecycle observer (92章)
    Frag->>LBM: getLocalBtManager() 单例首创
    LBM->>LBM: readPairedDevices(主线程,binder)<br/>→CachedBluetoothDeviceManager缓存就绪<br/>(此时无订阅者,纯建仓)

    FM->>Frag: onCreate → onCreatePreferences
    Note over Frag: inflate Preference树→controller.setPreference绑定
    FM->>Frag: onCreateView
    Note over Frag: 装RecyclerView(渐变蒙版)+空态图叠放<br/>Adapter=HighlightablePreferenceGroupAdapter
    FM->>Frag: onViewCreated
    Frag->>Frag: 挂语音监听 setViewListener(36篇)

    FM->>Frag: onStart
    Frag->>LBM: registerCallback(Fragment自己)
    Ctrl->>LBM: registerCallback(各Controller, onStartInternal)

    FM->>Frag: onResume ★首刷闸门放开
    Ctrl->>Ctrl: mIsViewInitialized=true → updateState

    par 开关(4跳全主线程)
        Ctrl->>Stack: adapter.getState() (binder)
        Ctrl->>Ctrl: handleStateChanged 直推开关UI
    and 已配对(7跳)
        Ctrl->>Ctrl: postDelayed 200ms (首开延迟)
        Ctrl->>BG: getCachedDevicesCopy+bondedDeviceFilter(binder)
        BG->>Ctrl: 回主线程 removeAll+逐个addPreference(排序插入)
        Note over Ctrl: onAttached→refreshDeviceUi 定title/三态摘要/图标
    and 可用设备(94章)
        Ctrl->>Ctrl: 200ms后 innerUpdateState 清重建
        Ctrl->>BG: triggerScan("update_state")
        BG->>Stack: startDiscovery
    end

    loop 扫描循环(扫完12s自动重扫)
        Stack-->>LBM: ACTION_FOUND (主线程!)
        LBM-->>Ctrl: onDeviceAdded (持锁dispatch)
        Ctrl->>Ctrl: 拦截/过滤→入缓冲队列
        Ctrl->>Ctrl: 每500ms一批≤10台 addPreference上屏
    end

    Note over Frag: 每次层级变化 → onPreferenceUpdateFinish<br/>→ 补齐语音序号角标
    Note over U: 页面可交互

2. 分阶段总表

#阶段干什么线程章节
S0Activity 启动ROM 校验、容器、标题栏、commit Fragment(异步)主91 §2
S1onAttach反射建 Controller + 注册生命周期观察者;LBM 单例首创 + 主线程同步 readPairedDevices 建仓主92 §1-3 / 93 §3
S2onCreate/Preferencesinflate Preference 树 + controller.setPreference 绑定(类型校验+挂监听)主92 §2
S3onCreateViewRecyclerView(渐变蒙版)+ 空态图同容器叠放;Adapter 装配主91 §5
S4onViewCreated挂语音监听;listView 设 ViewDescription;forEach controller.onViewCreated主91 §3 / 36
S5onStartFragment 与各 Controller 各自 registerCallback;开关设 summary主91 §3 / 92 §6
S6onResume★mIsViewInitialized=true 闸门放开;首开 200ms 延迟启动;空态按开关显隐;startLoading 动画起转主92 §5 / 93 §4 / 94 §1
S7首屏数据开关:getState 直推(onCreate 已首刷);已配对:后台过滤→主线程排序插入→三态摘要主+后台93
S8扫描上屏triggerScan→ACTION_FOUND→拦截/过滤→缓冲→500ms×10台批处理上屏主(回调)+后台(触发)94
S9角标补齐每次层级变化 → onPreferenceUpdateFinish(去重到一帧一拍)→ 语音序号主下一拍91 §4 / 94 §6
S10退出onPause 停动画/停扫/清缓存/重置首开标志;onStop 注销回调主+后台94 §7

3. 五张”记住就不迷路”的要点卡

flowchart LR
    subgraph K1["① 两个200ms"]
        A["首开延迟 BLUETOOTH_DATA_LAZY_TIME=200ms<br/>两个列表各一次, onPause重置<br/>★每次进页都有,非仅冷启动"]
    end
    subgraph K2["② 首帧与广播无关"]
        B["已配对首帧=LBM构造时同步建仓<br/>开关首帧=onCreate直读adapter<br/>广播只管首帧后的增量修正"]
    end
    subgraph K3["③ 主线程上的雷"]
        C["settingslib广播回调全在主线程<br/>addDevice持锁dispatch(CBDM:135)<br/>→ App侧回调里只许O(1)入队"]
    end
    subgraph K4["④ 两道闸门"]
        D["mIsViewInitialized: 首刷挡到onResume<br/>BluetoothScanManager: 扫描闸门<br/>两道的放行点都要熟"]
    end
    subgraph K5["⑤ 批处理防卡顿"]
        E["onDeviceAdded不直接上屏<br/>缓冲队列+500ms×10台+锁外UI<br/>密集环境的保命设计"]
    end
    K1 --- K2 --- K3 --- K4 --- K5

4. 页面上有哪些东西(首帧完成态)

flowchart TD
    subgraph Page["蓝牙主页(dialog样式)"]
        T["标题栏: 蓝牙 + 关闭钮<br/>(BaseDialogActivity 提供)"]
        SW["开关行: MiCarConnectSwitchPreference<br/>summary='可被发现为<车机名>'"]
        G1["已配对分组 (pk_bluetooth_paired_devices)<br/>已连接排最前→字母序<br/>三态摘要: 已连接/正在连接/已保存"]
        G2["可用设备分组 (pk_bluetooth_available_devices_e2)<br/>尾部刷新动画(全程旋转)<br/>只显示手机类设备(有意过滤)"]
        TIP["空态图: 蓝牙关时叠在列表中央"]
        VB["语音角标: 可见即可说激活时<br/>左侧序号(连续编号)"]
    end
    T --> SW --> G1 --> G2
    G1 -.叠放.- TIP
    G1 -.装饰.- VB
    G2 -.装饰.- VB

5. 学习地图:怎么读这个系列

flowchart LR
    Start["90 总览(本篇)"] --> N1["91 启动骨架<br/>答: 页面架子怎么搭起来的"]
    N1 --> N2["92 Controller装配<br/>答: XML怎么变成活的Controller"]
    N2 --> N3["93 首屏数据<br/>答: 开关和已配对从哪来"]
    N3 --> N4["94 扫描上屏<br/>答: 附近设备怎么一台台冒出来"]
    N4 --> Done["✅ 全链路闭环"]
    Done -.-> X1["深入: 01发现流程/02配对/03详情"]
    Done -.-> X2["排障: 40日志取证/42踩坑/68案例"]
    Done -.-> X3["语音: 36可见即可说"]
    style Start fill:#e1f5ff
    style Done fill:#e8f5e9

读完这个系列你应该能回答:

  1. 为什么打开蓝牙页,开关先出来、列表慢一拍?(200ms 首开延迟 + mIsViewInitialized 闸门)
  2. 为什么飞行模式开关蓝牙后回页面,已配对立刻有、附近设备要等?(缓存建仓 vs 真实扫描)
  3. 为什么展厅环境里设备是一台台冒出来而不是一次性全出?(500ms×10 批处理)
  4. 语音说”连接第 3 个”为什么能点中?(36 篇 + 91 §4 的补齐回调)

6. 与既有篇目的分工

主题本系列既有篇目
启动/首帧全景90-94(本文)—
扫描机制深挖(闸门/循环/过滤规则)94 讲启动视角01-发现流程 讲机制全量
配对流程—02-配对流程
已配对摘要状态机(运行期)93 §5 只讲首帧取值34-摘要状态机
CBD 缓存对象生命周期93 §3 只讲建仓时机60 系列
语音链路全解91/94 只标挂点36-可见即可说
性能/卡顿案例94 §8 只列设计68-案例走读

7. 已知未核实项(诚实清单)

  • 外部库(micarx appcompat / car-ui PreferenceFragment / micarx.preference)无本仓源码:Preference.onBindViewHolder 的文本绑定、PreferenceGroupAdapter 刷新细节按 androidx 语义理解;binarySearch 排序插入经 javap 字节码核实(micarx.preference 3.2.6)
  • VoiceSearchTrigger.uploadWidgetsAsync 执行线程未核实
  • Fragment onAttach 为进程首创 LBM 的结论基于调用点枚举,未做运行时验证