解决ViewTreeObserver引发的一个问题

121 阅读4分钟

在最近的项目开发中,遇到了一个关于布局的BUG。最开始以为这只是一次酣畅淋漓,非常麻烦的问题修复,慢慢深入发现了,只需要简单的适配即可。

图片

一、问题背景

在项目中,我们需要根据某些视图的变化动态更新布局参数。这个需求最早由同事实现,原本我以为只是一次简单的布局更新。可是,当我开始着手修复时,发现布局更新过程中会频繁导致应用卡顿,特别是在某些设备上,性能问题变得尤为明显。这让我意识到,问题远比我想象中的要复杂,需要进行更深入的分析。

代码片段:布局更新

binding.view.updateLayoutParams {
    width = wmRect.width()
    height = wmRect.height()
}

卡顿现象分析

当我开始排查时,发现频繁调用该更新方法会导致应用在布局传递过程中出现卡顿现象。最初,我认为这可能涉及到更复杂的布局渲染问题,但随着逐步深入分析,我发现卡顿的根源主要在于布局更新的方式和频率。

二、监听器的选择与性能考量

在Android中,View.OnLayoutChangeListener 和 ViewTreeObserver.OnGlobalLayoutListener 是两种常用的布局监听器,它们的适用场景和性能表现存在显著差异。在修复这个问题时,我发现合理选择布局监听器,对性能的提升至关重要。

1. View.OnLayoutChangeListener

  • 专注于单个视图OnLayoutChangeListener 只监听单个视图的布局变化,只有当目标视图的大小或位置发生变化时才会触发。由于它的局部监听特性,这使得它在性能上显得更加高效。
  • 避免全局布局传递:由于它仅关注特定视图的变化,因此不会引发整个视图树的布局传递,避免了不必要的计算和重绘,能够有效减少卡顿现象。

2. ViewTreeObserver.OnGlobalLayoutListener

  • 全局监听:与 OnLayoutChangeListener 不同,OnGlobalLayoutListener 会监听整个视图树的布局变化,每当任何视图发生布局变化时,它都会被触发。这种全局监听虽然功能强大,但会带来性能问题。

  • 可能引发卡顿:由于 OnGlobalLayoutListener 会在每次布局变化时都被触发,频繁的布局更新可能导致UI线程阻塞,尤其是在视图树较复杂或者更新频繁时,性能问题更为明显。

图片

为什么会卡顿?

在 OnGlobalLayoutListener 中频繁调用 updateLayoutParams 可能导致卡顿的原因主要有以下几点:

  • 布局传递循环:每当 updateLayoutParams 被调用时,它会触发新的布局传递,可能形成一个递归循环,导致应用卡死。
  • 多次触发更新:由于 OnGlobalLayoutListener 会在每次布局变化时触发,如果在此监听器中频繁调用 updateLayoutParams,会引发多次布局更新请求,从而增加计算开销,最终影响性能。
  • 性能开销updateLayoutParams 需要重新测量和布局视图,这个过程需要消耗大量的CPU资源。如果在 OnGlobalLayoutListener 中反复进行这样的操作,UI线程会被阻塞,导致卡顿。

三、问题复现与优化分析

在修复过程中,我发现以下方式更新布局参数时会导致卡顿问题的复现:

binding.view.updateLayoutParams {
    width = wmRect.width()
    height = wmRect.height()
}

图片

为了优化性能,我意识到可以通过在更新前检查当前视图的宽度和高度,来避免不必要的更新。具体来说,只有在视图的实际尺寸发生变化时,才进行布局更新。这种方式有效减少了更新的频率,也避免了不必要的性能消耗:

val currentWidth = binding.view.width
val currentHeight = binding.view.height
if (currentWidth != wmRect.width() || currentHeight != wmRect.height()) {
    binding.view.updateLayoutParams {
        width = wmRect.width()
        height = wmRect.height()
    }
}

通过这种简单的优化,问题得到了显著缓解,卡顿现象大大减少,应用的响应性能得到了提升。

四、总结

虽然最初问题看起来很复杂,但其实只需要合理选择布局监听器,优化布局更新的时机和方式,就能有效解决性能问题。总结起来,优化布局更新时的关键在于:

  1. 选择合适的布局监听器:根据需求选择 OnLayoutChangeListener 或 OnGlobalLayoutListener。若仅需要关注单个视图的变化,优先选择 OnLayoutChangeListener,避免全局监听带来的性能问题。
  2. 减少不必要的布局更新:通过条件判断避免频繁更新布局,只有在实际需要更新时才进行布局调整。
  3. 避免布局传递循环:确保布局更新操作不会触发多次递归布局传递,减少计算开销。

原文地址:mp.weixin.qq.com/s/MmFAuIpJl…