探索App性能优化之布局优化

669 阅读5分钟

一、页面布局对App性能的影响和原因

布局性能主要影响 :App的页面显示速度。

布局影响性能的实质:页面的测量 & 绘制时间,一个页面通过递归完成测量 & 绘制过程。

二、优化思路

合理的布局性能、布局层级、布局复用性 和 按需加载(测量&绘制时间)。

在布局优化中,Android的官方提到了这三种标签<include /><merge /><ViewStub />,下面也是简单说一下它们的优势,以及怎么使用。

三、具体方案

(一) 提高布局的复用性<include/>

1、作用:提取布局间的公共部分,通过提高布局的复用性从而减少测量 & 绘制时间。

2、使用:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=”match_parent”
    android:layout_height=”match_parent”
    android:background="@color/white"
    android:orientation="vertical" >
    
    <include layout="@layout/titlebar"/>
 
    <TextView 
       android:layout_width=”match_parent”
       android:layout_height="wrap_content"
       android:text="@string/Hello World"
       android:padding="10dp" />

</LinearLayout>

3、特别注意:

(1)<include/>标签可以使用单独的layout属性,这个也是必须使用的。

(2)<include/>标签若指定了id属性,而layout也定义了id,则layout的id会被覆盖。

(二) 减少视图层级<merge/>

1、作用:减少布局层级,配合<include/>标签使用,可优化加载布局文件时的资源消耗。

2、使用:

<merge/>标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。<merge/>多用于替换FrameLayout或者当一个布局包含另一个时,<merge/>标签消除视图层次结构中多余的视图组。

例如:你的主布局文件是相对布局,引入了一个相对布局的<include/>,这时<include/>布局使用的RelativeLayout就没意义了,反而减慢UI渲染。这时可以使用<merge/>标签优化。

//布局A:layout_a.xml
<?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" >
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_10"/>
    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_10"/>
</RelativeLayout>
//布局B:layout_b.xml
<?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" >
    <Button
        android:id="@+id/Button"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="@dimen/dp_10" />
        
    <include layout="@layout/layout_a.xml" />
</RelativeLayout>

在上述例子,在布局B中通过<include/>标签引用布局A,

此时B布局层级为 =  RelativeLayout ->> Button
                                   —>> RelativeLayout 
                                            ->> Button
                                            ->> TextView

现在使用<merge/>优化,将被引用布局A根标签的RelativeLayout改为<merge/>。那么在引用布局A时,布局A中的<merge/>标签内容根节点RelativeLayout会被去掉,在<include/>标签里存放的是布局A中的<merge/>标签为根节点的子节点即<include/>里存放的是:<Button/><TextView/>

 此时B布局层级为 =  RelativeLayout ->> Button 
                                 ->> Button
                                 ->> TextView

即已去掉之前无意义且多余的<RelativeLayout/>

//布局A:layout_a.xml
<?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" >
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_10"/>
    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_10"/>
</merge>

(三) 按需加载<ViewStub/>

1、作用:按需加载 外部引入的布局,注:属轻量级View、不占用显示 & 位置。 <ViewStub/>标签最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能。各种不常用的布局想进度条、显示错误消息等可以使用<ViewStub/>标签,以减少内存使用量,加快渲染速度。<ViewStub/>是一个不可见的,大小为0的View。

2、使用:

    // 步骤1、先设置好预显示的布局
    // 步骤2、在其他布局通过<ViewStub>标签引入外部布局(类似<include>)
                注:此时该布局还未被加载显示。
    // 步骤3、只有当ViewStub被设置为可见ViewStub.setVisibility(View.VISIBLE)或者
                调用ViewStub.inflate()时,ViewStub所指向的布局文件才会被inflate、
                    实例化,最终 显示<ViewStub>指向的布局。

   /** 
     * 实例说明:在布局A中引入布局B,只有在特定时刻C中才显示
     */  
     // 步骤1:先设置好预显示的布局B = layout_b.xml
     <?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" >
        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_10"/>
        <TextView
            android:id="@+id/textview"
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_10"/>
    </RelativeLayout>

    // 步骤2:在布局A通过<ViewStub>标签引入布局B(类似<include>);注:此时该布局还未被加载显示
    // 布局A:layout_a.xml
    <?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" >
        <Button
            android:id="@+id/Button"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="@dimen/dp_10" />
        <ViewStub
            android:id="@+id/Blayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout="@layout/layout_b" />
    </RelativeLayout>

    // 步骤3:
    只有当ViewStub被设置为可见 / 调用了ViewStub.inflate()时,ViewStub所指向的布局文件
        才会被inflate 、实例化,最终 显示<ViewStub>指向的布局
    ViewStub stub = (ViewStub) findViewById(R.id.Blayout);   
    stub.setVisibility(View.VISIBLE);
    //or
    stub.inflate();

3、特别注意:

(1) ViewStub中的layout布局不能使用merge标签,否则会报错。

(2)ViewStub的inflate只能执行一次,显示了之后,就不能再使用ViewStub控制它了。

(3)与View.setVisible(View.Gone)的区别:

  • 共同点:初始时不会显示

  • 不同点:ViewStub标签只会在显示时,才会渲染整个布局。View.GONE在初始化布局树的时候就已经添加在布局树上了。相比之下ViewStub标签节省布局文件的解析时间和内存的占用,从而具有更高的效率。

(四) 选择耗费性能较少的布局

1、性能耗费低的布局 = 功能简单 = LinearLayout、FrameLayout

2、性能耗费高的布局 = 功能复杂 = RelativeLayout,即布局过程需消耗更多性能(CPU资源 & 时间)

3、嵌套多个布局所耗费的性能 > 单个布局本身耗费的性能,即完成需求时:宁选择单个耗费性能高的布局,也不采用嵌套多个耗费性能低的布局。

4、通过合理选择布局类型,从而减少嵌套。即:完成复杂的UI效果时,尽可能选择1个功能复杂的布局(如RelativeLayout)完成,而不要选择多个功能简单的布局(如LinerLayout)通过嵌套完成。