布局优化
在进行Android应用的界面编写时,如果创建的布局层次结构比较复杂,View树嵌套的层次比较深,那么将会使得界面展现的时间比较长,导致应用运行起来越来越慢。Android布局的优化是实现应用响应灵敏的基础。遵循一些通用的编码准则有利于实现这个目标。
include 标签共享布局
在使用XML文件编写Android应用的界面时,经常会遇到在不同的页面中需要实现相同的布局,这时候就会写出重复的代码。例如由于几乎每个页面都有标题栏,因此,在每个页面中都要实现XML代码。这种方式显然是不建议的,最佳实践是将通用的布局抽取出来,独立成一个XML文件,然后在需要用到的页面中使用include便签引入进来,不仅减少了代码量,而且在需要修改标题栏的时候,只需修改这个独立的文件,而不是到每个页面中进行重复的修改劳动。假设我们抽取出来的标题栏布局名为layout_common_titile.xml,语句如下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--此处只是演示,实际项目代码要比这个复杂一些-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题栏"
android:layout_centerInParent="true"
/>
</RelativeLayout>
在页面MainActivity的布局中,使用到这个标题栏,引入的方式如下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.okii.layoutoptimize.MainActivity">
<!--引入外部共享布局 layout_common_title-->
<include layout="@layout/layout_common_title"
android:layout_height="56dp"
android:layout_width="match_parent"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
ViewStu标签实现延迟加载
ViewStub 是一种不可视并且大小为0的视图,它可以延迟到运行时才填充(inflate)布局资源。当 ViewStub 设置为可见或者被inflate之后,就会填充布局资源,之后这个ViewStub就会被填充的视图所代替,跟普通的视图不再有任何区别。ViewStub 的使用场景在于某个页面需要根据用户交互或者其他条件动态调整显示的视图,例如某个页面是从服务端获取数据进行展现的,正常情况下会展示获取到的数据,但在网络不可用的情况下,需要展示一张不可用的图片和相关提示信息,大多数情况下,这个不可用的视图是不需要显示出来的,如果不使用ViewStub,只是把不可用视图隐藏,那么它还是会被inflate并占用资源,如果使用ViewStub,则可以在需要显示的时候才会进行视图的填充,从而实现延迟加载的目的。假设页面加载数据失败时会展示一张网络出错的图片,这个图片的布局如下所示,它就是我们的ViewStub 会显示的视图。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/error_view"
>
<!--此处只是为了演示,实际项目代码要增加其他提示信息-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/network_error"
/>
</RelativeLayout>
页面使用ViewStub加载这个布局的代码如下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.okii.layoutoptimize.MainActivity">
<ViewStub
android:id="@+id/error_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/layout_network_error"
android:layout_centerInParent="true"
android:inflatedId="@+id/error_view"
/>
</RelativeLayout>
其中的android:inflatedId
的值是在Java
代码中调用 ViewStub
的 inflate()
或者 setVisibility()
方法时返回的ID
,这个ID
就是被填充的View
的ID
。
merge 标签减少布局层次
merge
标签在某些场景下可以用来减少布局的层次,由于 Activity
的ContentView
的最外层容器是一个 FrameLayout
,因此, 当一个独立的布局文件最外层是FrameLayout
, 且这个布局不需要设置背景 android:background
或者 android:padding
等属性时,可以使用<merge>
标签来代替<FrameLayout>
标签,从而减少一层多余的 FrameLayout
布局。另外一种可以使用<merge>
标签的情况是当前布局作为另外一个布局的子布局,使用<include>
标签引入时,我们使用前面include一节的例子,可以考虑把layout_common_title.xml 文件中的RelativeLayout 替换成merge,代码如下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--此处只是演示,实际项目代码要比这个复杂一些-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题栏"
android:layout_centerInParent="true"
/>
</RelativeLayout>
尽量使用CompoundDrawable
在LinearLayout布局中,如果存在相邻的ImageView和TextView,语句如下。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.okii.layoutoptimize.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<ImageView
android:id="@+id/image_view"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
/>
</RelativeLayout>
那么一般来说可以使用compound drawable 合二为一成为一个TextView, ImageView中的图片变成TextView的如下属性之一:drawableTop,drawableLeft,drawableRight 或者drawableBottom。原来两个View之间的间隔使用TextView的属性drawablePadding来代替,语句如下。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.okii.layoutoptimize.MainActivity">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:drawablePadding="10dp"
android:drawableBottom="@mipmap/ic_launcher"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
使用Lint
Android Lint 除了可以对Java代码进行静态检查之外,也可以用来为检查应用的布局是否存在可优化的地方。Lint的如下规则是专为为优化布局设置的:
- AndroidLintUseCompoundDrawables: 就是前面介绍的尽量使用CompoundDrawable。
- MergeRootFrame: 就是前面介绍的merge标签减少布局层次。
- TooManyViews: 单个布局中存在太多的View,默认情况下,单个布局中View的个数最多只能是80个,可以考虑使用CompoundDrawables 等来减少View的个数。
- TooDeepLayout: 避免过深的布局嵌套,默认情况下,单个布局中最多层级是10,可以考虑使用RelativeLayout来减少布局的层次。
- UselessParent: 当一个布局满足以下条件时,可以将它移除,并将它的子View移动到它的父容器中,以得到更扁平的布局层次:(1)这个布局中只有一个子View,也就是子View不存在兄弟View; (2) 这个布局中不是一个ScrollView或者根布局;(3) 这个布局没有设置背景android:background。
- NestedWeights: android:layout_weight 属性会使得View控件被测量两次,当一个LinearLayout拥有非0dp值的android:layout_weight属性,这时如果将它嵌套在另一个拥有非0dp的android:layout_weight的LinearLayout,那么这时测量的次数将呈指数级别增加。
- UselessLeaf: 一个布局如果既没有子View也没有设置背景,那么它将是不可见的,通常可以移除它,这样可以得到更扁平和高效的布局层级。
- InefficientWeight: 当LinearLayout中只有一个子View定义了android:layout_weight属性,更高性能的做法是使用0dp的android:layout_height或者android:layout_width来替换它,这样这个子View就不需要测量它自身对应的大小。