0. 前言
上一篇我们分析了为什么LinearLayout会比RelativeLayout性能更高,意义在于分析了这两种布局的实现源码,算是对一个小结论的证明过程,但是对布局性能的优化效果,对这两种布局的选择远不如减少布局层级、避免过分绘制、按需加载等效果明显。所以本篇将着重总结Android布局性能优化的各种技巧。本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52923827。
1. <include/>
<include>标签可以在一个布局中引入另外一个布局,通常适合于界面布局复杂、不同界面有共用布局的APP中,比如顶部布局、侧边栏布局、底部Tab栏布局、ListView的item布局等,将这些公共布局抽取出来再通过<include>标签引用,既可以使代码结构清晰,又可统一修改使用。
比如先写一个公共的标题栏标题栏title_bar.xml(这里就不具体实现了),并在我们的主xml文件里调用<include>来使用这个公共布局:
<?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"> <include layout="@layout/title_bar"/> </RelativeLayout>
当然include也可以使用layout属性来设置布局文件的宽高和位置,但需要注意的是,必须要复写android:layout_width和android:layout_height属性才能使用其它属性(如:android:layout_grivity、android:layout_align...、android:id等),这样可以避免include引用中的子组件属性影响到include的布局效果。
比如下面这个例子给我们include进的组件设置高度和位置:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp"> <include layout="@layout/title_bar" /> <include android:layout_width="match_parent" android:layout_height="60dp" android:layout_alignParentBottom="true" layout="@layout/ title_bar"/> </RelativeLayout> </RelativeLayout>
2. 减少嵌套
这个问题我们在LinearLayout和RelativeLayout的性能对对比中已经解释过了,在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。为开发者默认新建RelativeLayout是希望开发者能采用尽量少的View层级,因为很多效果是需要多层LinearLayout的嵌套,这必然不如一层的RelativeLayout性能更好。为了确保文章的实时更新,实时修改可能出错的地方,请确保这篇是原文,而不是无脑转载来的“原创文”,原文链接为SEU_Calvin的博客。
3. <merge/>
<merge/>标签通过减少View树的层级来优化Android的布局。先来用个例子演示一下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="merge标签使用" /> </RelativeLayout>
运行后使用“DDMS -> DumpView Hierarchy for UI Automator”工具,截图如下:
最下面两层RelativeLayout与TextView就是布局中的内容,上面的FrameLayout是ActivitysetContentView添加的顶层视图。下面我们将上述布局代码中的RelativeLayout修改为merge标签再查看层级结构如下:
(1)从结果来看,FrameLayout下面直接就是TextView,与之前的相比少了一层RelativeLayout但效果相同。这个例子中TextView不需要指定任何针对父视图的布局属性,只用于添加到父视图上并显示,这种情况就可以使用<merge/>标签优化。但是我们一般很少遇到这种情况。为了确保文章的实时更新,实时修改可能出错的地方,请确保这篇是原文,而不是无脑转载来的“原创文”,原文链接为SEU_Calvin的博客。
(2)更多的<merge/>标签使用情景是在LinearLayout里面嵌入一个布局(比如使用了include),而恰恰这个布局的根节点也是LinearLayout,这样就多了一层没有用的嵌套,增加了View深度,这个时候如果我们使用merge根标签就修饰被嵌入的布局的根标签就可以避免此问题。
4. ViewStub
一个最最最可能使用到的场景就是请求网络加载列表,如果网络异常或者加载失败,我们可以显示一个用于提示用户的View,上面可以点击重新加载。当网络正常时,我们就没有理由显示这个提示View。但是如果我们通过代码逻辑这种方式实现动态更改这个View的可见性(GONE或者VISIBLE),在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化且耗费内存资源。
为了解决这个性能问题,ViewStub应运而生,ViewStub是一个轻量级的View,看不见、不占布局位置、占用资源非常小。当ViewStub被设置为可见或调用了ViewStub.inflate()的时候,ViewStub所指向的布局才会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局,ViewStub控件本身就不存在了(ViewStub对象会被置空),取而代之的是被inflate的Layout,因此它也被称做惰性控件。为了确保文章的实时更新,实时修改可能出错的地方,请确保这篇是原文,而不是无脑转载来的“原创文”,原文链接为SEU_Calvin的博客。
综上ViewStub的原理,就可以使用它来方便的在运行时,决定要不要显示某个布局。使用实例如下:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > …… <ViewStub android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/hint_fail_view" android:inflatedId="@+id/hint_fail_view" android:layout="@layout/fail_view"/> </merge>
android:layout="@layout/fail_view"指向页面加载失败的布局文件,里面包含一个id为tv的TextView。
当出现网络异常时,我们在代码里这样使用ViewStub:
private View hintFailView; if (网络异常) { if (hintFailView == null) { ViewStub viewStub = (ViewStub)this.findViewById(R.id.hint_fail_view); hintFailView = viewStub.inflate(); //注意这里 TextView textView = (TextView) hintFailView.findViewById(R.id.tv); textView.setText("网络异常"); } hintFailView.setVisibility(View.VISIBLE); }else{ //网络正常 if (hintFailView!= null) { hintFailView.setVisibility(View.GONE); } //业务逻辑 }
5. 避免OverDraw
一个简单的过度绘制例子是父控件和其上的子控件都设置了Background,那么人们是看不到被子控件所覆盖的那部分父控件背景的,这就造成了OverDraw,我们可以通过设置-开发者选项-显示GPU过度绘制来查看应用是否存在严重的OverDraw问题。
如果你发现应用中有些色块为红色,那么你可要去优化它了,你需要去根据颜色提示去找到你过度绘制的地方,需要注意的是在我们有自己的背景色的情况下,顶层View的背景色我们可以置空来优化。
setContentView(R.layout.activity_overdraw_01); getWindow().setBackgroundDrawable(null);
除了去除不必要的背景色,还有一个防止OverDraw的方法,那就是使用画布的clipRect()方法来去除自定义View中不必要的重叠绘制。
在看clipRect方法之前先看看如果将res目录下的图片文件转换为bitmap对象,这里总结了两种方法,大家可以参考使用:
InputStream is = this.getContext().getResources().openRawResource(R.drawable.icon); Bitmap mBitmap = BitmapFactory.decodeStream(is); //或者使用BitmapDrawable Bitmap mBitmap = new BitmapDrawable(is).getBitmap();
clipRect方法可以截取画布中的一个矩形区域,在此区域外的将不再绘制显示。实例如下:
/* *author SEU_Calvin in 2016/10 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = mBmp.getWidth(); int height = mBmp.getHeight(); canvas.save(); mPaint.setColor(Color.CYAN); //先在屏幕的0,0处绘制一个与我们Bitmap宽高相等的蓝色矩形 canvas.drawRect(0, 0, width, height, mPaint); canvas.restore(); canvas.save(); //裁剪画布,左上角为0,0 右下角为指定宽高的2倍和1.5倍 canvas.clipRect(0, 0, width*2, height*3/2); //以width,height为左上角绘制我们的Bitmap,由于图片的下半部分在裁剪画布之外所以不显示 canvas.drawBitmap(mBmp, width, height, mPaint); canvas.restore(); }
结果如下所示,工作过程已经在代码的注释里写的很清楚了。
6. 其他小技巧
为了控制篇幅,将一些看了让人感到惊艳的布局优化小技巧总结分享到了布局性能优化的一些技巧(二),希望可以帮助到你~
最后希望各位看官老爷们多点赞支持~