布局优化

优化思想

减少布局文件的层级。

优化方法

删除无用的控件和布局

合理的选择控件容器

  • 多使用LinearLayout和FrameLayout,因为这两种布局简单高效。

  • 能使用RelativeLayout和LinearLayout中的一种来实现,优先选择使用LinearLayout,因为RelativeLayout功能复杂,绘制起来需要花费更多的时间,参考Android中RelativeLayout和LinearLayout性能分析&Android性能优化之减少UI过度绘制

  • 如果使用LinearLayout和FrameLayout需要嵌套才能实现效果,这时候可以选择使用RelativeLayout,因为嵌套增加了布局的层级,降低了绘制的效率。

合理使用include,merge和ViewStub

include标签

include主要是为了布局的重用。

1
2
3
4
5
6
7
8
9
<?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>

注意:

除了id属性之外,include标签支持layout_开头的属性,比如layout_width,layout_height。

对于id属性,如果include标签和包裹的布局同时设置了id属性,那么使用include标签设置的id属性。

如果在include标签中设置了layout_开头的属性,那么必须存在layout_width,layout_height属性,否则其他属性无效。

merge标签

主要和include标签一起使用,减少布局的层级,主要有以下两个应用场景:

1,当Activity的根布局是FrameLayout的时候,可以用merge替代FrameLayout布局,减少布局层级,因为contentView根布局是FrameLayout布局。

2,当一个布局要include进去另一个布局的时候,如果该布局和父布局是相同的布局,那么可以将该布局的根布局设置为merge。

merge的效果是FrameLayout的效果,但是如果将一个根布局是merge的布局include进去另一个布局的时候,merge将被忽略,这个布局的控件将完全被添加到父布局里面,控件展示效果将完全由父布局进行决定。比如,一个merge布局被include进一个FrameLayut,则控件按照FrameLayout的效果进行展示;如果被include进一个LinearLayout布局,则控件按照线性布局的效果进行展示。

使用merge需要注意的事项:

  • merge只能当作根布局使用
  • merge不是view,也不是viewgroup,他相当于声明了一些视图等待被添加
  • 因为merge不是view,所以merge标签设置的属性都是无效的
  • 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也是必须为merge下的视图指定一个父亲节点。

自定义view的时候,自定义View的布局文件,根节点如果设置成merge的话,也可以减少布局的层级,如果不在代码中进行属性设置的话,merge布局将按照帧布局的效果进行展示。

自定义view的时候,自定义View如果继承自LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。不过需要在布局代码中设置布局的属性,例如使用 setOrientation(VERTICAL)设置为垂直效果。如果不在代码中进行设置属性的话merge布局将按照帧布局的效果进行展示。例子参考:Android 布局优化之include与merge

参考链接:

Android 布局优化之include与merge
android布局优化-merge
使用merge标签替换FrameLayout布局
android之merge布局
Android Merge详解
Android LayoutInflater深度解析 给你带来全新的认识

ViewStub

ViewStub继承自View,不可视,宽和高都是零,本身不参与任何的布局和绘制过程。ViewStub主要是为了实现延迟加载,提高程序的初始化性能。

应用场景:某些特殊情况下才需要展示的页面,比如提示错误页面。

用法:

1
2
3
4
5
6
7
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />

用ViewStub加载layout文件时,可以调用 setVisibility(View.VISIBLE) 或者 inflate():

1
2
3
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

注意:

  • 一旦ViewStub visible/inflated,则ViewStub将从视图框架中移除,其id stub_import 也会失效
  • ViewStub被绘制完成的layout文件取代,并且该layout文件的rootview的id是android:inflatedId指定的id panel_import,root view的布局和ViewStub视图的布局保持一致
  • 虽然使用View.GONE也可以让一个布局在初始化的时候不显示,但是与ViewStub是有区别的:ViewStub只会在需要的时候渲染;而设置View.GONE,虽然布局不可见,在初始化布局树的时候,布局就已经被添加到了布局树里面,所以ViewStub的初始化效率更高。

建议阅读:ViewStub 用法详解中的例子。

背景优化

去掉window的默认的背景

当我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,这个背景是被DecorView持有的。当我们的自定义布局时又添加了一张背景图或者设置背景色,那么DecorView的background此时对我们来说是无用的,但是它会产生一次Overdraw,带来绘制性能损耗。去掉window的背景可以在onCreate()中setContentView()之后调用

1
getWindow().setBackgroundDrawable(null);

或者在theme中添加

1
android:windowbackground="null";

去掉其他不必要的背景

有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。再比如如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也同样可以解决问题。这里只简单举两个例子,我们在开发过程中的一些习惯性思维定式会带来不经意的Overdraw,所以开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上。

善用 ClipRect & QuickReject

为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

Android画布剪裁函数clipRect详解

善用draw9patch

给ImageView加一个边框,你肯定遇到过这种需求,通常在ImageView后面设置一张背景图,露出边框便完美解决问题,此时这个ImageView,设置了两层drawable,底下一层仅仅是为了作为图片的边框而已。但是两层drawable的重叠区域去绘制了两次,导致overdraw。优化方案: 将背景drawable制作成draw9patch,并且将和前景重叠的部分设置为透明。由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次overdraw。 但是背景图片必须制作成draw9patch才行,因为Android 2D渲染器只对draw9patch有这个优化,否则,一张普通的Png,就算你把中间的部分设置成透明,也不会减少这次overdraw。

避免“OverDesign“

overdraw会给APP带来不好的体验,overdraw产生的原因无外乎:复杂的Layout层级,重叠的View,重叠的背景这几种。开发人员无节制的View堆砌,究其根本无非是产品无节制的需求设计。有道是“由俭入奢易,由奢入俭难”,很多APP披着过度设计的华丽外衣,却忘了简单易用才是王道的本质,纷繁复杂的设计并不会给用户带来好的体验,反而会让用户有压迫感,产品本身也有可能因此变得卡顿。当然,一切抛开业务谈优化都是空中楼阁,这就需要产品设计也要有一个权衡,在复杂的业务逻辑与简单易用的界面展现中做一个平衡,而不是一味的OverDesign。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器