Android 性能优化之布局

Android 基本的五大布局,LinearLayout、FrameLayout、RelativeLayout、AbsoluteLayout和TableLayout,前三种布局常用到。其中FrameLayout效率最高,LinearLayout次之,RelativeLayout消耗最多,但功能最强大。FrameLayout是里面包含的控件,一层一层叠加放置,属性最少,除了通用的marginLeft marginTop marginStart等几个属性,只有一个,gravity,控制里面的view的所在的位置。FrameLayout一般用到根节点,比如视频播放,有暂停、快进等,此时用帧布局最合适。再有一些图片,要一个个显示,此时也适合用帧布局。
 

<?xml version="1.0" encoding="utf-8"?>   
  <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"   
 android:layout_width="fill_parent" android:layout_height="fill_parent" 
 android:gravity="center">   
     <TextView android:layout_width="fill_parent" android:layout_height="fill_parent"  
 android:background="#ff000000" android:gravity="center" android:text="1"/>   
     <TextView android:layout_width="fill_parent" android:layout_height="fill_parent"  
 android:background="#ff654321" android:gravity="center" android:text="2"/>   
      <TextView android:layout_width="50dp" android:layout_height="50dp"  
 android:background="#fffedcba" android:gravity="center" android:text="3"/>   
  </FrameLayout>

LinearLayout是线性布局,可以设置水平或竖直,里面的控件view是一个挨着一个,默认是水平。LinearLayout中的子元素属性android:layout_weight,它用于子元素在剩余空间中占有的大小比例。如果一行中有两个等长的文本框,weight值可以是同为1。如果一行中有两个不等长的文本框,那么他们的android:layout_weight值分别为1和2,并且android:layout_width="fill_parent",那么第一个文本框将占据剩余空间的三分之二,第二个文本框将占据剩余空间中的三分之一;如果android:layout_width="0dp"时,两个框的宽度相反,第一个占三分之一,第二个占三分之二。权重属性导致LinearLayout内部绘制两次,降低效率,所以尽量不要用此属性。LinearLayout一般用于布局的根节点,布局内部分为n个模块,模块之间无关联,纯粹的一个接一个,模块一般用RelativeLayout,功能强大。

RelativeLayout 相对布局,适合作为一个模块的容器,经常是用第一个放入的view为标准,其他的控件在它的基础上摆放,经常用的属性
android:layout_toLeftOf —— 该组件位于引用组件的左方

android:layout_toRightOf —— 该组件位于引用组件的右方 
     android:layout_above —— 该组件位于引用组件的上方 
     android:layout_below —— 该组件位于引用组件的下方 
        android:layout_alignParentLeft —— 该组件是否对齐父组件的左端 
        android:layout_alignParentRight —— 该组件是否齐其父组件的右端 
        android:layout_alignParentTop —— 该组件是否对齐父组件的顶部 
        android:layout_alignParentBottom —— 该组件是否对齐父组件的底部 
     android:layout_centerInParent —— 该组件是否相对于父组件居中 
     android:layout_centerHorizontal —— 该组件是否横向居中 
     android:layout_centerVertical —— 该组件是否垂直居中


有一点要说明,LinearLayout里面的view不可能重叠在一起,而RelativeLayout中的布局是可以重叠的。

布局优化,最主要的是层级的优化,一般而言,优秀的app标准是普通布局层级嵌套不要超过三层,这样在绘制布局才能快速,并且findViewById时,才能迅速找到对应的view,缩短时间。有的布局嵌套六七层,这时候就要想办法拼命减少层级了。ListView和RecyclerView嵌套item时,item布局越少越好,效果越明显。很容易碰到一个问题是item的宽度和高度显示的不太正确,前面博客讲解LayoutInflater 时提过,当时提了两种解决方法,

一  使用 Inflate(resId , parent, false )方法;

二  在item的跟布局上在加上一层布局,相对布局或线性布局都行,

android:layout_width="match_parent"
 android:layout_height="match_parent"

当我们追求技术时,当然选择第一种方案,不要随意的增加布局层数。并且刷新listview时,做到局部刷新,这样程序更快,更省内存。

布局优化,接下来是view的运用,比如要显示一个ImageView和一个TextView,我们完全可以利用TextView的属性,拔这两个view用一个TextView来显示,提高效率。再有就是使用自定义控件,把一些控件用view来绘制出来,提高效率。一般控件上下之间有分割线时,我们都会用view来实现,其实在LinearLayout中,我们可以用LinearLayoutCompat来代替LinearLayout,向下兼容,直接绘制出分割线,写个shape,设置属性引用。 

<android.support.v7.widget.LinearLayoutCompat 
             android:layout_width="match_parent" 
             android:layout_height="wrap_content" 
             android:layout_gravity="center|center_horizontal" 
             android:orientation="vertical" 
             app:divider="@drawable/line" 
             app:showDividers="middle|beginning|end">

line是用xml写的shape,

<?xml version="1.0" encoding="utf-8"?> 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"> 
     <solid android:color="@color/blue" /> 
     <!--需要设置高度,否则不显示--> 
     <size android:height="1px" /> 
 </shape>

布局优化,还有几个小杀器,include,merge和ViewStub 三个标签,include 是重用,比如每个页面都有title布局,布局一样,位子内容不一样,使用include,在activity中设置标题名;merge是减少层级意思,意思是里面包含的view和merge的父容器使用一个布局属性,例如你的主布局文件是LinearLayout竖直布局,里面包含引入了一个LinearLayout竖直布局,此时,可以把里面的这个LinearLayout替换为merge,相当于merge不在了,但布局xml看着清晰;ViewStub 最大的优点是当你需要时才会加载,平时是一个不可见的view。尤其是注册需要填xt,此时使用ViewStub 特别合适。

布局优化的另一个领域就是过度绘制了。

设置—->开发者选项—->显示GPU过度绘制,按不同颜色值来显示布局的过度绘制,绘制的层次从最优到最差:蓝,绿,淡红,红。前三种还能接受,第四种就不行了,要想办法优化,降低过度绘制有几种方法:

一、每个activity都有默认的背景,window会被默认添加一个纯色的背景,我们可以 getWindow().setBackgroundDrawable(null);去掉背景色。

二、layout布局中,根节点的背景色能去掉就去掉,尽量不要重合。ListView如果铺满屏幕,item有背景色,则Listview的背景色和根节点的背景色都可以去掉。

三、我们使用ViewPager+Fragment模式,如果每个Fragment都有背景色,则Activity中的可以省略。

四、用好现有的控件属性,比如Listview的分割线,尽量少在item里面重新绘制,可以使用list.setDevice()方法设置。

五、在写自定义控件view时,onDraw()方法要用好,绘制时尽量不要重叠绘制。