Skip to content

Android 布局优化

目录

  1. 布局性能指标
  2. 减少 View 层级
  3. 布局标签优化(merge/include/ViewStub)
  4. 过度绘制优化
  5. ConstraintLayout 深度解析
  6. 布局加载优化
  7. RecyclerView 性能优化
  8. 布局调试工具
  9. 面试考点总结

1. 布局性能指标

1.1 布局层级(View Hierarchy)

定义:布局层级的数量,即 View 树的高度

影响

  • 层级越深,measure 和 layout 耗时越长
  • 影响触摸事件传递
  • 增加过度绘制概率

优化目标

  • 保持层级在 10 层以内
  • 复杂页面不超过 15 层
kotlin
// 获取布局层级
fun getViewHierarchyDepth(view: View): Int {
    return if (view is ViewGroup) {
        val childrenDepth = if (view.childCount > 0) {
            view.childrenAsList.map { getViewHierarchyDepth(it) }
                .maxOrNull() ?: 0
        } else {
            0
        }
        1 + childrenDepth
    } else {
        1
    }
}

1.2 过度绘制(Overdraw)

定义:在同一帧中,某个像素被多次绘制

颜色标识(Debug 模式):

  • 蓝色:完美(绘制一次)
  • 绿色:优秀(最多 2 次)
  • 粉色:良好(最多 3 次)
  • 红色:警告(最多 5 次)
  • 紫色:过度绘制严重(超过 5 次)

影响

  • 浪费 GPU 资源
  • 降低渲染性能
  • 增加电量消耗

1.3 布局测量与布局耗时

kotlin
// measure 过程:计算 View 的大小
// layout 过程:计算 View 的位置
// draw 过程:绘制 View

// 获取布局耗时
class LayoutPerformanceMonitor {
    fun measureLayoutTime(view: View): Long {
        val start = System.nanoTime()
        view.measure(
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        )
        return (System.nanoTime() - start) / 1_000_000 // 毫秒
    }
}

2. 减少 View 层级

2.1 问题识别

常见导致层级过多的原因

xml
<!-- ❌ 错误示例:不必要的嵌套 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Text" />

        </LinearLayout>

    </LinearLayout>

</LinearLayout>
<!-- 层级:LinearLayout → LinearLayout → LinearLayout → TextView = 4 层 -->

2.2 优化策略

策略 1:使用单个容器布局

xml
<!-- ✅ 优化后:使用 constraints 替代嵌套 -->
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 层级:ConstraintLayout → TextView = 2 层 -->

策略 2:使用 margin 替代嵌套 Layout

xml
<!-- ❌ 错误:使用嵌套 Layout 做间距 -->
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text" />

</LinearLayout>

<!-- ✅ 正确:使用 margin 或 padding -->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    android:text="Text" />

策略 3:合并相同属性的兄弟 View

xml
<!-- ❌ 错误:多个 View 有相同的样式 -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textColor="@color/black"
        android:textSize="14sp"
        android:text="Item 1" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textColor="@color/black"
        android:textSize="14sp"
        android:text="Item 2" />

</LinearLayout>

<!-- ✅ 正确:使用 style -->
<style name="ItemTextStyle">
    <item name="android:textColor">@color/black</item>
    <item name="android:textSize">14sp</item>
</style>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        style="@style/ItemTextStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Item 1" />

    <TextView
        style="@style/ItemTextStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Item 2" />

</LinearLayout>

策略 4:避免过度使用 FrameLayout 堆叠

xml
<!-- ❌ 错误:多层 FrameLayout 堆叠 -->
<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/background" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Content" />

    </FrameLayout>

</FrameLayout>

<!-- ✅ 正确:单层 FrameLayout -->
<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/background" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Content" />

</FrameLayout>

2.3 常见布局对比

布局类型层级性能适用场景
LinearLayout简单线性布局
RelativeLayout相对定位(已废弃)
ConstraintLayout复杂布局(推荐)
FrameLayout层叠布局
GridLayout网格布局
CoordinatorLayout协同布局 + 行为

3. 布局标签优化(merge/include/ViewStub)

3.1 merge 标签

作用:合并布局,减少多余的嵌套层级

原理:merge 标签中的子 View 直接提升到父布局中

xml
<!-- 定义可复用的布局:res/layout/header_merge.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageView
        android:id="@+id/headerLogo"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/logo" />

    <TextView
        android:id="@+id/headerTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Header Title"
        android:textSize="18sp" />

</merge>

<!-- 使用 merge:res/layout/activity_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/header_merge" />

    <!-- headerLogo 和 headerTitle 直接成为 ConstraintLayout 的子 View -->

</androidx.constraintlayout.widget.ConstraintLayout>

注意事项

  1. merge 必须是根元素
  2. 父布局必须是 ViewGroup
  3. 不能设置 layout_width 和 layout_height

3.2 include 标签

作用:复用布局文件,减少重复代码

用法

xml
<!-- 定义通用布局:res/layout/item_card.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cardContainer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="@drawable/card_background">

    <ImageView
        android:id="@+id/itemImage"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/itemTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/itemDescription"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp" />

</LinearLayout>

<!-- 在多个地方复用 -->
<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- 或者在其他布局中 include -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/item_card" />
    <include layout="@layout/item_card" />
    <include layout="@layout/item_card" />

</LinearLayout>

属性覆盖

xml
<!-- include 可以覆盖被引用布局的属性 -->
<include
    layout="@layout/item_card"
    android:visibility="gone"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

3.3 ViewStub

作用:延迟加载布局,提高初始加载速度

原理:ViewStub 是一个轻量级的 View,只有在调用 inflate() 时才会加载真正的布局

xml
<!-- 定义需要延迟加载的布局:res/layout/ads_view.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/adsContainer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/ads_background">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Advertisement" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:src="@drawable/ads_image" />

</LinearLayout>

<!-- 使用 ViewStub:res/layout/activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:text="Main Content" />

    <!-- ViewStub 初始不可见,占用内存极小 -->
    <ViewStub
        android:id="@+id/adsViewStub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/ads_view" />

</LinearLayout>

代码使用

kotlin
class ViewStubActivity : AppCompatActivity() {
    private var adsView: View? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val viewStub = findViewById<ViewStub>(R.id.adsViewStub)
        
        // 延迟加载(例如:网络请求完成后)
        loadAdsAfterNetworkRequest(viewStub)
    }
    
    private fun loadAdsAfterNetworkRequest(viewStub: ViewStub) {
        // 模拟网络请求
        handler.postDelayed({
            // inflate 后,ViewStub 被替换为真实布局
            adsView = viewStub.inflate()
        }, 2000)
    }
    
    // 条件加载
    fun conditionallyLoadAds(viewStub: ViewStub, shouldLoad: Boolean) {
        if (shouldLoad) {
            adsView = viewStub.inflate()
        } else {
            // 不加载,ViewStub 被移除
            viewStubVisibility = View.GONE
        }
    }
}

ViewStub 特性

  1. 只使用一次,inflate 后被替换
  2. 不影响布局层级统计
  3. 提高初始加载速度
  4. 适合非必需、可延迟加载的内容

3.4 三种标签对比

标签作用层级影响使用场景
merge合并布局减少层级复用布局、减少嵌套
include包含布局保持原层级复用布局、减少重复代码
ViewStub延迟加载初始无层级可选内容、按需加载

4. 过度绘制优化

4.1 检测过度绘制

方法 1:Developer Options

开启方式

  1. 设置 → 关于手机 → 连续点击"版本号"开启开发者模式
  2. 开发者选项 → 调试绘制区域 → 开启"显示过度绘制"

颜色说明

  • 蓝色:完美
  • 绿色:优秀
  • 粉色:良好
  • 红色:警告
  • 紫色:严重

方法 2:adb 命令

bash
# 开启过度绘制检测
adb shell settings put global debug.overdraw 1

# 关闭
adb shell settings put global debug.overdraw 0

# 开启高亮模式
adb shell settings put global debug.overdraw 2

方法 3:代码检测

kotlin
class OverdrawDetector {
    fun detectOverdraw(activity: Activity) {
        val view = activity.window.decorView
        
        // 开启过度绘制检测
        view.setDebugDrawingEnabled(true)
        
        // 自定义检测逻辑
        detectOverdrawRecursive(view)
    }
    
    private fun detectOverdrawRecursive(view: View, depth: Int = 0) {
        // 检查背景
        if (view.background != null) {
            checkBackgroundOverlap(view)
        }
        
        // 递归检查子 View
        if (view is ViewGroup) {
            for (i in 0 until view.childCount) {
                detectOverdrawRecursive(view.getChildAt(i), depth + 1)
            }
        }
    }
    
    private fun checkBackgroundOverlap(view: View) {
        // 检查父 View 和子 View 的背景是否重叠
        val parent = view.parent as? View
        if (parent?.background != null && view.background != null) {
            // 检测到重叠
            Log.w("Overdraw", "Detected overdraw at: ${view.javaClass.simpleName}")
        }
    }
}

4.2 常见过度绘制场景

场景 1:背景色覆盖

xml
<!-- ❌ 错误:子 View 和父 View 都是白色背景 -->
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical">

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@android:color/white" />

</LinearLayout>

<!-- ✅ 正确:移除子 View 的背景 -->
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical">

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    <!-- 移除 android:background -->

</LinearLayout>

场景 2:图片与背景重叠

xml
<!-- ❌ 错误:ImageView 覆盖在背景上 -->
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/cover_image" />

</FrameLayout>

<!-- ✅ 正确:使用图片作为背景 -->
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/cover_image"
        android:scaleType="centerCrop" />

</FrameLayout>

场景 3:多层透明 View

xml
<!-- ❌ 错误:多层透明 View 叠加 -->
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/semi_transparent" />

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/semi_transparent" />

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/semi_transparent" />

</FrameLayout>

<!-- ✅ 正确:合并透明层 -->
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/dark_transparent" />
    <!-- 使用更深的透明度替代多层叠加 -->

</FrameLayout>

场景 4:ListView/RecyclerView 背景

xml
<!-- ❌ 错误:RecyclerView 和 Item 都有背景 -->
<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:clipToPadding="false"
    android:padding="8dp">

    <!-- Item 布局也有白色背景 -->
</androidx.recyclerview.widget.RecyclerView>

<!-- ✅ 正确:移除 RecyclerView 的背景 -->
<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:padding="8dp">
    <!-- 移除 android:background -->
</androidx.recyclerview.widget.RecyclerView>

4.3 优化策略

策略 1:移除不必要的背景

kotlin
// 代码移除背景
class BackgroundOptimizer {
    fun optimizeBackground(view: View) {
        // 如果父 View 和子 View 背景相同,移除子 View 背景
        val parent = view.parent as? View
        if (parent?.background == view.background) {
            view.background = null
        }
    }
}

策略 2:使用 LayerType

kotlin
// 对于复杂绘制,使用软件层
view.layerType = View.LAYER_TYPE_HARDWARE // 硬件加速(推荐)
// view.layerType = View.LAYER_TYPE_SOFTWARE // 软件渲染

// 动画结束后移除层
view.postDelayed({
    view.layerType = View.LAYER_TYPE_NONE
}, 500)

策略 3:使用 mergeDrawable

xml
<!-- ✅ 合并多层图片为一个 -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/background" />
    <item android:drawable="@drawable/overlay" />
</layer-list>

策略 4:设置 clipChildren 和 clipToPadding

xml
<ViewGroup
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="true"
    android:clipToPadding="true">
    <!-- 子 View 超出部分被裁剪,避免过度绘制 -->
</ViewGroup>

5. ConstraintLayout 深度解析

5.1 基础使用

xml
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 居中的 TextView -->
    <TextView
        android:id="@+id/titleText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@id/descriptionText"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- 在 titleText 下方的 descriptionText -->
    <TextView
        android:id="@+id/descriptionText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Description"
        app:layout_constraintBottom_toTopOf="@id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/titleText" />

    <!-- 底部的按钮 -->
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/descriptionText" />

</androidx.constraintlayout.widget.ConstraintLayout>

5.2 链式布局(Chain)

xml
<!-- 链式布局:多个 View 形成链 -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 链的起点 -->
    <TextView
        android:id="@+id/item1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 1"
        app:layout_constraintBaseline_toBaselineOf="@id/item2"
        app:layout_constraintStart_toStartOf="parent" />

    <!-- 链的中间 -->
    <TextView
        android:id="@+id/item2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 2"
        app:layout_constraintBaseline_toBaselineOf="@id/item3"
        app:layout_constraintEnd_toStartOf="@id/item3"
        app:layout_constraintStart_toEndOf="@id/item1" />

    <!-- 链的终点 -->
    <TextView
        android:id="@+id/item3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/item2" />

</androidx.constraintlayout.widget.ConstraintLayout>

5.3 比例和宽高比

xml
<!-- 保持宽高比 -->
<ImageView
    android:id="@+id/imageView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:src="@drawable/image"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<!-- 基于宽度的比例 -->
<TextView
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="W,1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

5.4 指南线(Guideline)

xml
<!-- 垂直和水平指南线 -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 垂直指南线(距离左边 20%) -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/verticalGuideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.2" />

    <!-- 水平指南线(距离顶部 100dp) -->
    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/horizontalGuideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="100dp" />

    <!-- 使用指南线定位 View -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Aligned to Guidelines"
        app:layout_constraintEnd_toStartOf="@id/verticalGuideline"
        app:layout_constraintTop_toTopOf="@id/horizontalGuideline" />

</androidx.constraintlayout.widget.ConstraintLayout>

5.5 屏障(Barrier)

xml
<!-- 屏障:动态调整布局 -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text1" />

    <TextView
        android:id="@+id/text3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text2" />

    <!-- 屏障:在 text1、text2、text3 右侧 -->
    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/rightBarrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="end"
        app:constraint_referenced_ids="text1,text2,text3" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintStart_toEndOf="@id/rightBarrier"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

5.6 流式布局(Flow)

xml
<!-- 类似 flex 的流式布局 -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.Group
        android:id="@+id/flowGroup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="tag1,tag2,tag3,tag4,tag5" />

    <TextView
        android:id="@+id/tag1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/tag_background"
        android:padding="8dp"
        android:text="Tag 1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tag2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/tag_background"
        android:padding="8dp"
        android:text="Tag 2"
        app:layout_goneMarginStart="8dp"
        app:layout_constraintBaseline_toBaselineOf="@id/tag1"
        app:layout_constraintStart_toEndOf="@id/tag1" />

    <!-- 更多标签... -->

</androidx.constraintlayout.widget.ConstraintLayout>

6. 布局加载优化

6.1 异步加载布局

kotlin
// 对于复杂布局,可以异步加载
class AsyncLayoutInflaterImpl : AsyncLayoutInflater {
    init {
        // 异步 inflate
        inflateAsync(R.layout.complex_layout, null) { view ->
            // 在 UI 线程设置内容
            runOnUiThread {
                setContentView(view)
            }
        }
    }
}

6.2 预加载常用布局

kotlin
class LayoutPreloader {
    private val layoutCache = LruCache<Int, View>(10)
    
    fun preloadLayout(context: Context, resourceId: Int) {
        val view = LayoutInflater.from(context).inflate(resourceId, null, false)
        layoutCache.put(resourceId, view)
    }
    
    fun getCachedView(resourceId: Int): View? {
        return layoutCache.get(resourceId)?.cloneInContext(null)
    }
}

6.3 使用布局模板

kotlin
// 为 RecyclerView Item 使用 LayoutTemplate
class AdapterWithTemplate : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = when (viewType) {
            TYPE_TEXT -> inflateTextLayout(parent)
            TYPE_IMAGE -> inflateImageLayout(parent)
            else -> inflateDefaultLayout(parent)
        }
        return ViewHolder(view)
    }
}

7. RecyclerView 性能优化

7.1 基础优化

kotlin
class OptimizedRecyclerViewAdapter : RecyclerView.Adapter<OptimizedViewHolder>() {
    
    // 1. 启用视图复用
    override fun onBindViewHolder(holder: OptimizedViewHolder, position: Int) {
        // 设置点击监听器时,使用 holder 而不是 view
        holder.itemView.setOnClickListener { 
            onItemClick(position) 
        }
    }
    
    // 2. 使用 DiffUtil 优化更新
    fun submitNewData(newList: List<Item>) {
        val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize(): Int = oldList.size
            override fun getNewListSize(): Int = newList.size
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return oldList[oldItemPosition].id == newList[newItemPosition].id
            }
            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return oldList[oldItemPosition] == newList[newItemPosition]
            }
        })
        diffResult.dispatchUpdatesTo(this)
        oldList = newList
    }
    
    // 3. 预加载图片
    override fun onBindViewHolder(holder: OptimizedViewHolder, position: Int) {
        val item = items[position]
        Glide.with(holder.itemView.context)
            .load(item.imageUrl)
            .into(holder.imageView)
    }
}

7.2 分区加载

kotlin
// 对于大量数据,使用分页加载
class PagingAdapter : RecyclerView.Adapter<ViewHolder>() {
    private val pagingDataFlow: Flow<PagingData<Item>> = pager {
        RemoteMediator(pagingSource)
    }.flow
    
    fun submitData(dataFlow: Flow<PagingData<Item>>) {
        submitForLayout(dataFlow)
    }
}

7.3 Item 动画优化

kotlin
// 使用默认动画或简化动画
recyclerView.itemAnimator = DefaultItemAnimator().apply {
    supportsChangeAnimations = false // 关闭变更动画
}

// 或使用自定义轻量动画
class LightItemAnimator : DefaultItemAnimator() {
    override fun add(holder: RecyclerView.ViewHolder): Boolean {
        // 简化入场动画
        return super.add(holder)
    }
}

8. 布局调试工具

8.1 Layout Inspector

功能

  • 实时查看 View 层级
  • 检查 View 属性
  • 检测过度绘制
  • 分析布局性能

使用

  1. Android Studio → Tools → Layout Inspector
  2. 连接设备,选择应用和 Activity
  3. 查看层级树和属性面板

8.2 Hierarchy Viewer

功能

  • 查看布局层级
  • 检测 View 重叠
  • 优化建议

启动

bash
# 从 SDK tools 目录
hierarchyviewer

8.3 Systrace/Perfetto

bash
# 使用 Perfetto 分析布局性能
perfetto-trace --time 30 com.example.app

9. 面试考点总结

9.1 基础概念

Q1: 什么是过度绘制?

  • 同一帧中像素被多次绘制
  • 浪费 GPU 资源,降低性能
  • 通过开发者选项检测

Q2: ConstraintLayout 的优势?

  • 扁平化布局,减少层级
  • 灵活的约束系统
  • 支持链、指南线、屏障等高级特性

Q3: merge 标签的作用?

  • 合并布局,减少嵌套层级
  • 必须作为根元素
  • 父布局必须是 ViewGroup

9.2 优化技巧

Q4: 如何减少 View 层级?

  1. 使用 ConstraintLayout
  2. 使用 margin 替代嵌套
  3. 使用 merge 标签
  4. 避免过度使用 FrameLayout 堆叠

Q5: 如何解决过度绘制?

  1. 移除不必要的背景
  2. 使用 drawables 替代多层 View
  3. 设置 clipChildren 和 clipToPadding
  4. 使用硬件加速

Q6: ViewStub 的使用场景?

  • 延迟加载非必需内容
  • 条件加载(如广告)
  • 提高初始加载速度

9.3 实战问题

Q7: RecyclerView 卡顿怎么办?

  1. 检查 ViewHolder 是否复用
  2. 避免在 onBindViewHolder 中做耗时操作
  3. 使用 DiffUtil 优化更新
  4. 图片懒加载和缓存

Q8: 复杂页面布局如何优化?

  1. 使用 ConstraintLayout 替代嵌套
  2. 拆分布局,使用 include 复用
  3. 使用 ViewStub 延迟加载
  4. 使用 merge 减少层级

总结

布局优化是 Android 性能优化的关键环节:

  1. 减少层级:使用 ConstraintLayout、merge、避免过度嵌套
  2. 减少过度绘制:移除多余背景、优化透明层
  3. 延迟加载:使用 ViewStub 按需加载
  4. 工具辅助:Layout Inspector、过度绘制检测

掌握这些优化技巧,可以有效提升应用的流畅度和用户体验。