在前面几篇文章其中。我们学习了怎样通过合理管理内存,以及高性能编码技巧的方式来提升应用程序的性能。然而实际上界面布局也会相应用程序的性能产生比較大的影响。假设布局写得糟糕的话。那么程序载入UI的速度就会很慢,从而造成不好的用户体验。那么本篇文章我们就来学习一下,怎样通过优化布局来提供应用程序的性能。还没有看过前面前面一篇文章的朋友建议能够先去阅读 Android最佳性能实践(三)——高性能编码优化 。



重用布局文件




Android系统中已经提供了许多好用的控件,这让我们在编写布局的时候能够很轻松。可是有些时候我们可能须要重复利用某个已经写好的布局,假设你总是使用复制粘贴的方式来进行布局重用,这显然是一种很笨的做法。

而Android当然也已经充分考虑到了布局重用的重要性。于是提供了<include>和<merge>这两个很实用的标签。以下我们就来逐个学习一下。


<include>




<include>标签能够同意在一个布局其中引入另外一个布局。那么比方说我们程序的全部界面都有一个公共的部分。这个时候最好的做法就是将这个公共的部分提取到一个独立的布局文件其中,然后在每一个界面的布局文件其中来引用这个公共的布局。


这里举个样例吧,我们应该都知道,眼下差点儿全部的软件都会有一个头布局。头布局中能够包括界面的标题、返回button、以及其它一些操作功能等。

那这样的一个头布局,有些软件是使用ActionBar来实现的,可是由于ActionBar的灵活性不太好,因而也有许多软件会选择自己去编写实现。那假设自己去实现的话。由于这个头布局是在全部界面都要使用的,显然我们不可能在每一个界面其中都去写一遍这个头布局的代码,因此这样的情况下使用<include>标签就很合适了。这里为了给大家演示一下,我就编写一个很easy的头布局,在res/layout目录中新建titlebar.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" >

<Button
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="Back" />

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textSize="20sp" />

<Button
android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="Done" />

</RelativeLayout>


能够看到,titlebar.xml中的布局很easy,外层是一个RelativeLayout,里面仅仅有两个Button和一个TextView。左边的Button用于实现返回功能,右边的Button用于实现完毕功能,中间的TextView则能够用于显示当前界面的标题。我们能够来预览一下titlebar的样子,例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_覆写


好的。那titlebar作为一个独立的布局如今我们已经编写完了。接下来的工作就很easy了,不管不论什么界面须要加入titlebar这个功能。仅仅须要在布局文件里引入titlebar.xml就能够了。那么比方说我们的程序其中有一个activity_main.xml文件,如今想要引入titlebar仅仅须要这样写:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

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

......

</LinearLayout>

很easy吧,一行include语句就能够搞定了。<include>标签其中能够指定一个layout属性,我们在这个layout属性中填写须要引入的布局名就能够了。并且使用这样的引入的方式,以后假设titlebar的界面有所变更。我们仅仅须要改动titlebar.xml这一个文件就能够了。而不是全部界面一个个地去改动。


等等!如今假设你执行一下程序会发现出大问题了,尽管titlebar是成功引入了。可是我们activity_main.xml中本来的界面全部都不见了!

出现这个问题是原因是由于titlebar的最外层布局是一个宽高都是match_parent的RelativeLayout,它会将整个布局都填充满,因而我们原本的布局也就看不见了。那既然问题的解决办法清楚了,相信你立马就想到应该怎么改动了,将RelativeLayout的layout_height属性改动成wrap_content不就能够了嘛。

没错。这样改动当然是没问题的。只是这样的改动方式会让全部引用titlebar的界面都受到影响,而怎样你仅仅希望让activity_main.xml这一个界面受影响的话。那么能够使用覆写<include>属性的方式。


在<include>标签其中。我们是能够覆写全部layout属性的。即include中指定的layout属性将会覆盖掉titlebar中指定的layout属性。因此。这里我们希望将titlebar的高度设置成wrap_content,就能够这样写:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/titlebar" />

......

</LinearLayout>


如今又一次执行一下程序应该就能够一切正常了,例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_android_02


除了layout_height之外。我们还能够覆写titlebar中的不论什么一个layout属性,如layout_gravity、layout_margin等,而非layout属性则无法在<include>标签其中进行覆写。另外须要注意的是,假设我们想要在<include>标签其中覆写layout属性。必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。


<merge>




<merge>标签是作为<include>标签的一种辅助扩展来使用的。它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。大家都知道,Android去解析和展示一个布局是须要消耗时间的,布局嵌套的越多,那么解析起来就越耗时,性能也就越差。因此我们在编写布局文件时应该让嵌套的层数越少越好。


在上面我们解说<include>标签的使用方法时主要介绍了它长处,可是它也存在着一个不好的地方。就是可能会导致产生多余的布局嵌套。

这里还是通过举例的方式跟大家说明一下,比方说我们须要编写一个确定取消button的公共布局,这样不论什么一个界面须要确定和取消功能时就不用再单独编写了,新建ok_cancel_layout.xml。代码例如以下所看到的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<Button
android:id="@+id/ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="OK" />

<Button
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="Cancel" />

</LinearLayout>


能够看到,这个界面也是很easy,外层是一个垂直方向的LinearLayout,LinearLayout中包括了两个button,一个用于实现确定功能,一个用于实现取消功能。如今我们能够来预览一下这个界面,例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_android_03


好的。然后我们有一个profile.xml的界面须要编辑一些内容。那么这里就能够将ok_cancel_layout这个布局引入到profile.xml界面其中,例如以下所看到的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Edit something here" />

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

</LinearLayout>

在profile.xml其中有一个EditText控件用于编辑内容,然后以下使用了<include>标签来将ok_cancel_layout布局进行引入,如今又一次执行一下程序。界面效果例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_xml_04


看上去效果很不错对吗?可是在你毫无察觉的情况下。眼下profile.xml这个界面其中事实上已经存在着多余的布局嵌套了!感觉还没写几行代码呢,怎么这就已经有多余的布局嵌套了?不信的话我们能够通过View Hierarchy工具来查看一下。例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_布局文件_05


能够看到。最外层首先是一个FrameLayout。这个无可厚非,不知道为什么最外层是FrameLayout的朋友能够去參考 ​Android LayoutInflater原理分析。带你一步步深入了解View(一)​ 这篇文章。然后FrameLayout中包括的是一个LinearLayout。这个就是我们在profile.xml中定义的最外层布局。接下来的部分就有问题了,在最外层的LinearLayout其中包括了两个元素,一个是EditText,还有一个又是一个LinearLayout,然后在这个内部的LinearLayout其中才包括了确定和取消这两个button。


相信大家已经能够看出来了吧,这个内部的LinearLayout就是一个多余的布局嵌套。实际上并不须要这样一层。让两个button直接包括在外部的LinearLayout其中就能够了。

而这个多余的布局嵌套事实上就是由于布局引入所导致的,由于我们在ok_cancel_layout.xml中也定义了一个LinearLayout。那么应该怎样优化掉这个问题呢?当然就是使用<merge>标签来完毕了。改动ok_cancel_layout.xml中的代码,例如以下所看到的:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

<Button
android:id="@+id/ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="OK" />

<Button
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="Cancel" />

</merge>


能够看到,这里我们将ok_cancel_layout最外层的LinearLayout布局删除掉,换用了<merge>标签。这就表示当有不论什么一个地方去include这个布局时,会将<merge>标签内包括的内容直接填充到include的位置,不会再加入不论什么额外的布局结构。

好的,<merge>的使用方法就是这么简单,如今又一次执行一下程序,你会看到界面没有不论什么改变,然后我们再通过View Hierarchy工具来查看一下当前的View结构,例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_布局文件_06


OK,能够看到,如今EditText和两个button都直接包括在了LinearLayout以下,我们的profile.xml其中也就不存在多余的布局嵌套了。


仅在须要时才载入布局




有的时候我们会遇到这样的场景。就是某个布局其中的元素许多,但并非全部元素都一起显示出来的,而是一般情况下仅仅显示部分经常使用的元素。而那些不经常使用的元素仅仅有在用户进行特定操作的情况下才会显示出来。


这里举个大家都很熟悉的样例,我们在加入联系人的时候事实上能够编辑的字段真的许多,姓名、电话、email、传真、住址、昵称等等等等,但事实上基本上大家最经常使用的就是填一个姓名,填一个电话而已。那么将这么多繁杂的字段都一起显示在界面上事实上并非一种很好的做法。由于大多数人都是用不到这些字段的。比較聪明的做法就是把最经常使用的姓名和电话显示在界面上,然后给用户提供一个加入许多其它字段的选项。当用户真的有须要去加入其它信息的时候,我们才将另外的元素显示到界面上。


说到实现这样一个功能,我相信大多数人的第一反应就是将不经常使用的元素使用INVISIBLE或者GONE进行隐藏,然后当用户须要使用这些元素的时候再把它们置成VISIBLE显示出来。使用这样的方式肯定能够实现功能的,可是性能方面就表现得一般了,由于即使是将元素进行隐藏。它们事实上还是在布局其中的,每一个元素还拥有着自己的宽、高、背景等等属性,解析布局的时候也会将这些隐藏的元素一一解析出来。


那么我们怎样才干让这些不经常使用的元素仅在须要时才去载入呢?Android为此提供了一种很轻量级的控件。ViewStub。ViewStub虽说也是View的一种,可是它没有大小,没有绘制功能,也不參与布局,资源消耗很低,将它放置在布局其中基本能够觉得是全然不会影响性能的。


以下我们就来学习一下怎样使用ViewStub来完毕仅在须要时才去载入布局的功能,眼下profile.xml中仅仅有一个EditText用于编辑信息。那么比方说我们还有另外三个不太经常使用的EditText,就能够将它们定义在另外一个布局文件其中。新建profile_extra.xml文件。代码例如以下所看到的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<EditText
android:id="@+id/edit_extra1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:hint="Extra field 1" />

<EditText
android:id="@+id/edit_extra2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Extra field 2" />

<EditText
android:id="@+id/edit_extra3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Extra field 3" />

</LinearLayout>

能够看到。在profile_extra.xml这个布局文件其中定义了三个EditText。也就是用于编辑那些不经常使用信息的控件,如今我们能够来预览一下这个布局,例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_布局文件_07


眼下profile_extra.xml是一个独立的布局,和profile.xml这个布局文件是全然没有关系的。

接下来我们改动profile.xml文件里的代码,例如以下所看到的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="@string/edit_something_here" />

<Button
android:id="@+id/more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:text="More" />

<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/profile_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

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

</LinearLayout>

能够看到,这里我们新增了一个More Button,这个button就是用于去载入那些不经常使用的元素的,然后在Button的以下定义了一个ViewStub。

在ViewStub控件中,我们先是通过id属性给它指定了一个唯一标识。又通过layout属性将profile_extra布局传入进来。接着给ViewStub指定了一个宽高。注意。尽管ViewStub是不占用不论什么空间的。可是每一个布局都必须要指定layout_width和layout_height属性,否则执行就会报错。


接着改动ProfileActivity中的代码。在Activity中加入More Button的点击事件。并在点击事件中进行例如以下逻辑处理:

private EditText editExtra1;
private EditText editExtra2;
private EditText editExtra3;

public void onMoreClick() {
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
if (viewStub != null) {
View inflatedView = viewStub.inflate();
editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);
editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);
editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);
}
}


当点击More Button之后我们首先会调用findViewById()方法将ViewStub的实例获取到,拿到ViewStub的实例之后就很easy了,调用inflate()方法或者setVisibility(View.VISIBLE)都能够将隐藏的布局给载入出来。而载入的这个布局就是刚才在XML其中配置的profile_extra布局。


调用inflate()方法之后会将载入出来的布局进行返回,之后我们就能够对这个布局进行随意的操作了。再次隐藏显示。或者获取子元素的实例等。注意这里我对ViewStub的实例进行了一个非空推断,这是由于ViewStub在XML中定义的id仅仅在一開始有效。一旦ViewStub中指定的布局载入之后。这个id也就失败了。那么此时findViewById()得到的值也会是空。


如今我们又一次执行一下程序,界面例如以下图所看到的:


Android最佳性能实践(四)——布局优化技巧_xml_08


能够看到,界面上仅仅有一个Morebutton,ViewStub是全然不占用不论什么空间的。然后点击一下Morebutton,新的界面例如以下所看到的:


Android最佳性能实践(四)——布局优化技巧_xml_09


没有问题。profile_extra.xml中定义的布局已经载入出来了,并且显示的位置也是在Morebutton和OKbutton之间,正是ViewStub控件定义的位置,说明我们确实已经将ViewStub成功使用起来了。


另外须要提醒大家一点,ViewStub所载入的布局是不能够使用<merge>标签的,因此这有可能导致载入出来的布局存在着多余的嵌套结构,详细怎样去取舍就要依据各自的实际情况来决定了。对于那些隐藏的布局文件结构相当复杂的情况,使用ViewStub还是一种相当不错的选择的,即使添加了一层没用的布局结构。仍然还是利大于弊。


经过四篇文章的学习。我们已经掌握了不少能够提高Android应用程序性能的技巧。这些技巧多数都是来自于Android Doc,我也是从中选取了一些感觉比較实用的部分。然后又加入了自己的理解呈现给大家。假设大家想要继续学习许多其它关于性能优化的技巧。能够到这个网址上阅读许多其它内容 ​

关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

微信扫一扫下方二维码就可以关注:

Android最佳性能实践(四)——布局优化技巧_xml_10         Android最佳性能实践(四)——布局优化技巧_嵌套_11