【Android 性能優(yōu)化】—— UI篇

圖片來自別樣網(wǎng)

本文同時發(fā)布在CSDN上蠢壹,歡迎查看

1. 前言

隨著最近幾年移動市場蓬勃發(fā)展区匠,引來大批人員投入到Android、IOS的開發(fā)前線蛙埂,與此同時全國各大培訓(xùn)機(jī)構(gòu)每月都培養(yǎng)出成千上萬名號稱擁有2到3年工作經(jīng)驗(yàn)的開發(fā)者倦畅。當(dāng)然,這都已經(jīng)不是什么秘密了绣的,從目前來看叠赐,中國IT行業(yè)的主力軍基本上都走過培訓(xùn)的道路。

但問題是屡江,這號稱23年工作經(jīng)驗(yàn)者芭概,使招聘單位錯誤的認(rèn)為:23年開發(fā)經(jīng)驗(yàn)和剛剛結(jié)束的培訓(xùn)經(jīng)歷,基本上劃等號惩嘉。這就導(dǎo)致了企業(yè)大幅度提高用人標(biāo)準(zhǔn)罢洲,造成了為何如今移動開發(fā)市場依舊火熱,但是工作卻不好找的現(xiàn)狀文黎。

最悲慘的例子恐怕就是前幾年IOS如日中天惹苗,可時間就過了一年開發(fā)人員就出現(xiàn)了井噴的情況殿较,大量IOS開發(fā)者找不到工作。

總的來說:工作機(jī)會的確是很多鸽粉,但是企業(yè)把用人要求都大大提高了。如何在萬千人群中脫穎而出抓艳,走上人生巔峰触机,迎娶白富美,沒有亮點(diǎn)玷或,是萬萬不行滴儡首。。偏友。

接下來我就一起學(xué)習(xí)Android UI優(yōu)化吧

2. Android渲染機(jī)制分析

大家在開發(fā)應(yīng)用的時候或多或少都遇到過可感知的界面卡頓現(xiàn)象蔬胯,尤其是在布局層次嵌套太多,存在不必要的繪制位他,或者onDraw方法中執(zhí)行了過多耗時操作氛濒、動畫執(zhí)行的次數(shù)過多等情況下,很容易造成此類情況鹅髓。如今APP設(shè)計都要求界面美觀舞竿、擁有更多的動畫、圖片等時尚元素從而打造良好的用戶體驗(yàn)窿冯。但是大量復(fù)雜的渲染工作很可能造成Android系統(tǒng)壓力過大骗奖,無法及時完成渲染工作。那么多久執(zhí)行一次渲染醒串,才能讓界面流暢運(yùn)行呢执桌?

一圖勝千言

如上圖所示,Android系統(tǒng)每隔16ms就會發(fā)送一個VSYNC信號(VSYNC:vertical synchronization 垂直同步芜赌,幀同步)仰挣,觸發(fā)對UI進(jìn)行渲染,如果每次渲染都成功缠沈,這樣就能夠達(dá)到流暢的畫面所需要的正常幀率:60fps椎木。一旦這時候系統(tǒng)正在做大于16ms的耗時操作,系統(tǒng)就會無法響應(yīng)VSYNC信號博烂,執(zhí)行渲染工作香椎,導(dǎo)致發(fā)生丟幀現(xiàn)象。

大家在察覺到APP卡頓的時候禽篱,可以看看logcat控制臺畜伐,會有drop frames類似的警告
本引用來自: Android UI性能優(yōu)化實(shí)戰(zhàn) 識別繪制中的性能問題

丟幀啦。躺率。玛界。万矾。

例如上圖所示:如果你的某個操作花費(fèi)時間是24ms,系統(tǒng)在得到VSYNC信號的時候就無法進(jìn)行正常渲染慎框,只能等待下一個VSYNC信號(第二個16ms)才能執(zhí)行渲染工作良狈。那么用戶在32ms內(nèi)看到的會是同一幀畫面。(我就是感覺google給的圖給錯了笨枯,明明是 32ms薪丁,怎么給標(biāo)了一個34ms,難道是有其他寓意我沒有理解上去馅精?严嗜??)

用戶容易在UI執(zhí)行動畫洲敢、ListView漫玄、RecyclerView滑動的時候感知到界面的卡頓與不流暢現(xiàn)象。所以開發(fā)者一定要注意在設(shè)計布局時不要嵌套太多層压彭,多使用 include方法引入布局睦优。同時不要讓動畫執(zhí)行次數(shù)太多,導(dǎo)致CPU或者GPU負(fù)載過重壮不。

看到這里同學(xué)可能會疑問:為什么是16ms渲染一次刨秆,和60fps有什么關(guān)系呢?下面讓我們看一下原理:

16ms意味著著1000/60hz忆畅,相當(dāng)于60fps衡未。

那么只要解釋為什么是60fps,這個問題就迎刃而解:

這是因?yàn)槿搜酆痛竽X之間的寫作無法感知超過60fps的畫面更新家凯,12fps大概類似手動快速翻動書籍的幀率缓醋,這是明顯可以感知到不夠順滑的。
24fps使得人眼感知的是連續(xù)的線性運(yùn)動绊诲,這其實(shí)是歸功于運(yùn)動模糊效果送粱,24fps是電影膠圈通常使用的幀率,因?yàn)檫@個幀率已經(jīng)足夠支撐大部分電影畫面需要表達(dá)的內(nèi)容掂之,同時能夠最大的減少費(fèi)用支出抗俄。
但是低于30fps是
無法順暢表現(xiàn)絢麗的畫面內(nèi)容的,此時就需要用到60fps來達(dá)到想要的效果世舰,當(dāng)然超過60fps是沒有必要的
本引用來源:Google 發(fā)布 Android 性能優(yōu)化典范 - 開源中國社區(qū)

3.1 界面卡頓的主要元兇—— 過度繪制(Overdraw)

3.1 什么是過度繪制动雹?

過渡繪制是指屏幕上某個像素在同一幀的時間內(nèi)繪制了多次。在多層次的UI結(jié)構(gòu)里面跟压,如果不可見的UI也在做繪制操作胰蝠,這就會導(dǎo)致某些像素區(qū)域被繪制了多次,這就是很大程度上浪費(fèi)了CPU和GPU資源。最最常見的過度繪制茸塞,就是設(shè)置了無用的背景顏色6阕!钾虐!

3.2 如何發(fā)現(xiàn)過度繪制噪窘?

對于Overdraw這個問題還是很容易發(fā)現(xiàn)的,我們可以通過以下步驟打開顯示GPU過度繪制(Show GPU Overrdraw)選項(xiàng)

設(shè)置 -> 開發(fā)者選項(xiàng) -> 調(diào)試GPU過度繪制 -> 顯示GPU過度繪制

打開以后之后效扫,你會發(fā)現(xiàn)屏幕上有各種顏色倔监,此時你可以切換到需要檢測的程序與界面,對于各個色塊的含義荡短,請看下圖:

Overdraw的參考圖

藍(lán)色丐枉,淡綠哆键,淡紅掘托,深紅代表了4種不同程度的Overdraw情況,
藍(lán)色: 意味著overdraw 1倍籍嘹。像素繪制了兩次闪盔。大片的藍(lán)色還是可以接受的(若整個窗口是藍(lán)色的,可以擺脫一層)辱士。
綠色: 意味著overdraw 2倍泪掀。像素繪制了三次。中等大小的綠色區(qū)域是可以接受的但你應(yīng)該嘗試優(yōu)化颂碘、減少它們异赫。
淡紅: 意味著overdraw 3倍。像素繪制了四次头岔,小范圍可以接受塔拳。
深紅: 意味著overdraw 4倍。像素繪制了五次或者更多峡竣。這是錯誤的靠抑,要修復(fù)它們。
我們的目標(biāo)就是盡量減少紅色Overdraw适掰,看到更多的藍(lán)色區(qū)域颂碧。

3.3 解決問題的工具和方法

通過Hierarchy Viewer去檢測渲染效率,去除不必要的嵌套
通過Show GPU Overdraw去檢測Overdraw类浪,最終可以通過移除不必要的背景载城。

4. UI優(yōu)化實(shí)踐

4.1 移除不必要的background

(由于公司項(xiàng)目還處于保密階段,所以摘取了Android UI性能優(yōu)化實(shí)戰(zhàn) 識別繪制中的性能問題的部分示例)
下面看一個簡單的展示ListView的例子:

  • activity_main
?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:paddingLeft="@dimen/activity_horizontal_margin"
              android:paddingRight="@dimen/activity_horizontal_margin"
              android:background="@android:color/white"
              android:paddingTop="@dimen/activity_vertical_margin"
              android:paddingBottom="@dimen/activity_vertical_margin"
              android:orientation="vertical"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/narrow_space"
        android:textSize="@dimen/large_text_size"
        android:layout_marginBottom="@dimen/wide_space"
        android:text="@string/header_text"/>

    <ListView
        android:id="@+id/id_listview_chats"
        android:layout_width="match_parent"
        android:background="@android:color/white"
        android:layout_height="wrap_content"
        android:divider="@android:color/transparent"
        android:dividerHeight="@dimen/divider_height"/>
</LinearLayout>
  • item的布局文件
<?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="horizontal"
    android:paddingBottom="@dimen/chat_padding_bottom">

    <ImageView
        android:id="@+id/id_chat_icon"
        android:layout_width="@dimen/avatar_dimen"
        android:layout_height="@dimen/avatar_dimen"
        android:src="@drawable/joanna"
        android:layout_margin="@dimen/avatar_layout_margin" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:textColor="#78A"
            android:orientation="horizontal">

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:padding="@dimen/narrow_space"
                android:text="@string/hello_world"
                android:gravity="bottom"
                android:id="@+id/id_chat_name" />

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:textStyle="italic"
                android:text="@string/hello_world"
                android:padding="@dimen/narrow_space"
                android:id="@+id/id_chat_date" />
        </RelativeLayout>

        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/narrow_space"
            android:background="@android:color/white"
            android:text="@string/hello_world"
            android:id="@+id/id_chat_msg" />
    </LinearLayout>
</LinearLayout>
  • Activity的代碼
package com.zhy.performance_01_render;

import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

/**
 * Created by zhy on 15/4/29.
 */
public class OverDrawActivity01 extends AppCompatActivity
{
    private ListView mListView;
    private LayoutInflater mInflater ;

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

        setContentView(R.layout.activity_overdraw_01);

        mInflater = LayoutInflater.from(this);
        mListView = (ListView) findViewById(R.id.id_listview_chats);

        mListView.setAdapter(new ArrayAdapter<Droid>(this, -1, Droid.generateDatas())
        {
            @Override
            public View getView(int position, View convertView, ViewGroup parent)
            {

                ViewHolder holder = null ;
                if(convertView == null)
                {
                    convertView = mInflater.inflate(R.layout.chat_item,parent,false);
                    holder = new ViewHolder();
                    holder.icon = (ImageView) convertView.findViewById(R.id.id_chat_icon);
                    holder.name = (TextView) convertView.findViewById(R.id.id_chat_name);
                    holder.date = (TextView) convertView.findViewById(R.id.id_chat_date);
                    holder.msg = (TextView) convertView.findViewById(R.id.id_chat_msg);
                    convertView.setTag(holder);
                }else
                {
                    holder = (ViewHolder) convertView.getTag();
                }

                Droid droid = getItem(position);
                holder.icon.setBackgroundColor(0x44ff0000);
                holder.icon.setImageResource(droid.imageId);
                holder.date.setText(droid.date);
                holder.msg.setText(droid.msg);
                holder.name.setText(droid.name);

                return convertView;
            }

            class ViewHolder
            {
                ImageView icon;
                TextView name;
                TextView date;
                TextView msg;
            }

        });
    }


}

實(shí)體的代碼

package com.zhy.performance_01_render;

import java.util.ArrayList;
import java.util.List;

public class Droid
{
    public String name;
    public int imageId;
    public String date;
    public String msg;


    public Droid(String msg, String date, int imageId, String name)
    {
        this.msg = msg;
        this.date = date;
        this.imageId = imageId;
        this.name = name;
    }

    public static List<Droid> generateDatas()
    {
        List<Droid> datas = new ArrayList<Droid>();

        datas.add(new Droid("Lorem ipsum dolor sit amet, orci nullam cra", "3分鐘前", -1, "alex"));
        datas.add(new Droid("Omnis aptent magnis suspendisse ipsum, semper egestas", "12分鐘前", R.drawable.joanna, "john"));
        datas.add(new Droid("eu nibh, rhoncus wisi posuere lacus, ad erat egestas", "17分鐘前", -1, "7heaven"));
        datas.add(new Droid("eu nibh, rhoncus wisi posuere lacus, ad erat egestas", "33分鐘前", R.drawable.shailen, "Lseven"));

        return datas;
    }


}

現(xiàn)在的效果是:



注意费就,我們的需求是整體是Activity是個白色的背景个曙。
開啟Show GPU Overdraw以后:



對比上面的參照圖,可以發(fā)現(xiàn)一個簡單的ListView展示Item,竟然很多地方被過度繪制了4X 垦搬。 那么呼寸,其實(shí)主要原因是由于該布局文件中存在很多不必要的背景,仔細(xì)看上述的布局文件猴贰,那么開始移除吧对雪。
  • 不必要的Background 1

我們主布局的文件已經(jīng)是background為white了,那么可以移除ListView的白色背景

  • 不必要的Background 2

Item布局中的LinearLayout的android:background="@android:color/darker_gray"

  • 不必要的Background 3

Item布局中的RelativeLayout的android:background="@android:color/white"

  • 不必要的Background 4

Item布局中id為id_msg的TextView的android:background="@android:color/white"

這四個不必要的背景也比較好找米绕,那么移除后的效果是:



對比之前的是不是好多了~~~接下來還存在一些不必要的背景瑟捣,你還能找到嗎?

  • 不必要的Background 5

這個背景比較難發(fā)現(xiàn)栅干,主要需要看Adapter的getView的代碼迈套,上述代碼你會發(fā)現(xiàn),首先為每個icon設(shè)置了背景色(主要是當(dāng)沒有icon圖的時候去顯示)碱鳞,然后又設(shè)置了一個頭像桑李。那么就造成了overdraw,有頭像的完全沒必要去繪制背景窿给,所有修改代碼:

Droid droid = getItem(position);
                if(droid.imageId ==-1)
                {
                    holder.icon.setBackgroundColor(0x4400ff00);
                    holder.icon.setImageResource(android.R.color.transparent);
                }else
                {
                    holder.icon.setImageResource(droid.imageId);
                    holder.icon.setBackgroundResource(android.R.color.transparent);
                }

ok贵白,還有最后一個,這個也是非常容易被忽略的崩泡。

  • 不必要的Background 6

記得我們之前說禁荒,我們的這個Activity要求背景色是白色,我們的確在layout中去設(shè)置了背景色白色角撞,那么這里注意下呛伴,我們的Activity的布局最終會添加在DecorView中,這個View會中的背景是不是就沒有必要了谒所,所以我們希望調(diào)用mDecor.setWindowBackground(drawable);热康,那么可以在Activity調(diào)用getWindow().setBackgroundDrawable(null);

setContentView(R.layout.activity_overdraw_01); 
getWindow().setBackgroundDrawable(null);

ok,一個簡單的listview顯示item百炬,我們一共找出了6個不必要的背景褐隆,現(xiàn)在再看最后的Show GPU Overdraw 與最初的比較。


ok剖踊,對比參照圖庶弃,基本已經(jīng)達(dá)到了最優(yōu)的狀態(tài)。

4.2 使用布局標(biāo)簽優(yōu)化布局

4.2.1 <include>標(biāo)簽

相信大家使用的最多的布局標(biāo)簽就是 <include>了德澈。 <include>的用途就是將布局中的公共部分提取出來以供其他Layout使用歇攻,從而實(shí)現(xiàn)布局的優(yōu)化。這種布局的編寫方式大大便利了開發(fā)梆造,個人感覺這種思想和React Native中的面向組件編程思想有著異曲同工之妙缴守,都是將特定功能抽取成為一個獨(dú)立的組件葬毫,只要控制其中傳入的參數(shù)就可以滿局不同的需求。例如:我們在編輯Android界面的時候常常需要添加標(biāo)題欄屡穗,如果在不使用<include>的情況下贴捡,只能在每一個需要顯示標(biāo)題欄的xml文件中編寫重復(fù)的代碼,費(fèi)時費(fèi)力村砂。但是只要我們將這個需要多次被使用的標(biāo)題欄布局抽取成一個獨(dú)立的xml文件烂斋,然后在需要的地方使用<include>標(biāo)簽引入即可。
下面以在一個布局main.xml中用include引入另一個布局foot.xml為例础废。main.mxl代碼如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/simple_list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="80dp" />

    <include
        android:id="@+id/my_foot_ly"
        layout="@layout/foot" />

</RelativeLayout>

其中include引入的foot.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" 
    android:id="@+id/my_foot_parent_id">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@+id/title_tv"/>

    <TextView
        android:id="@+id/title_tv"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />
</RelativeLayout>

<include>使用起來很簡單,只需要指定一個layout屬性為需要包含的布局文件即可评腺。當(dāng)然還可以根據(jù)需求指定 android:id帘瞭、 android:heightandroid:width屬性來覆蓋被引入根節(jié)點(diǎn)屬性蒿讥。

注意
在使用<include>標(biāo)簽最常見的問題就是 findViewById查找不到<include>進(jìn)來地控件的跟布局蝶念,而這個問題出現(xiàn)的前提就是在include的時候設(shè)置了id當(dāng)設(shè)置id后诈悍,原有的foot.xml跟布局Id已經(jīng)被替換為在 <include>中指定的id祸轮,所以在 findViewById查找原有id的時候就會報空指針異常兽埃。

View titleView = findViewById(R.id.my_foot_parent_id) ; // 此時id已經(jīng)被覆蓋 titleView 為空侥钳,找不到。此時空指針 
View titleView = findViewById(R.id.my_foot_ly) ; //重寫指定id即可

<include>標(biāo)簽簡單的說就是相當(dāng)與將layout指定的布局整體引入到main.xml中柄错。所以我們就和操作直接在main.xml中的布局是一樣的只不過有一個上面提到的更布局id被覆蓋的問題舷夺。

4.2.2 <ViewStub>標(biāo)簽

ViewStub標(biāo)簽同include一樣可以用來引入一個外部布局。不同的是售貌,ViewStub引入的布局默認(rèn)是不會顯示也不會占用位置的给猾,從而在解析的layout的時候可以節(jié)省cpu、內(nèi)存等硬件資源颂跨。

ViewStub常常用來引入那些默認(rèn)不顯示敢伸,只在特定情況下才出現(xiàn)的布局,例如:進(jìn)度條恒削,網(wǎng)絡(luò)連接失敗顯示的提示布局等池颈。
下面以在一個布局main.xml中加入網(wǎng)絡(luò)錯誤時的提示頁面network_error.xml為例。main.mxl代碼如下:

<?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" >

……
    <ViewStub
        android:id="@+id/network_error_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/network_error" />

</RelativeLayout>

其中network_error.xml為只有在網(wǎng)絡(luò)錯誤時才需要顯示的布局钓丰,默認(rèn)不會被解析躯砰,示例代碼如下:

<?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/network_setting"
        android:layout_width="@dimen/dp_160"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="@string/network_setting" />

    <Button
        android:id="@+id/network_refresh"
        android:layout_width="@dimen/dp_160"
        android:layout_height="wrap_content"
        android:layout_below="@+id/network_setting"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/dp_10"
        android:text="@string/network_refresh" />

</RelativeLayout>

在代碼中通過(ViewStub)findViewById(id)找到ViewStub,通過stub.inflate()展開ViewStub携丁,然后得到子View琢歇,如下:

private View networkErrorView;

private void showNetError() {
  if (networkErrorView != null) {
    networkErrorView.setVisibility(View.VISIBLE);
  }else{
    ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
    if(stub !=null){
      networkErrorView = stub.inflate();
    
      //  效果和上面是一樣的
      //  stub.setVisibility(View.VISIBLE);   // ViewStub被展開后的布局所替換
      //  networkErrorView =  findViewById(R.id.network_error_layout); // 獲取展開后的布局
    }
 }
}

private void showNormal() {
  if (networkErrorView != null) {
    networkErrorView.setVisibility(View.GONE);
  }
}

在上面showNetError()中展開了ViewStub,同時我們對networkErrorView進(jìn)行了保存,這樣下次不用繼續(xù)inflate李茫。

注意這里我對ViewStub的實(shí)例進(jìn)行了一個非空判斷揭保,這是因?yàn)閂iewStub在XML中定義的id只在一開始有效,一旦ViewStub中指定的布局加載之后魄宏,這個id也就失敗了掖举,那么此時findViewById()得到的值也會是空
viewstub標(biāo)簽大部分屬性同include標(biāo)簽類似。

注意:
根據(jù)需求我們有時候需要將View的可講性設(shè)置為GONE娜庇,在inflate時塔次,這個View以及他的字View還是會被解析的。所以使用<ViewStub>就能避免解析其中的指定的布局文件名秀。從而加快布局的解析時間励负,節(jié)省cpu內(nèi)存等硬件資源。同時ViewStub所加載的布局是不可以使用<merge>標(biāo)簽的

4.2.3 <merge>標(biāo)簽

在使用了include后可能會導(dǎo)致布局嵌套太多匕得,導(dǎo)致視圖節(jié)點(diǎn)太多继榆,減慢了解析速度。

merge標(biāo)簽可用于兩種典型情況:

  1. 布局頂接點(diǎn)是FrameLayout并且不需要設(shè)置background或者padding等屬性汁掠,可使用merge代替略吨,因?yàn)?code>Activity內(nèi)容視圖的parent view就是一個FrameLayout,所以可以用merge消除只能一個考阱。
  2. 某布局作為子布局被其他布局include時翠忠,使用merge當(dāng)作該布局的頂節(jié)點(diǎn),這樣在被引入時乞榨,頂結(jié)點(diǎn)會自動被忽略秽之,而其自己點(diǎn)全部合并到主布局中。

以【4.2.1 <include>標(biāo)簽 】中的代碼示例為例吃既,使用用hierarchy viewer查看main.xml布局如下圖:

[引自:Android性能優(yōu)化系列之布局優(yōu)化](http://blog.csdn.net/u012124438/article/details/54564659)

可以發(fā)現(xiàn)多了一層沒必要的RelativeLayout考榨,將foot.xml中RelativeLayout改為merge,如下:

<?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_40"
        android:layout_above="@+id/text"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />

</merge>

運(yùn)行后再次用hierarchy viewer查看main.xml布局如下圖:
[引自:Android性能優(yōu)化系列之布局優(yōu)化](http://blog.csdn.net/u012124438/article/details/54564659)

這樣就不會有多余的RelativeLayout節(jié)點(diǎn)了鹦倚。
參考:
Android UI性能優(yōu)化實(shí)戰(zhàn) 識別繪制中的性能問題
Google 發(fā)布 Android 性能優(yōu)化典范
Android性能優(yōu)化系列之布局優(yōu)化

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末河质,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子震叙,更是在濱河造成了極大的恐慌掀鹅,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捐友,死亡現(xiàn)場離奇詭異淫半,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匣砖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門科吭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昏滴,“玉大人,你說我怎么就攤上這事对人∫ナ猓” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵牺弄,是天一觀的道長姻几。 經(jīng)常有香客問我,道長势告,這世上最難降的妖魔是什么蛇捌? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮咱台,結(jié)果婚禮上络拌,老公的妹妹穿的比我還像新娘。我一直安慰自己回溺,他們只是感情好春贸,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遗遵,像睡著了一般萍恕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上车要,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天允粤,我揣著相機(jī)與錄音,去河邊找鬼屯蹦。 笑死维哈,一個胖子當(dāng)著我的面吹牛绳姨,可吹牛的內(nèi)容都是我干的登澜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼飘庄,長吁一口氣:“原來是場噩夢啊……” “哼脑蠕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跪削,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤谴仙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碾盐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晃跺,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年毫玖,在試婚紗的時候發(fā)現(xiàn)自己被綠了掀虎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凌盯。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烹玉,靈堂內(nèi)的尸體忽然破棺而出驰怎,到底是詐尸還是另有隱情,我是刑警寧澤二打,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布县忌,位于F島的核電站,受9級特大地震影響继效,放射性物質(zhì)發(fā)生泄漏症杏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一瑞信、第九天 我趴在偏房一處隱蔽的房頂上張望鸳慈。 院中可真熱鬧,春花似錦喧伞、人聲如沸走芋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翁逞。三九已至,卻和暖如春溉仑,著一層夾襖步出監(jiān)牢的瞬間挖函,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工浊竟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怨喘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓振定,卻偏偏與公主長得像必怜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子后频,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,290評論 25 707
  • 注意事項(xiàng): 布局優(yōu)化梳庆;盡量使用include、merge卑惜、ViewStub標(biāo)簽膏执,盡量不存在冗余嵌套及過于復(fù)雜布局(...
    HarryXR閱讀 5,182評論 1 19
  • 在說性能優(yōu)化之前,我們必須了解為什么要做性能優(yōu)化露久,首先第一點(diǎn)肯定是為了用戶體驗(yàn)更米,你想啊要是你的App用起來很卡...
    PengJunJun閱讀 410評論 0 0
  • 版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請注明出處毫痕! 優(yōu)化性能一般從渲染征峦,運(yùn)算與內(nèi)存碉纳,電量三個方面進(jìn)行畏腕,...
    LooperJing閱讀 23,437評論 20 88
  • 很衰的片子只適合心情愉悅的時候看立莉,當(dāng)你本身很衰的時候榨崩,再看看那些生活的“饋贈”,你懂的竖伯。 其實(shí)也還好還好存哲,只是每次...
    狐十八閱讀 446評論 0 0