第4章 onCreate与onViewCreated时间线分析:生命周期方法执行顺序与耗时

4.1 现象识别

用户进入页面后,ViewModel 数据虽已就绪,但 UI 仍然出现可感知的延迟(白屏、加载指示器停留、空白帧),特别是在 Fragment 中首次初始化列表或图表时。该现象通常表现为:onCreate / onViewCreated 方法执行完毕后,UI 仍未及时刷新,导致首次渲染滞后 100ms~500ms 甚至更久。

典型用户反馈:“页面已经跳转,但内容要等一会儿才显示出来”“按返回键再进入,有时快有时慢”。

4.2 数据采集

使用 Systrace / Method Tracing 或简单的时间戳日志,精准定位延迟发生在哪个生命周期回调内。

// Fragment 中添加时间戳日志
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Log.d("TimeLine", "onCreate start: ${System.nanoTime()}")
    // 原有的初始化代码(可能含耗时操作)
    Log.d("TimeLine", "onCreate end: ${System.nanoTime()}")
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    Log.d("TimeLine", "onViewCreated start: ${System.nanoTime()}")
    // 原有的 View 初始化代码(可能含耗时操作)
    Log.d("TimeLine", "onViewCreated end: ${System.nanoTime()}")
}

采集后对比两个时间戳的差值,若超过 50ms 且处于主线程同步执行,即为延迟来源。

4.3 原因定位

Fragment 的生命周期顺序决定了哪个回调阻塞了首次显示:

生命周期方法执行顺序是否阻塞 UI 首次显示常见耗时操作
onCreate (Activity)1是View 层级初始化、布局解析
onCreate (Fragment)2是ViewModel 同步加载、文件读取
onCreateView3否†仅 inflate,不触发子 View 测量
onViewCreated4是数据绑定、列表填充、图片加载
onStart5否—
onResume6否—

† onCreateView 返回 View 但不会立即触发 measure/layout,因此不会直接阻塞用户看到的白屏阶段。

判断规则:如果时间戳日志显示 onCreate 或 onViewCreated 内耗时 > 50ms,且该段代码为同步的 I/O、复杂计算或数据库查询,则 100% 是延迟根因。

下面用时间线图展现阻塞点:

onCreate / onViewCreated 时间线阻塞点 Activity/Fragment 启动 onCreate 同步耗时→阻塞 onCreateView 无阻塞 onViewCreated 同步耗时→阻塞 onStart/Resume 无阻塞 首次UI显示 可能被延迟 若同步加载ViewModel → 阻塞 若同步填充数据 → 阻塞 推荐:ViewModel + viewModelScope 异步加载

4.4 修复动作

将阻塞主线程的耗时操作迁移到 ViewModel 的 init 或 viewModelScope 中异步执行,并在 onViewCreated 中仅订阅结果。

// ViewModel
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

    init {
        viewModelScope.launch(Dispatchers.IO) {
            val data = repository.fetchHeavyData()   // 耗时操作
            _uiState.value = UiState.Success(data)
        }
    }
}

// Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    lifecycleScope.launch {
        viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.uiState.collect { state ->
                // 更新 UI,不阻塞生命周期回调
            }
        }
    }
}
要点:确保耗时操作在 viewModelScope 内执行(自动关联生命周期的取消机制),不要在 init 中直接调用阻塞 API 而不使用协程。

4.5 复测

移除原有同步代码后,再次运行相同的场景,通过时间戳日志验证每个生命周期回调内耗时是否降至 5ms 以下:

// 预期输出(示例)
onCreate start: 3000000 ns
onCreate end: 3002000 ns    // 仅 2ms
onViewCreated start: 3100000 ns
onViewCreated end: 3101500 ns  // 仅 1.5ms

同时观察用户感知:首次显示不再有白屏或加载指示器残留。

4.6 预防

  • 在团队代码规范中明确规定:onCreate / onViewCreated 中禁止直接调用 Repository 或 Room 的同步方法。
  • 使用 Lifecycle-aware 组件(如 LiveData / Flow + repeatOnLifecycle)管理数据流,确保 Observer 只在合适的生命周期阶段激活。
  • 编写自动化单元测试或插桩测试,验证 ViewModel 的初始化是否异步:通过 runBlockingTest 检查 viewModelScope 内是否包含 delay 或 withContext(Dispatchers.IO) 调用。
  • 在 CI 流程中引入 性能基线检查,监控关键页面的首次显示帧时间(FrameTiming),超过阈值则报警。
总结口诀:“onCreate 别阻塞,onViewCreated 别同步;异步搬到 ViewModel,一键消除白屏痛。”