Android开发中的布局很重要吗?那是当然。一切的显示样式都是由这个布局决定的,你说能不重要吗。要实现一个好的布局,不只是实现了、显示出来就完了,不管层次,堆砌代码也可以实现功能,但是这显然违背了Android布局设计的原则。可能你会说,Android布局设计哪有什么原则,我可以明确告诉你,当然有,只要有利于提高最终效果的方法、意识,我们都可以把它提升为原则。在Android布局设计中,这个最终效果就是快的页面加载速度,好的流畅度,而这个方法就太多了,鉴于本人水平有限,下面就说几种自己实践过的、切实有效的方法吧。前奏太长,罗哩罗嗦,好吧,开始说起。
最近做演出项目,碰到了一个以前没见过的错误StackOverflowError,具体错误如下:
查过资料之后才发现,原因就出在布局的嵌套层次上,说明白点儿就是“嵌套层次过深,导致栈溢出”,这样就引出了一个问题,我们在Android开发中经常听到的是内存泄漏、堆溢出(听起来都头皮发麻的OOM),好像很少听说有栈溢出的,好了,找找Android中对栈大小的限制吧。抱歉,没找到,只在支离破碎的国外论坛中间看到有说1K的,有说4K的,反正没看到有官方的说明,纠结良久还是一筹莫展,看哪位大虾如果知道的话就指点一下本鸟吧。。。。。。既然遇到了问题,还是转回头来解决问题吧,无法提高栈的大小,那就只能提高栈的使用率了,最直接的方法就是缩减层级,而且缩减层级还有一个重要的好处就是提高页面加载速度,因为Android中的布局是嵌套加载的,多一层布局就要耗费很长的加载时间。这时候我才体会到了merge的妙用,不只是merge,下面我会结合hierarchyviewer工具来分别讲述一下merge,ViewStub,include在布局优化中的作用。
1、merge
顾名思义,就是合并、融合的意思。使用它可以有效的将某些符合条件的多余的层级优化掉。使用merge的场合主要有两处:
(1) 自定义View中使用,父元素尽量是FrameLayout,当然如果父元素是其他布局,而且不是太复杂的情况下也是可以使用的
(2) Activity中的整体布局,根元素需要是FrameLayout
下面这个例子将融合这两种情况,来展示如何缩减布局层次。
总体显示界面如图所示:
其中粉红色圈住的部分为我们的自定义View。
整个界面的布局layout_mergedemo.xml如下:
自定义布局view_item.xml如下:
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:textSize="25sp"
- android:textColor="#FF0000FF"
- android:gravity="center_horizontal"
- android:text="我的家园"/>
- <com.example.myandroiddemo.MyItemView
- android:id="@+id/myitem_view"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
- </FrameLayout>
自定义View中使用下面来解析布局:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal" >
- <ImageView
- android:id="@+id/view_item_img"
- android:layout_width="50dp"
- android:layout_height="50dp"
- android:scaleType="fitXY"/>
- <TextView
- android:id="@+id/view_item_txt"
- android:layout_width="fill_parent"
- android:layout_height="50dp"
- android:gravity="center_vertical"
- android:layout_marginLeft="10dp"
- android:textSize="20sp"
- android:textColor="#FFFF0000"/>
- </LinearLayout>
整个功能开发完成之后,使用hierarchyviewer来看一下布局层次:
我们发现简单的一个功能竟然使用了六层布局,包括每个Window自动添加的PhoneWindow$DecorView和FrameLayout(id/content)。明显可以看到这个布局存在冗余,比如第二层和第三层的FrameLayout,比如第四层的MyItemView(LinearLayout子类)和第五层的LinearLayout,都可以缩减。
好了,该我们的merge标签大显身手了。 将布局这样修改:简化layout_mergedemo.xml:
简化 view_item.xml:
- <merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools">
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:textSize="25sp"
- android:textColor="#FF0000FF"
- android:gravity="center_horizontal"
- android:text="我的家园"/>
- <com.example.myandroiddemo.MyItemView
- android:id="@+id/myitem_view"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
- </merge>
- <merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools" >
- <ImageView
- android:id="@+id/view_item_img"
- android:layout_width="50dp"
- android:layout_height="50dp"
- android:scaleType="fitXY"/>
- <TextView
- android:id="@+id/view_item_txt"
- android:layout_width="fill_parent"
- android:layout_height="50dp"
- android:gravity="center_vertical"
- android:layout_marginLeft="10dp"
- android:textSize="20sp"
- android:textColor="#FFFF0000"/>
- </merge>
因为这里merge代替的布局元素为LinearLayout,而不是FrameLayout,所以我们需要在自定义布局代码中将LinearLayout的属性添加上,比如垂直或者水平布局,比如背景色等,此处设置为水平布局:
- private void initView(Context context) {
- setOrientation(LinearLayout.HORIZONTAL);
- ……
- }
OK,就是这么简单,再来看看布局层次吧:
哈哈,只有四层了,加载速度也明显加快,特别是如果布局比较复杂,子View较多的情况下,合理使用merge能大大提高程序的速度和流畅性。
但是使用merge标签还是有一些限制的,具体有以下几点:
(1)merge只能用在布局XML文件的根元素
(2)使用merge来inflate一个布局时,必须指定一个ViewGroup作为其父元素,并且要设置inflate的attachToRoot参数为true。(参照inflate(int, ViewGroup, boolean))
(3)不能在ViewStub中使用merge标签。最直观的一个原因就是ViewStub的inflate方法中根本没有attachToRoot的设置
说到这儿,merge的使用也就讲完了。还想唠叨一下做的演出项目,因为本人水平有限,并且产品提的这个显示需求确实比较BT,所以整个项目的显示框架做了很多嵌套,具体点就是ActivityGroup中嵌套ViewFlipper,然后再嵌套TabHost,然后再嵌套自己的Activity,大体数了一下,最多竟然有20几层,My God!知道Android布局设计的原则是什么吗?最好是10层以下,尽量不要超过15层,如果再多性能就会下降,也可能会出现问题,就是我们看到的StackOverFlow,这个项目被证实也确实在某些低端机上发生了这种错误,比如HTC的某某机型,OPPO的某某机型,(不要声讨我,没有恶意贬低的意思<_>),最终我使用merge缩呀缩呀,把大部分的布局都缩减到了15层以下,一测试,通过!
还要说一下,因为Window窗体(比如Activity)加载时会自动添加PhoneWindow$DecorView和FrameLayout(id/content)两层布局,所以如果我们在Activity的自定义布局根元素中使用merge,而且想设置总体背景什么的,可以用(id/content)将FrameLayout取出来,再设置属性,可以这样实现:
好了,就这样吧!!!
2、ViewStub
这是什么玩应儿呢?其实就是一个轻量级的页面,我们通常使用它来做预加载处理,来改善页面加载速度和提高流畅性,ViewStub本身不会占用层级,它最终会被它指定的层级取代。
还是说说演出项目吧,还说?对了,实践才能发现问题嘛,在哪儿发现问题就在那儿改进。由于项目中用到了比较多的动画,而且嵌套布局比较复杂,所以在Android低端机上进行页面切换时候经常让人感觉卡卡的,不怎么流畅,因为页面切换动画和标题旋转动画是同时进行的,所以为了达到更好的体验就需要使用一种方法,在动画进行时尽量的减少其他操作,特别是页面加载重绘。赶紧想办法,起初我想先将要加载的页面所有的组件都初始为gone显示状态,整个页面只留一下加载滚动条,后来发现这是不行滴,因为在Android的机制里,即使是将某一个控件的visibility属性设置为不可见的gone,在整个页面加载过程中还是会加载此控件的。再后来就用到了ViewStub,在做页面切换动画时,只在页面中放一个loading加载图标和一个ViewStub标签,像下面这样:
layout_loading.xml布局文件:
制造这个标签纯碎是为了布局重用,在项目中通常会存在一些布局公用的部分,比如自定义标题,我们不需要把一份代码Ctrl C, Ctrl V的到处都是,严重违背程序简洁化、模块儿化的设计思想,毕竟作为现代化的码农,也需要与时俱进,不再抱着以前那种卖代码的思想来编写程序了(一行代码几个钱?)。这里就不写例子了,随便网上搜一下,include的使用一大堆,简单说一下如何调用吧,比如现在有一个公用布局head.xml,如果在其他的布局中引用,只需要这样添加include标签: