15、Android性能优化之布局优化:减少布局层级、使用ViewStub、Merge标签、ConstraintLayout优化

布局优化,说白了就是让界面渲染得更快、更流畅。我见过不少项目,功能没问题,但一滑动就掉帧,点个按钮要等半天才响应。排查下来,十有八九是布局写得太“重”了。

今天咱们就聊聊布局优化的几个核心手段。这些技巧我在实际项目中反复用过,效果立竿见影。

为什么布局会拖慢性能?

Android的布局渲染,本质上是一个“测量-布局-绘制”的过程。每次界面刷新,系统都要从根布局开始,一层层往下遍历。

你想想看,如果嵌套了七八层布局,每个子View都要计算自己的位置和大小。这就像你在一栋楼里找房间,每层都要停下来问路。能不慢吗?

所以优化的核心思路就两条:减少层级延迟加载

核心原则:能平铺就别嵌套,能延迟就别提前加载。

布局优化核心知识体系 布局优化 减少布局层级 ViewStub 延迟加载 Merge 标签 ConstraintLayout • 避免多层 LinearLayout 嵌套 • 使用 RelativeLayout 平铺 • 用 ConstraintLayout 替代 • 减少冗余的 ViewGroup • 配合 include 使用 • 避免不必要的嵌套 • 扁平化布局结构 • 灵活的相对定位

一、减少布局层级:别让ViewGroup“套娃”

我刚开始写Android时,特别喜欢用LinearLayout。一个页面从上到下,左边右边,全是LinearLayout嵌套。后来用Hierarchy Viewer一看,好家伙,一个列表项居然有七八层。

减少层级最直接的办法,就是用RelativeLayout或ConstraintLayout替代多层LinearLayout

举个例子,一个常见的“头像+用户名+描述”布局:

<!-- ❌ 不推荐:多层嵌套 -->
<LinearLayout android:orientation="horizontal">
    <ImageView android:id="@+id/avatar" />
    <LinearLayout android:orientation="vertical">
        <TextView android:id="@+id/name" />
        <TextView android:id="@+id/desc" />
    </LinearLayout>
</LinearLayout>

<!-- ✅ 推荐:用RelativeLayout平铺 -->
<RelativeLayout>
    <ImageView android:id="@+id/avatar"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true" />
    <TextView android:id="@+id/name"
        android:layout_toRightOf="@id/avatar"
        android:layout_alignTop="@id/avatar" />
    <TextView android:id="@+id/desc"
        android:layout_toRightOf="@id/avatar"
        android:layout_below="@id/name" />
</RelativeLayout>

我的经验:用Hierarchy Viewer或Layout Inspector检查布局层级。如果一个页面超过5层,就该考虑优化了。我一般控制在3层以内。

二、ViewStub:按需加载,别一股脑全显示

有些布局不是一开始就需要显示的。比如“加载失败”的提示、用户协议弹窗、或者某些高级功能面板。如果一启动就把它们全inflate出来,白白浪费了时间和内存。

ViewStub就是干这个的。它本身不占布局空间,只有当你调用inflate()setVisibility(View.VISIBLE)时,才会真正加载。

<!-- 定义ViewStub -->
<ViewStub
    android:id="@+id/stub_loading_error"
    android:layout="@layout/layout_loading_error"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

// 在代码中按需加载
ViewStub stub = findViewById(R.id.stub_loading_error);
if (stub != null) {
    View errorView = stub.inflate();
    // 或者直接 stub.setVisibility(View.VISIBLE);
}

注意:ViewStub一旦inflate之后,就不再是ViewStub了,而是被替换成了实际的布局。所以第二次调用inflate会抛异常。我习惯在inflate之前先判空。

我曾经在一个新闻列表页里,用ViewStub延迟加载了“分享面板”和“评论输入框”。用户不点击分享按钮,这两个布局就不会创建。页面启动速度提升了将近30%。

三、Merge标签:去掉多余的根节点

Merge标签的作用很简单:当你用<include>引入一个布局时,去掉那个布局的根ViewGroup

举个例子,你有一个公共的“标题栏”布局:

<!-- title_bar.xml -->
<LinearLayout xmlns:android="..."
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView android:id="@+id/title" />
    <Button android:id="@+id/back" />
</LinearLayout>

如果你在另一个布局里用<include layout="@layout/title_bar" />,那就会多出一层LinearLayout。这层LinearLayout除了增加层级,没有任何意义。

用Merge优化后:

<!-- title_bar.xml 优化版 -->
<merge xmlns:android="...">
    <TextView android:id="@+id/title" />
    <Button android:id="@+id/back" />
</merge>

这样include进来的就是两个直接子View,少了一层嵌套。

适用场景:Merge标签只适合作为<include>的根标签,或者作为自定义ViewGroup的根布局。不能直接用在Activity的setContentView里。

四、ConstraintLayout:扁平化布局的利器

ConstraintLayout是Google推出的“终极扁平化方案”。它允许你用相对定位、链、屏障等约束关系,在一个平面内完成复杂布局。

以前用LinearLayout嵌套三层才能实现的效果,用ConstraintLayout一层就能搞定。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView android:id="@+id/avatar"
        android:layout_width="48dp"
        android:layout_height="48dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <TextView android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/avatar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@id/avatar"
        android:layout_marginStart="12dp" />

    <TextView android:id="@+id/desc"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/avatar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/name"
        android:layout_marginStart="12dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

你看,一个ConstraintLayout就搞定了。没有嵌套,没有多余的ViewGroup。

我个人习惯:新项目一律用ConstraintLayout作为根布局。除非页面极其简单(比如只有一个TextView),否则不用LinearLayout。ConstraintLayout配合layout_constraintDimensionRatio还能轻松实现比例缩放,这在适配不同屏幕时特别好用。

五、实战建议:优化不是一蹴而就的

布局优化这件事,我建议你分三步走:

  1. 先测量:用Layout Inspector看看当前页面的层级深度和渲染耗时。找到“重灾区”。
  2. 再优化:针对层级深的区域,用ConstraintLayout或RelativeLayout替换多层LinearLayout。对不常用的布局,用ViewStub延迟加载。
  3. 最后验证:优化完再测一次,看看渲染时间有没有降下来。我一般要求页面渲染时间控制在16ms以内(60fps)。

避坑指南:我曾经为了追求“绝对扁平”,把所有的View都塞进一个ConstraintLayout里。结果约束条件写了几十条,可读性极差,维护起来想哭。后来我学会了适度分组——用<Group><include>把逻辑相关的View包起来,既保持了扁平,又保证了可读性。

嗯,布局优化说白了就是“做减法”。少一层嵌套,就少一次测量;少一个View,就少一次绘制。把这些细节做好,你的App自然就流畅了。


公众号:蓝海资料掘金营,微信deep3321