本文同時發(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)屏幕上有各種顏色倔监,此時你可以切換到需要檢測的程序與界面,對于各個色塊的含義荡短,請看下圖:
藍(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 與最初的比較。
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:height
、android: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)簽可用于兩種典型情況:
- 布局頂接點(diǎn)是
FrameLayout
并且不需要設(shè)置background
或者padding
等屬性汁掠,可使用merge
代替略吨,因?yàn)?code>Activity內(nèi)容視圖的parent view
就是一個FrameLayout
,所以可以用merge
消除只能一個考阱。 - 某布局作為子布局被其他布局include時翠忠,使用merge當(dāng)作該布局的頂節(jié)點(diǎn),這樣在被引入時乞榨,頂結(jié)點(diǎn)會自動被忽略秽之,而其自己點(diǎn)全部合并到主布局中。
以【4.2.1 <include>標(biāo)簽 】中的代碼示例為例吃既,使用用hierarchy viewer查看main.xml布局如下圖:
可以發(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布局如下圖: 這樣就不會有多余的RelativeLayout節(jié)點(diǎn)了鹦倚。
參考:
Android UI性能優(yōu)化實(shí)戰(zhàn) 識別繪制中的性能問題
Google 發(fā)布 Android 性能優(yōu)化典范
Android性能優(yōu)化系列之布局優(yōu)化