91 · Activity 与 Fragment 启动骨架
系列:90-总览 | 91-启动骨架 | 92-Controller 装配 | 93-首屏数据 | 94-扫描上屏 行号基线:fuben_micarsettings dev HEAD(2026-09-13)。
0. 一句话结论
蓝牙主页是一个对话框式 Activity + 单 Fragment + RecyclerView(preference 适配) 的骨架:Activity 只提供标题栏和容器,Fragment 负责把 XML 变成视图树,真正的内容全在 Controller(见 92 章)。commit() 是异步的——Activity onCreate 返回时 Fragment 生命周期还没开始跑。
1. 父类链:每一层贡献什么
flowchart TD subgraph Act["Activity 链"] A1["BluetoothSettingsActivity (Kotlin)<br/>首页Fragment/标题/关闭广播/导航接管"] A2["DialogBaseCarSettingsActivity :72<br/>骨架: setContentView+容器+launchFragment+标题栏实现"] A3["BaseConflictDialogActivity.kt:16<br/>冲替弹窗管理+initCustomTitleView"] A4["micarx.appcompat.app.BaseDialogActivity (外部库)<br/>toolbar/customTitleButton/rightIconContainer"] A5["AppCompatActivity (androidx)"] A1 --> A2 --> A3 --> A4 --> A5 end subgraph Frag["Fragment 链"] F1["BluetoothMainEntrySettingsFragment :55<br/>业务: 语音监听/空态/自动开蓝牙"] F2["DialogSettingsFragment.kt:11<br/>RecyclerView 创建+渐变蒙版"] F3["TopLevelSettingsFragment.java:55<br/>EventBus/锚点/页标记"] F4["SettingsFragment.java:36"] F5["BaseXmlParserSettingsFragment.java:98<br/>★XML解析+Controller装配(92章主角)"] F6["car-ui PreferenceFragment → micarx PreferenceFragmentCompat (外部库)"] F1 --> F2 --> F3 --> F4 --> F5 --> F6 end
Activity 侧各层贡献:
| 层 | 贡献 | 锚点 |
|---|---|---|
DialogBaseCarSettingsActivity | checkAppMatchRom ROM 校验(不匹配直接 safeFinish);setContentView(R.layout.car_setting_dialog_activity)(只有一个 FrameLayout dialog_container);getInitialFragment() 抽象 + launchIfDifferent + launchFragmentInternal(replace+addToBackStack+commit,:346-352);updateTitleBar()(:247-281);UxRestrictions 监听启动(:173-177);onResume 上报语音 pageAlias(:224-244) | :152-191 |
BaseConflictDialogActivity | 冲替弹窗管理(onCreate 注册 DialogActivityConflictManager);initCustomTitleView():标题=configPageTitle(),右侧 inflate 四件(filter/text/switch/circle_button) | .kt:27-37,50-73 |
BluetoothSettingsActivity | getInitialFragment()→BluetoothMainEntrySettingsFragment(:88-90);resetScanFlag("bluetooth_setting_onCreate")(:43);接管导航点击(:46);注册 ACTION_CLOSE_ACTIVITY 广播(CarPlay/AA 连接后自动关页,:40-63);resetToMainFragmentIfNeeded() 供外部关蓝牙时弹回退栈(:105-120) | — |
2. 启动时序:Activity onCreate 到 Fragment 挂载
sequenceDiagram autonumber participant L as 桌面/Dock点击 participant AMS as AMS/WMS participant Act as BluetoothSettingsActivity participant FM as FragmentManager participant Frag as BluetoothMainEntrySettingsFragment L->>AMS: startActivity AMS->>Act: onCreate Note over Act: super.onCreate 链:<br/>ROM校验 :154-158<br/>setContentView(只有dialog_container) :170<br/>UxRestrictions启动 :173-177<br/>标题栏初始化 Act->>Act: getInitialFragment() [:88-90] Act->>FM: launchFragmentInternal: replace+addToBackStack+commit [:346-352] Note over FM: ⚠️ commit() 异步!<br/>onCreate 继续走完才轮到 Fragment 生命周期 Act->>Act: resetScanFlag / 接管导航 / 注册关闭广播 [:43-63] Act-->>AMS: onCreate 返回 FM->>Frag: onAttach→onCreate→onCreateView→onViewCreated<br/>→onStart→onResume (主线程后续message) Note over Frag: onAttach: 解析XML元数据+建Controller(92章)<br/>onCreatePreferences: inflate Preference树<br/>onResume: 首屏数据开拉(92章§5闸门)
3. Fragment 生命周期逐方法清单(本类行为 + super 链)
锚点均指 BluetoothMainEntrySettingsFragment.java:
| 回调 | 本类行为 | super 链关键行为 |
|---|---|---|
onAttach :208-211 | 拿 LocalBluetoothManager 单例 | 解析 XML 元数据 + 创建全部 Controller + 注册 lifecycle observer(BaseXmlParserSettingsFragment.java:291-352) |
onCreate :77-80 | handleAutoEnableBluetooth() 读 intent extra bluetooth_auto_enable(:250-262) | onCreatePreferences inflate XML 成 Preference 树并绑定 controller(:647-667) |
onCreateView :100-107 | inflate 空态提示 micar_bluetooth_nopaired_device 进 list_container(与 RecyclerView 同容器叠放) | 建 RecyclerView(渐变蒙版,DialogSettingsFragment.kt:65-81);LayoutManager(:464-479);Adapter 装配(:741-756) |
onViewCreated :110-118 | 挂语音监听 MiCarView.setViewListener(getListView(), …) → notifyVoiceActive(见 36 篇) | 关 item 动画;listView 设语音 ViewDescription;forEach controller.onViewCreated(:597) |
onStart :214-218 | Fragment 自己也 registerCallback(this)(注意:与 Controller 的注册相互独立)+ setForegroundActivity | — |
onResume :148-155 | 蓝牙关时显示空态图;mIsAutoEnableBluetooth 时 enableBluetooth()(:264-287,UI 先行置 loading+checked,后台线程池 ThreadPoolUtils.ASYNC 开蓝牙) | 上报语音 pageAlias(:1489-1496) |
onPause | 未重写 | dismiss 锚点菜单、隐藏语音气泡 |
onStop :221-225 | unregisterCallback + setForegroundActivity(null) | — |
onDestroyView :121-124 | 摘语音监听 setViewListener(..., null) | 移除 pending Runnable/scroll listener/AdapterDataObserver(:707-719) |
其它关键方法:onBluetoothStateChanged(:158-168,STATE_OFF→显示空态+弹回退栈到主页);preHandleBtOff()(:191-205,UI 先行:关蓝牙时先隐藏两个列表+显示空态,不等广播回来);onPreferenceUpdateFinish(:228-248,见下节)。
4. HighlightablePreferenceGroupAdapter:层级变化 → 语音序号补齐
sequenceDiagram participant P as PreferenceGroup<br/>(设备增删/可见性变化) participant A as HighlightablePreferenceGroupAdapter participant F as Fragment P->>A: onPreferenceHierarchyChange (可能一帧内多次) A->>A: post去重的 deferred super调用 [:147-170]<br/>(防 UpdateOp 双重回收) A->>A: removeCallbacks + post mSyncRunnableFollower<br/>★一帧内多次变化只留最后一拍 A->>F: onPreferenceUpdateFinish [:228-248] F->>F: 取两个controller → refreshDeviceVoiceAssistOrder<br/>补齐语音序号角标
创建时机:BaseXmlParserSettingsFragment.onCreateAdapter()(:741-746)→ 本类覆写 createHighlightableAdapter(:89-96)挂上 setOnPreferenceUpdateListener(this) 并关掉分类背景。这就是 94 章 §6 说的”批处理上屏后序号补齐”的回调源。
5. 视图树装配:从 XML 到屏幕
flowchart TD subgraph ALayer["Activity 层"] AL["car_setting_dialog_activity.xml<br/>= 一个全屏 FrameLayout dialog_container<br/>+ BaseDialogActivity 提供的标题栏(toolbar/导航钮/右容器)"] end subgraph FRoot["Fragment 根: car_ui_preference_fragment_auto.xml"] LC["@android:id/list_container (FrameLayout)"] LC --> RV["RecyclerView settings_recycler_view<br/>(渐变蒙版包裹, DialogSettingsFragment.kt:65-81)"] LC --> TIP["空态提示 micar_bluetooth_nopaired_device<br/>(蓝牙关图标, 默认gone)"] end subgraph Tree["PreferenceScreen 树 (bluetooth_mainlist_fragment.xml)"] N1["MiCarConnectSwitchPreference<br/>开关"] N2["SpacePreference 8dp"] N3["MiCarPreferenceCategory 已配对<br/>(空容器,数据后填)"] N4["SpacePreference 10dp"] N5["CarUIPreferenceCategory 可用设备<br/>+尾部刷新动画widget"] end ALayer --> FRoot Tree -->|"HighlightablePreferenceGroupAdapter<br/>onCreateAdapter :741-756"| RV
空态设计:micar_bluetooth_nopaired_device.xml(蓝牙关闭图标,默认 gone)在 onCreateView:104 塞进 list_container,与 RecyclerView 同容器叠放;onResume:150 和 onBluetoothStateChanged:158-168 按蓝牙开关控制显隐——蓝牙关时列表中央显示空态图。
6. 线程模型速查
| 阶段 | 线程 | 证据 |
|---|---|---|
| Activity/Fragment 全生命周期、XML 解析、inflate | 主线程 | FragmentManager 事务语义;commit 异步但仍在主线程 message |
| 蓝牙广播回调(含 ACTION_FOUND) | 主线程 | receiver handler=null(LocalBluetoothManager.java:57-72 → BluetoothEventManager.java:230),类注释自述 “dispatches on the UI thread” |
onPreferenceUpdateFinish | 主线程下一拍 | mUIHandler post(HighlightablePreferenceGroupAdapter.java:108,166-169) |
bluetoothAdapter.enable()(自动开蓝牙) | 后台线程池 | ThreadPoolUtils.ASYNC(:286) |
| 语音 widget 上报 | async(未核实具体线程) | VoiceSearchTrigger.uploadWidgetsAsync(:665-666) |
7. 设计要点
commit()异步:Activity onCreate 返回时 Fragment 还没 attach——任何”Activity onCreate 里直接操作 Fragment 视图”的写法都是 bug。- Fragment 与 Controller 各自独立注册蓝牙回调(Fragment onStart:214-218 vs Controller onStartInternal:113-117)——Fragment 关心开关态(空态/弹栈),Controller 关心列表数据。
- UI 先行哲学:
preHandleBtOff()先隐藏列表再等广播;enableBluetooth()先置 loading 再异步开。感知流畅 > 状态精确,广播回来再校准。 - 空态与列表同容器叠放:不靠 visibility 切换列表,而是盖一层图标——避免列表清空/重建闪烁。
- 外部库边界:
BaseDialogActivity(micarx appcompat)和 car-uiPreferenceFragment无本仓源码,标题栏/根布局 inflate 的行为细节以 MiCarSupport 源码为参照(本笔记已标注未核实项)。