布局是 Android 应用中直接影响用户体验的关键部分。如果实现不当,您的布局可能会导致应用界面缓慢且需要占用大量内存。Android SDK 包含一些工具,有助于您识别布局性能方面的问题,将这些工具与这里的课程相结合,您将能够以尽可能少的内存占用量实现流畅的滚动界面。
优化布局层次结构
使用基本布局结构可打造最高效的布局是一种常见的误解。无论如何,您添加到应用中的每个微件和布局都需要进行初始化、布局和绘制。例如,使用 LinearLayout
的嵌套实例会导致视图层次结构过深。此外,嵌套多个使用 layout_weight
参数的 LinearLayout
实例成本非常高,因为每个子级都需要测量两次。如果要反复扩充布局(例如,在 ListView
或 GridView
在本课中,您将学习如何使用 Hierarchy Viewer 和 Lint 来检查和优化布局。
检查布局
Android SDK 工具包含一个名为 Hierarchy Viewer 的工具,可让您在应用运行时分析布局。使用此工具可帮助您发现布局性能方面的瓶颈。
通过 Hierarchy Viewer,您可以在已连接的设备或模拟器上选择正在运行的进程,然后显示布局树。各个块上的信号灯代表其测量、布局和绘制性能,有助于您识别潜在问题。
例如,图 1 显示了用作 ListView
图 1. ListView
Hierarchy Viewer 会显示可用设备及其正在运行的组件的列表。从 Windows 标签中选择您的组件,然后点击 Hierarchy View 查看所选组件的布局层次结构。例如,图 2 显示了图 1 所示列表项的布局。
图 2. 图 1 中的布局的布局层次结构,使用 LinearLayout
修改布局
由于上述布局性能因嵌套的 LinearLayout
而有所下降,因此可以通过展平布局(使布局变浅变宽,而非变窄变深)来提高性能。作为根节点的 RelativeLayout
允许此类布局。因此,将此设计转换为使用 RelativeLayout
图 4. 图 1 中的布局的布局层次结构,使用 RelativeLayout
。
这些优势可能看起来并不显眼,但却会成倍地提升性能,因为此布局用于列表中的每一项。
大部分差异都是由于在 LinearLayout
设计中使用 layout_weight
在一些复杂的布局中,系统可能会浪费时间和资源来多次测量同一个界面元素。这种现象称为 Double Taxation。如需详细了解 Double Taxation 以及如何防止出现这种情况,请参阅性能和视图层次结构。
使用 Lint
对布局文件运行 lint 工具来搜索可能的视图层次结构优化始终是一种很好的做法。Lint 取代了 Layoutopt 工具,并提供了更加强大的功能。下面显示了 lint 规则的一些示例:
- 使用复合可绘制对象 - 包含
ImageView
- 和
TextView
- 的
LinearLayout
- 合并根框架 - 如果
FrameLayout
- 无用的叶子 - 没有子级或没有背景的布局通常可以移除(因为它不可见),以使布局层次结构更加扁平高效。
- 无用的父级 - 如果一个布局含有子级但没有同级、不是
ScrollView
- 深层布局 - 嵌套过多的布局会降低性能。考虑使用
RelativeLayout
- 或
GridLayout
Lint 的另一项优势是,它已集成到 Android Studio 中。Lint 会在您编译程序时自动运行。借助 Android Studio,您还可以针对特定编译变体或针对所有编译变体运行 lint 检查。
您还可以在 Android Studio 中使用 File > Settings > Project Settings 选项管理检查配置文件和配置检查。系统会显示“Inspection Configuration”页面,其中包含支持的检查。
图 5. 检查配置
Lint 能够自动修复某些问题、提供用于解决其他问题的建议,以及直接跳转到违规代码进行审核。
通过重复使用布局
尽管 Android 通过各种微件来提供可重复使用的小型互动元素,但您可能还需要重复使用需要特殊布局的大型组件。为了高效地重复使用完整的布局,您可以使用 和 重复使用布局特别强大,因为它允许您创建可重复使用的复杂布局。例如,“是/否”按钮面板,或包含说明文本的自定义进度条。这也意味着您可以单独提取、管理多个布局中的任何常见应用元素,然后将其添加到各个布局中。因此,尽管您可以通过编写自定义 View
创建可重复使用的布局
如果您已经知道您想要重复使用的布局,请创建一个新的 XML 文件并定义布局。例如,下面的布局定义了要添加到每个 Activity 中的标题栏 (titlebar.xml
):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/titlebar_bg" tools:showIn="@layout/activity_main" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/gafricalogo" /> FrameLayout>
根 View
注意:上述 XML 中的 tools:showIn
使用 标记
在要添加可重复使用的组件的布局中,添加
下面是布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/app_bg" android:gravity="center_horizontal"> <include layout="@layout/titlebar"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello" android:padding="10dp" /> ... LinearLayout>
您还可以通过在 标记中指定已添加布局的根视图的所有布局参数(任何 android:layout_*
<include android:id="@+id/news_title" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/title"/>
不过,如果您想要使用 标记替换布局属性,则必须同时替换 android:layout_height
和 android:layout_width
,其他布局属性才能生效。
使用 标记
在一个布局中包含另一个布局时, 标记有助于消除视图层次结构中的冗余视图组。例如,如果您的主布局是一个垂直 LinearLayout
,其中两个连续视图可以在多个布局中重复使用,那么放置这两个视图的可重复使用布局需要有自己的根视图。不过,如果使用另一个 LinearLayout
作为可重复使用的布局的根,则会导致垂直 LinearLayout
内出现垂直 LinearLayout
。嵌套的 LinearLayout
为了避免包含此类冗余视图组,您可以改用
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/add"/> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/delete"/> merge>
现在,当您将该布局添加到其他布局中(使用 标记)时,系统会忽略 元素并直接在布局中放置两个按钮,以代替
视图加载延迟
有时,您的布局可能需要很少使用的复杂视图。无论是作品详情、进度指示器还是撤消消息,您都可以通过仅在需要时加载这些视图来减少内存使用量并加快渲染速度。
如果您具有应用将来可能需要的复杂视图,则可以使用延迟加载资源这项重要的方法。您可以通过为复杂且很少使用的视图定义 ViewStub
定义 ViewStub
ViewStub
是一种没有任何维度的轻量型视图,它不会绘制任何内容或参与布局。因此,扩充和离开视图层次结构的成本比较低。每个 ViewStub
只需包含 android:layout
以下 ViewStub
<ViewStub android:id="@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" />
加载 ViewStub 布局
当您想要加载 ViewStub
指定的布局时,可通过调用 setVisibility(View.VISIBLE)
将其设为可见,或调用 inflate()
。
KOTLIN
findViewById<View>(R.id.stub_import).visibility = View.VISIBLE // or val importPanel: View = findViewById<ViewStub>(R.id.stub_import).inflate()
JAVA
findViewById(R.id.stub_import).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
注意:inflate()
方法会在完成后返回扩充的 View
。因此,如果您需要与布局互动,则无需调用 findViewById()
。该布局可见/扩充之后,ViewStub
元素将不再是视图层次结构的一部分。它会被替换为经过扩充的布局,该布局的根视图的 ID 便是由 ViewStub 的 android:inflatedId
属性指定的那个 ID。(为 ViewStub
指定的 ID android:id
仅在 ViewStub
注意:ViewStub
的一个缺点是它目前不支持要扩充的布局中的