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 侧各层贡献:

层贡献锚点
DialogBaseCarSettingsActivitycheckAppMatchRom 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
BluetoothSettingsActivitygetInitialFragment()→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-80handleAutoEnableBluetooth() 读 intent extra bluetooth_auto_enable(:250-262)onCreatePreferences inflate XML 成 Preference 树并绑定 controller(:647-667)
onCreateView :100-107inflate 空态提示 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-218Fragment 自己也 registerCallback(this)(注意:与 Controller 的注册相互独立)+ setForegroundActivity—
onResume :148-155蓝牙关时显示空态图;mIsAutoEnableBluetooth 时 enableBluetooth()(:264-287,UI 先行置 loading+checked,后台线程池 ThreadPoolUtils.ASYNC 开蓝牙)上报语音 pageAlias(:1489-1496)
onPause未重写dismiss 锚点菜单、隐藏语音气泡
onStop :221-225unregisterCallback + 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. 设计要点

  1. commit() 异步:Activity onCreate 返回时 Fragment 还没 attach——任何”Activity onCreate 里直接操作 Fragment 视图”的写法都是 bug。
  2. Fragment 与 Controller 各自独立注册蓝牙回调(Fragment onStart:214-218 vs Controller onStartInternal:113-117)——Fragment 关心开关态(空态/弹栈),Controller 关心列表数据。
  3. UI 先行哲学:preHandleBtOff() 先隐藏列表再等广播;enableBluetooth() 先置 loading 再异步开。感知流畅 > 状态精确,广播回来再校准。
  4. 空态与列表同容器叠放:不靠 visibility 切换列表,而是盖一层图标——避免列表清空/重建闪烁。
  5. 外部库边界:BaseDialogActivity(micarx appcompat)和 car-ui PreferenceFragment 无本仓源码,标题栏/根布局 inflate 的行为细节以 MiCarSupport 源码为参照(本笔记已标注未核实项)。