性能优化包含的部分不少,包括布局、内存、耗电、流量等等,其中布局优化是最容易掌握,也最容易被你们所忽视的一个方面,今天,就来介绍一下有关布局优化的一些技巧。android
当咱们的布局中有多个相同的布局时,可使用include
标签来进行布局的复用,这样,当视觉须要修改单个Item
的间距,文字大小时,只须要修改一个布局就能够了,例如像下面这种状况,咱们就可使用include
标签来实现: canvas
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include android:id="@+id/include_1" layout="@layout/layout_is_merge" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<include android:id="@+id/include_2" layout="@layout/layout_is_merge" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<include android:id="@+id/include_3" layout="@layout/layout_is_merge" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>
复制代码
单个Item
的布局为:性能优化
<?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">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_content_1"
android:text="tv_content_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_content_2"
android:text="tv_content_2"
android:layout_marginLeft="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</merge>
复制代码
要点:bash
<include>
所指定的layout
中包含的控件,那么就须要给<include>
指定id
,再经过它来寻找子容器中的控件。<include>
标签中,能够指定layout_xxx
属性,它将会覆盖子布局中的根标签中的属性。当出现下面这种状况:一个xml
布局文件的根节点是一个FrameLayout
,而且它没有一个有用的背景,那么当该xml
布局文件渲染出的ViewGroup
被添加到父布局中时,链接处就会出现一个多余的节点,而采用<merge>
标签能够去掉这一无用节点,从而下降布局的层级。异步
例如,在上面的例子当中,咱们使用了<merge>
标签的情形为: async
<merge>
标签,那么情形为:
LayoutInflater
的inflate
方法渲染出以<merge>
做为根节点标签的xml
文件时,必须传入不为null
的root
参数,且attachToRoot
参数必须为true
。<merge>
只可做为xml
的根节点。<merge>
既不是View
也不是ViewGroup
,它只是表示一组等待被添加的视图,所以,对它设定的任何属性都是无用的。当咱们的布局中,存在一些须要按序加载的控件,那么就可使用ViewStub
标签预先声明,当状况知足时再去实例化ViewStub
中所声明的布局,其用法以下:ide
ViewStub
,而且经过layout
标签指定对应的布局layout_stub
:<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/view_stub"
android:inflatedId="@+id/view_inflated"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/layout_stub"/>
</LinearLayout>
复制代码
ViewStub
,再调用它的inflate
或者setVisibility(View.VISIBLE)
方法,其返回的布局就是layout=
所指定的布局的根节点:private void inflateIfNeed() {
//1.获取到布局中的ViewStub。
mViewStub = (ViewStub) findViewById(R.id.view_stub);
//2.调用其inflate方法实例化它所指定的layout。
mStubView = mViewStub.inflate();
}
复制代码
要点:工具
ViewStub
只能调用一次inflate
或者setVisibility(View.VISIBLE)
方法,而且调用完以后它将再也不可用,ViewStub
原先所在位置将被替换成为layout
参数所指定的布局的根节点,而且其根节点的id
值将变成android:inflatedId
所指定的值:
当咱们须要经过父容器来容纳多个子控件时,如何选择父容器,将会影响到布局的效率,而对于父容器的选择,有如下几点原则:布局
FrameLayout
、不带layout_weight
参数的LinearLayout
、RelativeLayout
,这里选取的标准为带有layout_weight
的LinearLayout
或者RelativeLayout
会测量两次。LinearLayout
时,应当尽可能避免使用layout_weight
参数。RelativeLayout
嵌套RelativeLayout
。Google
新推出的ConstraintLayout
布局。当咱们存在多种不一样大小、颜色或者图文混排须要显示时,咱们每每会利用多个TextView
来进行组合,可是某些效果经过一个TextView
就能够实现,通常来讲,利用SpannableStringBuilder
能够经过单个TextView
实现多种不一样的布局,更多Span
的用法能够参考这篇文章:Android 中各类 Span 的用法,下面以不一样大小的TextView
为例:性能
private void useSpan() {
TextView textView = (TextView) findViewById(R.id.tv_span);
SpannableStringBuilder ssb = new SpannableStringBuilder("300 RMB");
//设置文字大小。
ssb.setSpan(new RelativeSizeSpan(6.0f), 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
//设置文字颜色。
ssb.setSpan(new ForegroundColorSpan(0xff303F9F), 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(ssb);
}
复制代码
最终能够实现以下的效果:
例以下面的布局:
LinearLayout
自带的
divider
属性来实现分割线:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:showDividers="beginning|end|middle"
android:divider="@android:drawable/divider_horizontal_bright"
android:dividerPadding="5dp"
android:paddingTop="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 2"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 3"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 4"/>
</LinearLayout>
复制代码
与分割线相关的属性包括如下几个:
divider
:传入分割线的drawable
,能够是一个图片,也能够是本身经过xml
实现的drawable
。showDividers
:分割线显示的位置,beginning/middle/end
,分割对应头部、中间、尾部。dividerPadding
:分割线距离两边的间距。Space
控件位于android.support.v4.widget
包中,与通常控件不一样,它的draw
方法是一个空实现,所以它只占位置,而不去渲染,使用它来进行占位填充比其它控件更加高效,例以下面,咱们须要将一行均等地分红五份,有颜色部分位于2,4
当中:
Space
控件,加上
layout_weight
属性来实现:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
<View
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@color/colorAccent"/>
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
<View
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@color/colorAccent"/>
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
</LinearLayout>
复制代码
当出现图片在文案的四周时,咱们应当首先考虑可以经过单个TextView
来实现,而不是经过LinearLayout
包裹TextView+ImageView
的方式来实现,例以下面的效果:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 方式一:使用 ImageView + TextView -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:src="@android:drawable/ic_btn_speak_now"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="ImageView + TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 方式二:使用单个 TextView -->
<TextView
android:drawableLeft="@android:drawable/ic_btn_speak_now"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="单个 TextView"/>
</LinearLayout>
复制代码
能够看到,虽然都是实现了图片加上文字的显示效果,可是第二种经过单个TextView
来实现其布局层级更少,而且控件的个数更少,所以效率更高,而且图片不只能够显示在左边,还能够显示在TextView
的四周,图片和TextView
之间的间隔能够经过drawablePadding
来实现。
当两个控件在布局上有重叠的部分,可是它们具备背景时,就会出现过分绘制的状况,形成无用的性能损耗。而且肉眼没法发现,须要经过设置当中的”调试GPU过分绘制"选项进行检查,详细使用以下:性能优化工具知识梳理(3) - 调试GPU过分绘制 & GPU呈现模式分析。例以下面布局当中,根布局和子控件有100dp
部分重叠,而且它们都有背景:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<LinearLayout
android:background="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</LinearLayout>
复制代码
那么最终,打开过分绘制检测时,就会出现下面的效果:
WindowBackgroud
当咱们使用某些主题时,系统有可能在DecorView
中给咱们加上一个背景,可是有时候它是无用的,例如上面的例子中,咱们根布局为紫色,这其实就是因为默认主题中的背景所致使的,咱们能够经过下面的方式去除掉该背景。@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_overdraw);
getWindow().setBackgroundDrawable(null);
}
复制代码
此时的检测结果以下,能够看到,根布局就不存在过分绘制的状况了:
当咱们在自定义控件,并重写onDraw
方法来完成相应的需求时,一些错误的操做每每会致使布局效率的下降,通常来讲,有两点须要注意:
Canvas
的ClipRect
方法避免过分绘制这里用一个简单的例子来讲明一下第二点的实现,当咱们须要实现下面这个多张图片重叠的自定义控件时:
public class ClipRectView extends View {
private static final int[] ID = new int[]{R.drawable.pic_1, R.drawable.pic_2, R.drawable.pic_3};
private Bitmap[] mBitmaps;
public ClipRectView(Context context) {
super(context);
prepareBitmap();
}
public ClipRectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
prepareBitmap();
}
public ClipRectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
prepareBitmap();
}
private void prepareBitmap() {
mBitmaps = new Bitmap[ID.length];
int i = 0;
for (int id : ID) {
mBitmaps[i++] = BitmapFactory.decodeResource(getResources(), id);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Bitmap bitmap : mBitmaps) {
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.translate(bitmap.getWidth() / 2, 0);
}
}
}
复制代码
可是,若是咱们打开调试GPU
过分绘制的开关,那么能够获得下面的检测结果,能够发如今两张图片重叠的地方,会出现明显的过分绘制:
ClipRect
对
onDraw
方法进行优化:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
int bits = mBitmaps.length;
for (int i = 0; i < bits; i++) {
Bitmap bitmap = mBitmaps[i];
int bitW = bitmap.getWidth();
int bitH = bitmap.getHeight();
if (i != 0) {
canvas.translate(bitW / 2, 0);
}
canvas.save();
if (i != bits - 1) {
canvas.clipRect(0, 0, bitW / 2, bitH);
}
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
}
canvas.restore();
}
复制代码
此时,检测的结果以下,和上图相比,咱们很好地解决了过分绘制的问题:
在Android Support Library 24
中,提供了一个AsyncLayoutInflater
工具类用于实现xml
布局的异步inflate
,它的用法和普通的LayoutInflater
相似,只不过它inflate
的执行是在子线程当中,当这一过程完成以后,再经过OnInflateFinishedListener
接口,回调到主线程当中。
首先是整个Activity
的根布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_async"
android:text="开始异步 Inflate 布局"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="40dp"/>
</LinearLayout>
复制代码
接下来是须要异步inflate
的子布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="异步 Inflate 的布局"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"/>
</LinearLayout>
复制代码
使用方式以下:
private void asyncInflated() {
TextView textView = (TextView) findViewById(R.id.tv_async);
final ViewGroup root = (ViewGroup) findViewById(R.id.ll_root);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(OptActivity.this);
asyncLayoutInflater.inflate(R.layout.layout_async, root, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resId, ViewGroup parent) {
parent.addView(view);
}
});
}
});
}
复制代码
其inflate
方法接收三个参数:
inflate
的布局id
。inflate
完成的回调,该回调是在主线程当中执行。须要注意,在该回调执行时,异步inflate
出来的布局并无添加到父布局当中,所以,咱们须要经过addView
的方法将其添加到View
树当中。最终的运行结果为:
在分析布局有可能致使的性能问题时,咱们通常会用到如下几种工具,这些工具咱们在以前学习性能优化工具的时候都有接触过:
HierecyViewer
性能优化工具知识梳理(4) - Hierarchy ViewerGPU
过分绘制 性能优化工具知识梳理(3) - 调试GPU过分绘制 & GPU呈现模式分析Lint
检查 性能优化工具知识梳理(8) - Lint