文档章节

性能优化--布局优化技巧

android-key
 android-key
发布于 2017/07/09 00:12
字数 3181
阅读 21
收藏 0

1.重用布局文件

1.)include标签

首先用得最多的应该是include,按照官方的意思,include就是为了解决重复定义相同布局的问题。使用起来很方便,我这里就不举例子。

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/my_title_parent_id"   
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <include 
      android:id="@+id/my_title_ly"  
      layout="@layout/titlebar" />  
  
</LinearLayout>  

注意事项:

 1.如果你只想单独修改某个布局文件的titleBar而其他布局文件的titleBar不受影响的话,我们可以使用覆写<include>属性的方式

在<include>标签当中,我们是可以覆写所有layout属性的,即include中指定的layout属性将会覆盖掉titlebar中根视图指定的layout属性。因此,这里我们希望将titlebar的高度设置成wrap_content,就可以这样写:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/my_title_parent_id"   
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <include  
        android:id="@+id/my_title_ly"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        layout="@layout/titlebar" />  
  
</LinearLayout>  

而非layout属性则无法在<include>标签当中进行覆写。另外需要注意的是,如果我们想要在<include>标签当中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。

2.使用include最常见的问题就是findViewById查找不到目标控件,这个问题出现的前提是在include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。例如上述例子中,include时设置了该布局的id为my_title_ly,而my_title_layout.xml中的根视图的id为my_title_parent_id。此时如果通过findViewById来找my_title_parent_id这个控件,然后再查找my_title_parent_id下的子控件则会抛出空指针。代码如下 :

View titleView = findViewById(R.id.my_title_parent_id) ;  
// 此时 titleView 为空,找不到。此时空指针
 TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");  

正确的方式:

// 使用include时设置的id,即R.id.my_title_ly
View titleView = findViewById(R.id.my_title_ly) ;  
// 通过titleView找子控件
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");  

或者更简单的直接查找它的子控件:

TextView titleTextView = (TextView)findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");

通过看源码出现这样的原因是:解析过程中会首先判断include标签的id如果不是View.NO_ID的话会把该id设置给被引入的布局根元素的id,即此时在我们的例子中被引入的id为my_title_parent_id的根元素RelativeLayout的id被设置成了include标签中的id,即RelativeLayout的id被动态修改成了”my_title_ly”。因此此时我们再通过“my_title_parent_id”这个id来查找根元素就会找不到了! 
所以结论就是: 如果include中设置了id,那么就通过include的id来查找被include布局根元素的View;如果include中没有设置Id, 而被include的布局的根元素设置了id,那么通过该根元素的id来查找该view即可。拿到根元素后查找其子控件都是一样的。

2.)Merge标签

<merge>标签是作为<include>标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。大家都知道,Android去解析和展示一个布局是需要消耗时间的,布局嵌套的越多,那么解析起来就越耗时,性能也就越差,因此我们在编写布局文件时应该让嵌套的层数越少越好。

比如我们写一个公共的确定和取消按钮ok_cancel_layout.xml

<?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="wrap_content"  
    android:orientation="vertical" >  
  
    <Button  
        android:id="@+id/ok"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:text="OK" />  
  
    <Button  
        android:id="@+id/cancel"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:text="Cancel" />  
  
</LinearLayout>  

然后再另一个界面main.xml去引用

<?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" >  
      
    <EditText  
        android:id="@+id/edit"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginBottom="10dp"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="Edit something here" />  
      
    <include 
    layout="@layout/ok_cancel_layout"/>  
  
</LinearLayout>  

目前main.xml这个界面当中其实已经存在着多余的布局嵌套了!感觉还没写几行代码呢,怎么这就已经有多余的布局嵌套了?不信的话我们可以通过View Hierarchy工具来查看一下,如下图所示:

相信大家已经可以看出来了吧,这个内部的LinearLayout就是一个多余的布局嵌套,实际上并不需要这样一层,让两个按钮直接包含在外部的LinearLayout当中就可以了。而这个多余的布局嵌套其实就是由于布局引入所导致的,那么应该怎样优化掉这个问题呢?当然就是使用<merge>标签来完成了,修改ok_cancel_layout.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<merge xmlns:android="http://schemas.android.com/apk/res/android">  
  
    <Button  
        android:id="@+id/ok"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:text="OK" />  
  
    <Button  
        android:id="@+id/cancel"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:text="Cancel" />  
  
</merge>  

我们在通过View Hierarchy工具来查看一下,如下图所示:

可以看到,这里我们将ok_cancel_layout最外层的LinearLayout布局删除掉,换用了<merge>标签,这就表示当有任何一个地方去include这个布局时,会将<merge>标签内包含的内容直接填充到include的位置,不会再添加任何额外的布局结构.

让我意想不到的是Merge竟然是个activity,并且还动态的加载了LinearLayout对象.

/** 
 * Exercise <merge /> tag in XML files. 
 */  
public class Merge extends Activity {  
    private LinearLayout mLayout;  

    @Override  
    protected void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  

        mLayout = new LinearLayout(this);  
        mLayout.setOrientation(LinearLayout.VERTICAL);  
        LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout);  

        setContentView(mLayout);  
    }  

    public ViewGroup getLayout() {  
        return mLayout;  
    }  
}  

通过看源码发现:在解析的过程中,如果是merge标签,那么第一步:首先获取merge标签的parent ,第二步:获取布局参数 ,第三部:递归解析每个子元素 ,第四步:将子元素直接添加到merge标签的parent view中 ,这样就保证了不会引入额外的层级。
3.)ViewStub标签

根据官方的解释大致可翻译成:其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。

有的时候我们会遇到这样的场景,就是某个布局当中的元素非常多,但并不是所有元素都一起显示出来的,而是普通情况下只显示部分常用的元素,而那些不常用的元素只有在用户进行特定操作的情况下才会显示出来。

这里举个大家都非常熟悉的例子,我们在添加联系人的时候其实可以编辑的字段真的非常多,姓名、电话、email、传真、住址、昵称等等等等,但其实基本上大家最常用的就是填一个姓名,填一个电话而已。那么将这么多繁杂的字段都一起显示在界面上其实并不是一种很好的做法,因为大多数人都是用不到这些字段的。比较聪明的做法就是把最常用的姓名和电话显示在界面上,然后给用户提供一个添加更多字段的选项,当用户真的有需要去添加其它信息的时候,我们才将另外的元素显示到界面上。

说到实现这样一个功能,我相信大多数人的第一反应就是将不常用的元素使用INVISIBLE或者GONE进行隐藏,然后当用户需要使用这些元素的时候再把它们置成VISIBLE显示出来。使用这种方式肯定可以实现功能的,但是性能方面就表现得一般了,因为即使是将元素进行隐藏,它们其实还是在布局当中的,每个元素还拥有着自己的宽、高、背景等等属性,解析布局的时候也会将这些隐藏的元素一一解析出来。

那么我们如何才能让这些不常用的元素仅在需要时才去加载呢?Android为此提供了一种非常轻量级的控件,ViewStub。ViewStub虽说也是View的一种,但是它没有大小,没有绘制功能,也不参与布局,资源消耗非常低,将它放置在布局当中基本可以认为是完全不会影响性能的。

还是举例说明例子1:比如我们在添加三个EditText构成另一个布局edittext_extra_layout.xml

<?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" >  
  
    <EditText  
        android:id="@+id/edit_extra1"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:hint="Extra field 1" />  
  
    <EditText  
        android:id="@+id/edit_extra2"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="Extra field 2" />  
  
    <EditText  
        android:id="@+id/edit_extra3"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="Extra field 3" />  
  
</LinearLayout>  

接下来我们修改main.xml文件中的代码,如下所示:

<?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" >  
  
    <EditText  
        android:id="@+id/edit"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginBottom="10dp"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="@string/edit_something_here" />  
  
    <Button  
        android:id="@+id/more"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_gravity="right"  
        android:layout_marginRight="20dp"  
        android:layout_marginBottom="10dp"  
        android:text="More" />  
      
    <ViewStub   
        android:id="@+id/view_stub"  
        android:layout="@layout/profile_extra"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        />  
  
    <include layout="@layout/ok_cancel_layout" />  
  
</LinearLayout>  

可以看到,这里我们新增了一个More Button,这个按钮就是用于去加载那些不常用的元素的,然后在Button的下面定义了一个ViewStub。在ViewStub控件中,我们先是通过id属性给它指定了一个唯一标识,又通过layout属性将profile_extra布局传入进来,接着给ViewStub指定了一个宽高。注意,虽然ViewStub是不占用任何空间的,但是每个布局都必须要指定layout_width和layout_height属性,否则运行就会报错。

private EditText editExtra1;  
private EditText editExtra2;  
private EditText editExtra3;  
  
public void onMoreClick() {  
    ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);  
    if (viewStub != null) {  
        View inflatedView = viewStub.inflate();  
        editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);  
        editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);  
        editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);  
    }  
}  

调用inflate()方法之后会将加载出来的布局进行返回,之后我们就可以对这个布局进行任意的操作了,再次隐藏显示,或者获取子元素的实例等。注意这里我对ViewStub的实例进行了一个非空判断,这是因为ViewStub在XML中定义的id只在一开始有效,一旦ViewStub中指定的布局加载之后,这个id也就失败了,那么此时findViewById()得到的值也会是空。

例子2

<ViewStub  
    android:id="@+id/stub_import"  
    android:inflatedId="@+id/stub_comm_lv"  
    android:layout="@layout/my_comment_layout"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:layout_gravity="bottom" /  
<?xml version="1.0" encoding="utf-8"?>  
<ListView xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:id="@+id/my_comm_lv"  
    android:layout_height="match_parent" >  

</ListView>  
public class MainActivity extends Activity {  

    public void onCreate(Bundle b){  
        // main.xml中包含上面的ViewStub  
        setContentView(R.layout.main);  

        // 方式1,获取ViewStub,  
        ViewStub listStub = (ViewStub) findViewById(R.id.stub_import);  
        // 加载评论列表布局  
        listStub.setVisibility(View.VISIBLE);  
        // 获取到评论ListView,注意这里是通过ViewStub的inflatedId来获取  
            ListView commLv = findViewById(R.id.stub_comm_lv);  
                if ( listStub.getVisibility() == View.VISIBLE ) {  
                       // 已经加载, 否则还没有加载  
                }  
            }  
       }  

通过setVisibility(View.VISIBILITY)来加载评论列表,此时你要获取到评论ListView对象的话,则需要通过findViewById来查找,而这个id并不是ViewStub的id。 
这是为什么呢 ?

通过看源码原因是:

1、加载目标布局

2、如果ViewStub的inflatedId不是NO_ID则把inflatedId设置为目标布局根元素的id,即评论ListView的id

3、将ViewStub自身从parent中移除
4、将目标布局的根元素添加到parent中

public class MainActivity extends Activity {  

    // 把commLv2设置为类的成员变量  
    ListView commLv2 = null;  
    //  
    public void onCreate(Bundle b){  
        // main.xml中包含上面的ViewStub  
        setContentView(R.layout.main);  

        // 方式二  
        ViewStub listStub2 = (ViewStub) findViewById(R.id.stub_import) ;  
        // 成员变量commLv2为空则代表未加载  
        if ( commLv2 == null ) {  
        // 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象  
          commLv2 = (ListView)listStub2.inflate();  
        } else {  
        // ViewStub已经加载  
        }  

    }  

}  
  1. 判断是否已经加载过, 如果通过setVisibility来加载,那么通过判断可见性即可;如果通过inflate()来加载是不可以通过判断可见性来处理的,而需要使用方式2来进行判断。
  2. findViewById的问题,注意ViewStub中是否设置了inflatedId,如果设置了则需要通过inflatedId来查找目标布局的根元素。

 

© 著作权归作者所有

共有 人打赏支持
android-key
粉丝 7
博文 119
码字总数 170926
作品 0
武汉
性能优化技巧知识梳理(3) - 如何排查列表卡顿问题

一、前言 如果现在用户反馈某个列表很卡,你会怎么排查问题? 这样一个简短的问题,其实考察了我们多方面的知识。要答出其中的一两个小点其实并不难,难的是如何能够由外之内,由浅入深娓娓道...

泽毛
06/20
0
0
要点提炼|开发艺术之性能优化

本篇重点是性能优化,包括内容: 布局优化 绘制优化 内存泄漏优化 MAT工具 响应速度优化 ListView优化 Bitmap优化 线程优化 其他 1.布局优化 a.核心思想:尽量减少布局文件的层级。 b.方式:...

minmin_1123
2017/12/30
0
0
Android应用程序性能优化

应用程序的性能问题体现在很多方面, 比如第一次启动速度慢,或者进入某一界面速度慢;动画执行过程不流畅,或者动画执行卡顿时间长;ListView列表滑动过程中卡顿,不流畅;应用程序自定义的...

博为峰教研组
2016/11/24
8
0
Android开发性能优化总结

Android性能调优包含 移动网络优化 Java(Android)代码优化 布局优化 数据库性能优化 参考链接 http://www.trinea.cn/android/android-traceview/ 本文主要针对代码调优 应用程序的性能问题体...

蜗牛崛起
2017/10/19
0
0
十大技巧快速提升原生APP开发性能

移动应用市场用户争夺战日益激烈,原来做APP拼想法拼创意拼是否抓住用户痛点。现在,精细化用户体验成为了一个APP能否留存用户的关键问题,一旦用户觉得体验不畅,马上就有竞品APP后补,如何...

wojiu9712
2016/02/26
392
0

没有更多内容

加载失败,请刷新页面

加载更多

Flask 开发填坑

插件的选择: flask-security 真的是个鸡肋啊。自带的页面,好丑。还不如用flask-login来做呢。

pearma
59分钟前
2
0
讲述下 :LVM逻辑卷管理遇到的问题

LVM学习逻辑卷管理创建逻辑卷遇到的问题 1 实验环境 系统 内核 发行版本 CentOS 2.6.32-754.2.1.el6.x86_64 CentOS release 6.10 (Final) 由于是最小化安装没有xfs命令,yum安装如下包支持此...

linuxprobe16
今天
1
0
day95-20180922-英语流利阅读-待学习

Hey Jude 半个世纪传唱不衰的背后故事 毛西 2018-09-22 1.今日导读 2004 年,The Beatles 被《滚石》杂志选为“历史上最伟大的 50 位流行音乐家的第一位”。这四名来自英国利物浦的男孩不仅对...

飞鱼说编程
今天
3
0
OSChina 周六乱弹 —— 放假前期焦虑症晚期

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @andonny :分享Matteo的单曲《Panama》: 《Panama》- Matteo 手机党少年们想听歌,请使劲儿戳(这里) @新垣吉衣OSC :我发现只要去有小朋友...

小小编辑
今天
339
10
wait()被notify()后,接着执行wait()后面的语句

wait()被notify()后,接着执行wait()后面的语句

noteman
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部