Android 性能优化之布局优化

Android系统中已经提供了非常多好用的控件,这让我们在编写布局的时候可以很轻松。但是有些时候我们可能需要反复利用某个已经写好的布局,如果你总是使用复制粘贴的方式来进行布局重用,这显然是一种很笨的做法。而Android当然也已经充分考虑到了布局重用的重要性,于是提供了这两个非常有用的标签,下面我们就来逐个学习一下。

include

标签可以允许在一个布局当中引入另外一个布局,那么比如说我们程序的所有界面都有一个公共的部分,这个时候最好的做法就是将这个公共的部分提取到一个独立的布局文件当中,然后在每个界面的布局文件当中来引用这个公共的布局。

举个例子吧,我们应该都知道,目前几乎所有的软件都会有一个头布局,头布局中可以包含界面的标题、返回按钮、以及其它一些操作功能等。那这样的一个头布局,有些软件是使用ActionBar来实现的,但是由于ActionBar的灵活性不太好,因而也有很多软件会选择自己去编写实现。那如果自己去实现的话,由于这个头布局是在所有界面都要使用的,显然我们不可能在每个界面当中都去写一遍这个头布局的代码,因此这种情况下使用标签就非常合适了。这里为了给大家演示一下,我就编写一个非常简单的头布局,在res/layout文件夹中新建toolbar.xml作为头布局,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8"?>
<merge>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">


<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />


<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="?attr/actionBarSize"
android:layout_marginRight="?attr/actionBarSize"
android:singleLine="true"
android:textSize="18dp" />

</RelativeLayout>
</android.support.design.widget.AppBarLayout>
</merge>

merge

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

  • 实例1

对于这样的布局view,在RecyclerView里面滑动时,一般会有infalte布局过多,type过多,混乱,代码不能很好的组织管理。图一的RecyclerView分为多Type,每个type自定义view,可以分开单独的view显示逻辑,见实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void onBindViewHolder(ViewHolder holder, Columns data, int position) {
switch (getItemViewType(position)) {
case TYPE_LIFE_SIR:
((ColumnLifeSirView) holder.getConvertView()).setData(data.getLife_sir_fav_list());
break;
case TYPE_LIFE:
((ColumnLifeView) holder.getConvertView()).setData(data.getLife_channel().get(0));
break;
case TYPE_TALENT:
((ColumnTalentView) holder.getConvertView()).setData(data.getTalent_channel().get(0));
break;
case TYPE_TOBE_TALENT:
((ColumnTobeTalentView) holder.getConvertView()).setData(data.getTo_be_talent());
break;
case TYPE_LIFE_HEADER:
((ColumnTalentLifeHeaderView) holder.getConvertView()).setType(ColumnTalentLifeHeaderView.TYPE_LIFE);
break;
case TYPE_TALENT_HEADER:
((ColumnTalentLifeHeaderView) holder.getConvertView()).setType(ColumnTalentLifeHeaderView.TYPE_TALENT);
break;
}
}

  • 实例2

viewpager分页加载多个tab页,对于Android平台的内存问题来说是个严重考验,由于viewpager的预缓存机制,会自动加载上下的offset页,所以在图2中,处于对内存的性能考虑,我们需要让用户滑动到当前页以后,再去做数据加载,节省内存开销以及用户的流量。
代码如下,在setUserVisibleHint方法里判断是否用户可见:

1
2
3
4
5
6
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser)
visibleLoadData();
}

图2中同样有多个type,和实例1类似,分为多type来操作。对于图2中的第一个view来说,可能很多开发者会选择用LinearLayout 嵌套RelativeLayout 然后再加横向ScrollView或者ListView或者Recyclerview来布局,但是多了一层viewGroup,提高了布局复杂度,在滑动过程中可能就会出现失帧的情况了。

如图所示,当前页面只有一层布局,只有一个最外层的RelativeLayout即可。

  • 实例3

这个view涉及到viewpager嵌套viewpager,对于FragmentManager以及ChildFragmentManager不做过多叙述,不理解可自行搜索。
图3中因为要用到对当前viewpager中view的图片高斯模糊背景,用到了v8 Render,用法自行搜索,推荐500px的第三方库500px-android-blur

善用merge标签

利用ViewHierarchy工具可以查看当前页面的布局逻辑。对于自定义复合view来说,很多人会inflate一个自定义布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<RelativeLayout 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="wrap_content">


<rec.ui.view.loopviewpager.LoopViewPager
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="200dp" />


<rec.ui.view.pageIndicator.CirclePageIndicator
android:id="@+id/banner_indicator"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/banner"
android:layout_centerHorizontal="true"
android:layout_marginBottom="5dp"
app:fillColor="#FFFFFFFF"
app:pageColor="#25000000"
app:radius="4dp"
app:strokeWidth="0dp" />

</RelativeLayout>

1
2
3
4
5
6
public class HomeItemBannerView extends RelativeLayout{
//...
private void init() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.item_home_banner, this, true);
}
}

很多开发者会这样去做复合view,这样是错误的,其实用ViewHierarchy一看就能知道,当前的view是继承自RelativeLayout,而infalte的root viewGroup也是RelativeLayout,重复嵌套同一层布局,在布局比较复杂的情况下, 这样的操作是很耗性能的。

正确的做法应该将布局文件中的RelativeLayout改为merge标签,对于xml的root节点的RelativeLayout的数据在
HomeItemBannerView中的init()方法通过代码设置即可。注意:如果是merge标签了,要注意merge的使用场景,merge标签的parent view 必须是一个ViewGroup,否则会运行报错哦。另外,在view 中inflate布局时要注意attachToRoot为true哦。如果不熟悉关于inflate的一些属性,可以自己搜索学习下。