博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
性能优化技巧知识梳理(1) 布局优化
阅读量:5997 次
发布时间:2019-06-20

本文共 7497 字,大约阅读时间需要 24 分钟。

一、前言

性能优化包含的部分很多,包括布局、内存、耗电、流量等等,其中布局优化是最容易掌握,也最容易被大家所忽视的一个方面,今天,就来介绍一下有关布局优化的一些技巧。

二、布局优化技巧

(1) 使用 标签进行布局复用

当我们的布局中有多个相同的布局时,可以使用include标签来进行布局的复用,这样,当视觉需要修改单个Item的间距,文字大小时,只需要修改一个布局就可以了,例如像下面这种情况,我们就可以使用include标签来实现:

根布局为:

复制代码

单个Item的布局为:

复制代码

要点

  • 直接在根布局中,如果希望找到<include>所指定的layout中包含的控件,那么就需要给<include>指定id,再通过它来寻找子容器中的控件。
  • <include>标签中,可以指定layout_xxx属性,它将会覆盖子布局中的根标签中的属性。

(2) 使用 标签减少布局层级

当出现下面这种情况:一个xml布局文件的根节点是一个FrameLayout,并且它没有一个有用的背景,那么当该xml布局文件渲染出的ViewGroup被添加到父布局中时,连接处就会出现一个多余的节点,而采用<merge>标签可以去掉这一无用节点,从而降低布局的层级。

例如,在上面的例子当中,我们使用了<merge>标签的情形为:

假如我们没有使用
<merge>标签,那么情形为:
要点

  • 当需要通过LayoutInflaterinflate方法渲染出以<merge>作为根节点标签的xml文件时,必须传入不为nullroot参数,且attachToRoot参数必须为true
  • <merge>只可作为xml的根节点。
  • <merge>既不是View也不是ViewGroup,它只是表示一组等待被添加的视图,因此,对它设定的任何属性都是无用的。

(3) 使用 ViewStub 标签动态加载布局

当我们的布局中,存在一些需要按序加载的控件,那么就可以使用ViewStub标签预先声明,当情况满足时再去实例化ViewStub中所声明的布局,其用法如下:

  • 首先,在布局中预先声明ViewStub,并且通过layout标签指定对应的布局layout_stub
复制代码
  • 当需要加载以上指定的布局时,那么首先通过获得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所指定的值:

(4) 选择合适的父容器以减少布局层级和测量次数

当我们需要通过父容器来容纳多个子控件时,如何选择父容器,将会影响到布局的效率,而对于父容器的选择,有以下几点原则:

  • 首先应当考虑布局层级最小的方案。
  • 布局层级相同时,就应当选取合适的父容器,一般来说,有以下几点经验:
  • 选取的优先级为:FrameLayout、不带layout_weight参数的LinearLayoutRelativeLayout,这里选取的标准为带有layout_weightLinearLayout或者RelativeLayout会测量两次。
  • 当使用LinearLayout时,应当尽量避免使用layout_weight参数。
  • 避免使用RelativeLayout嵌套RelativeLayout
  • 如果允许,那么可以使用Google新推出的ConstraintLayout布局。

(5) 使用 SpannableStringBuilder 替换多个 TextView 的实现

当我们存在多种不同大小、颜色或者图文混排需要显示时,我们往往会利用多个TextView来进行组合,但是某些效果通过一个TextView就可以实现,一般来说,利用SpannableStringBuilder可以通过单个TextView实现多种不同的布局,更多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);    }复制代码

最终可以实现如下的效果:

除此之外,还可以实现图文混排,例如下面这样:

(6) 使用 LinearLayout 自带的分割线,而不是在布局中手动添加一个 ImageView

例如下面的布局:

此时我们就可以使用
LinearLayout自带的
divider属性来实现分割线:

复制代码

与分割线相关的属性包括以下几个:

  • divider:传入分割线的drawable,可以是一个图片,也可以是自己通过xml实现的drawable
  • showDividers:分割线显示的位置,beginning/middle/end,分割对应头部、中间、尾部。
  • dividerPadding:分割线距离两边的间距。

(7) 使用 Space 控件进行合理的占位

Space控件位于android.support.v4.widget包中,与一般控件不同,它的draw方法是一个空实现,因此它只占位置,而不去渲染,使用它来进行占位填充比其它控件更加高效,例如下面,我们需要将一行均等地分成五份,有颜色部分位于2,4当中:

这时候,就可以通过
Space控件,加上
layout_weight属性来实现:

复制代码

(8) 使用 TextView 的 drawableLeft/drawableTop 属性来替代 ImageView + TextView 的布局

当出现图片在文案的四周时,我们应当首先考虑能够通过单个TextView来实现,而不是通过LinearLayout包裹TextView+ImageView的方式来实现,例如下面的效果:

其布局如下所示:

复制代码

可以看到,虽然都是实现了图片加上文字的显示效果,但是第二种通过单个TextView来实现其布局层级更少,并且控件的个数更少,因此效率更高,并且图片不仅可以显示在左边,还可以显示在TextView的四周,图片和TextView之间的间隔可以通过drawablePadding来实现。

(9) 去掉不必要的背景

  • 在布局层级中避免重叠部分的背景

当两个控件在布局上有重叠的部分,但是它们具有背景时,就会出现过度绘制的情况,造成无用的性能损耗。并且肉眼无法发现,需要通过设置当中的”调试GPU过度绘制"选项进行检查,详细使用如下:。例如下面布局当中,根布局和子控件有100dp部分重叠,并且它们都有背景:

复制代码

那么最终,打开过度绘制检测时,就会出现下面的效果:

  • 去掉无用的WindowBackgroud 当我们使用某些主题时,系统有可能在DecorView中给我们加上一个背景,但是有时候它是无用的,例如上面的例子中,我们根布局为紫色,这其实就是由于默认主题中的背景所导致的,我们可以通过下面的方式去除掉该背景。
@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_overdraw);        getWindow().setBackgroundDrawable(null);    }复制代码

此时的检测结果如下,可以看到,根布局就不存在过度绘制的情况了:

(10) 优化自定义控件中的 onDraw 方法

当我们在自定义控件,并重写onDraw方法来完成相应的需求时,一些错误的操作往往会导致布局效率的降低,一般来说,有两点需要注意:

  • 避免在其中进行对象的分配
  • 使用CanvasClipRect方法避免过度绘制

这里用一个简单的例子来说明一下第二点的实现,当我们需要实现下面这个多张图片重叠的自定义控件时:

假如我们直接使用下面的方式,也可以实现上面的效果:

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();    }复制代码

此时,检测的结果如下,和上图相比,我们很好地解决了过度绘制的问题:

(11) 使用 AsyncLayoutInflater 异步加载布局

Android Support Library 24中,提供了一个AsyncLayoutInflater工具类用于实现xml布局的异步inflate,它的用法和普通的LayoutInflater类似,只不过它inflate的执行是在子线程当中,当这一过程完成之后,再通过OnInflateFinishedListener接口,回调到主线程当中。

首先是整个Activity的根布局:

复制代码

接下来是需要异步inflate的子布局:

复制代码

使用方式如下:

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树当中。

最终的运行结果为:

(12) 使用性能检测工具,找出布局中的性能瓶颈

在分析布局有可能导致的性能问题时,我们一般会用到以下几种工具,这些工具我们在之前学习性能优化工具的时候都有接触过:

  • HierecyViewer
  • 调试GPU过度绘制
  • Lint检查

更多文章,欢迎访问我的 Android 知识梳理系列:

  • Android 知识梳理目录:
  • 个人主页:
  • 个人知识总结目录:

转载地址:http://ehqlx.baihongyu.com/

你可能感兴趣的文章
OnCheckedChangeListener和setChecked之间冲突问题解决
查看>>
DPDK 分析
查看>>
PhotoShop 图像处理 算法 汇总
查看>>
017-通过govendor管理依赖包
查看>>
通通玩blend美工(5)——旋转木马,交互性设计
查看>>
java并发线程池---了解ThreadPoolExecutor就够了
查看>>
Json转list,二层解析转换
查看>>
【mysql】mysql中varcher属性最大值能存多长
查看>>
Java程序监控指标
查看>>
【设计模式】桥接模式 Bridge Pattern
查看>>
Spring之Bean的配置方式
查看>>
slice全解析
查看>>
阿里云线上ROS静态路由转发,有大坑。
查看>>
基于SpringBoot的项目管理后台
查看>>
Logstash使用grok插件解析Nginx日志
查看>>
朱晔的互联网架构实践心得S1E9:架构评审一百问和设计文档五要素
查看>>
golang map/json/struct互转换
查看>>
redis详细配置文件
查看>>
oracle安装界面中文乱码解决
查看>>
C#中HttpWebRequest的用法详解(转载)
查看>>