如需轉(zhuǎn)載請?jiān)u論或簡信,并注明出處凶硅,未經(jīng)允許不得轉(zhuǎn)載
- Android布局優(yōu)化(一)LayoutInflate — 從布局加載原理說起
- Android布局優(yōu)化(二)優(yōu)雅獲取界面布局耗時(shí)
- Android布局優(yōu)化(三)使用AsyncLayoutInflater異步加載布局
- Android布局優(yōu)化(四)X2C — 提升布局加載速度200%
- Android布局優(yōu)化(五)繪制優(yōu)化—避免過度繪制
目錄
前言
本系列的前面幾篇文章我們介紹了布局加載的原理及優(yōu)化,布局加載完成后(生成VIew對(duì)象)就要進(jìn)行視圖繪制捷绑,我們知道编检,android要求每幀的繪制時(shí)間不超過16ms允懂,不然就會(huì)導(dǎo)致丟幀及應(yīng)用卡頓。所以本文將會(huì)介紹一些布局繪制優(yōu)化技巧
如何監(jiān)控應(yīng)用渲染速度
點(diǎn)擊設(shè)置—>開發(fā)人員選項(xiàng)—>監(jiān)控—>GPU呈現(xiàn)模式分析粥航,然后選擇 在屏幕上顯示為條形圖 即可以看到一個(gè)圖表生百,如下圖所示
1.沿水平軸的每個(gè)豎條都代表一個(gè)幀蚀浆,每個(gè)豎條的高度表示渲染該幀所花的時(shí)間(單位:毫秒)
2.水平綠線表示 16 毫秒。 要實(shí)現(xiàn)每秒 60 幀杨凑,代表每個(gè)幀的豎條需要保持在此線以下摆昧。 當(dāng)豎條超出此線時(shí),可能會(huì)使動(dòng)畫出現(xiàn)暫停
再來看下每個(gè)豎條的顏色代表什么意思:
分析從哪些方向進(jìn)行繪制優(yōu)化
從GPU呈現(xiàn)模式分析可以看出來伺帘,我們能夠進(jìn)行優(yōu)化的點(diǎn)主要就是測量伪嫁、布局偶垮、繪制驹吮、動(dòng)畫和輸入處理
- 測量、布局啄枕、繪制過程都會(huì)存在自頂而下遍歷過程频祝,所以如果布局的層級(jí)過多,這會(huì)占用額外的CPU資源
- 當(dāng)屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次(Overdraw)常空,這會(huì)浪費(fèi)大量的CPU以及GPU資源
- 在繪制過程,也就是
onDraw()
方法內(nèi)漓糙,我們應(yīng)該盡量避免局部對(duì)象的創(chuàng)建昆禽,因?yàn)?code>onDraw()方法在繪制過程中會(huì)多次調(diào)用,大量的局部變量可能會(huì)造成內(nèi)存抖動(dòng) - 合理使用動(dòng)畫捡硅,這個(gè)本章不做討論盗棵,有興趣的可以自己了解動(dòng)畫的相關(guān)知識(shí)
- 不應(yīng)該在Event響應(yīng)的回調(diào)中做耗時(shí)操作
總結(jié)下來視圖繪制優(yōu)化主要要解決的問題就是:
減少view樹層級(jí)纹因,要寬而淺,避免窄而深
如何檢測過度繪制
點(diǎn)擊設(shè)置—>開發(fā)人員選項(xiàng)—>硬件—>調(diào)試GPU過度繪制逼蒙,然后選擇 顯示過度繪制區(qū)域 即可以看到一個(gè)圖表寄疏,如下圖所示
再來看下每種顏色代表什么意思:
有些過度繪制是無法避免的陕截。但是在優(yōu)化界面時(shí),應(yīng)該盡量讓大部分的界面顯示為原色(即無過度繪制)或者為藍(lán)色(僅有 1 次過度繪制)社搅。如果出現(xiàn)粉色或者紅色,應(yīng)該查看代碼看看能否盡量避免
如何避免過度繪制
移除window的背景
一般情況下我們的AppTheme
都默認(rèn)帶會(huì)有windowBackground
<style name="AppTheme" parent="Theme.AppCompat.Light">
...
<item name="android:windowBackground">@color/background_material_light</item>
...
</style>
但是這個(gè)windowBackground
大部分清潔下都是沒有什么意義的合呐,因?yàn)槲覀兺紩?huì)在布局文件中設(shè)置我們當(dāng)前view的背景顏色淌实。如果我們同時(shí)設(shè)置了windowBackground
和布局文件中的background
猖腕,那就會(huì)出現(xiàn)兩次繪制,這顯然是沒有什么意義的放坏,因?yàn)樽罱K用戶看到的顏色還是以background
為準(zhǔn)
我們可以通過下面兩個(gè)方法來解決這個(gè)問題
- 在xml中設(shè)置
<item name="android:windowBackground">@null</item>
通過代碼設(shè)置
getWindow().setBackgroundDrawable(null);
移除控件中不需要的背景
例子:
- 列表頁(
RecyclerView
) 與 其內(nèi)子控件(Item
)的背景相同淤年,故可移除子控件(Item
)布局中的背景 - 對(duì)于1個(gè)
ViewPager
+多個(gè)Fragment
組成的首頁界面逻炊,若每個(gè)Fragment
都設(shè)有背景色,即 ViewPager 則無必要設(shè)置豹休,可移除
所以對(duì)于控件背景顏色的設(shè)置基本可以歸納為以下兩個(gè)原則:
- 對(duì)于子控件桨吊,如果其背景顏色跟父布局一致视乐,那么就不用再給子控件添加背景了
- 如果子控件背景五顏六色,且能夠完全覆蓋父布局留美,那么父布局就可以不用添加背景了
減少透明度的使用
對(duì)于不透明的view
伸刃,只需要渲染一次即可把它顯示出來。但是如果這個(gè)view
設(shè)置了alpha
值景图,則至少需要渲染兩次碉哑。這是因?yàn)槭褂昧?code>alpha的view
需要先知道混合view
的下一層元素是什么,然后再結(jié)合上層的view
進(jìn)行Blend混色處理妆毕。透明動(dòng)畫笛粘、淡入淡出和陰影等效果都涉及到某種透明度,這就會(huì)造成了過度繪制⊥及兀可以通過減少渲染這些透明對(duì)象來改善過度繪制蚤吹。比如:在TextView
上設(shè)置帶透明度alpha
值的黑色文本可以實(shí)現(xiàn)灰色的效果。但是繁涂,直接通過設(shè)置灰色的話能夠獲得更好的性能
使用ConstraintLayout減少布局層級(jí)
ConstraintLayout
二驰,可以翻譯為約束布局桶雀,在2016年Google I/O 大會(huì)上發(fā)布。ConstraintLayout
相比RelativeLayout
全肮,其性能更好棘捣,也更容易使用。連官方的hello world都用ConstraintLayout
來寫了评疗。所以極力推薦使用ConstraintLayout
來編寫布局
關(guān)于ConstraintLayout
如何使用禁熏,推薦一篇文章講的非常詳細(xì):http://www.reibang.com/p/17ec9bd6ca8a瞧毙,所以這里就不過多介紹了寄症。當(dāng)你熟練使用它之后有巧,相信我悲没,你再也不想用其他布局了!
使用merge標(biāo)簽減少布局層級(jí)
我們通過兩個(gè)例子來認(rèn)識(shí)merge
標(biāo)簽
- 自定義view時(shí)使用
merge
標(biāo)簽
比如我們現(xiàn)在要寫一個(gè)自定義viewGroup繼承自RelativeLayout
public class MyViewGroup extends RelativeLayout {
public MyView(Context context) {
this(context, null, 0);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public void initView() {
LayoutInflater.from(getContext()).inflate(R.layout.layout_my_view, this, true);
}
}
我們通過LayoutInflater
將XML加載出view并添加到這個(gè)自定義view的根布局中甜橱,這時(shí)候我們的XML文件就可以這么寫岂傲。我們在根布局中使用了merge標(biāo)簽子檀,就代表這個(gè)xml文件的根布局就是其parent,也就是我們上面的MyViewGroup
褂痰,這樣相比在根布局中使用RelativeLayout
就減少了一個(gè)布局層級(jí)
這里有一個(gè)細(xì)節(jié)需要注意:當(dāng)我們使用merge
標(biāo)簽時(shí)缩歪,如果我們希望在Design窗口中實(shí)時(shí)預(yù)覽布局效果,我們需要使用 tools:parentTag="android.widget.RelativeLayout"
來告訴AndroidStudio你的父布局是什么
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:parentTag="android.widget.RelativeLayout">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="hello world" />
</merge>
- 有時(shí)候我們會(huì)通 過
include
標(biāo)簽來提高布局的復(fù)用性苟翻,如果layout_include_xx.xml
的布局和其父布局使用的是同一個(gè)布局類型崇猫,如線性布局等需忿。這時(shí)候就可以在layout_include_xx.xml
中使用merge
標(biāo)簽來減少布局層級(jí)
使用ViewStub標(biāo)簽延遲加載
ViewStub
是一個(gè)不可見的View
類,用于在運(yùn)行時(shí)按需懶加載資源涕烧,只有在代碼中調(diào)用了viewStub.inflate()
或者viewStub.setVisible(View.visible)
方法時(shí)才內(nèi)容才變得可見议纯。這里需要注意的一點(diǎn)是溢谤,當(dāng)ViewStub
被inflate
到parent時(shí)憨攒,ViewStub
就被remove掉了肝集,即當(dāng)前view hierarchy中不再存在ViewStub
蛛壳,而是使用對(duì)應(yīng)的layout
視圖代替
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/layout_error_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
通常用于不常使用的控件衙荐,如
- 網(wǎng)絡(luò)請求失敗的提示
- 列表為空的提示
- 新內(nèi)容、新功能的引導(dǎo)树肃,因?yàn)橐龑?dǎo)基本上只顯示一次
- 又或者我們寫了一個(gè)通用的自定義 View瀑罗,但其中部分子 View 只在部分情況下才顯示
ViewStub
標(biāo)簽使用注意點(diǎn):
ViewStub
標(biāo)簽不支持merge
標(biāo)簽斩祭。因此這有可能導(dǎo)致加載出來的布局存在著多余的嵌套結(jié)構(gòu)乡话,具體如何去取舍就要根據(jù)各自的實(shí)際情況來決定了ViewStub
的inflate
只能被調(diào)用一次,第二次調(diào)用會(huì)拋出異常雖然
ViewStub
是不占用任何空間的,但是每個(gè)布局都必須要指定layout_width
和layout_height
屬性诬像,否則運(yùn)行就會(huì)報(bào)錯(cuò)
減少自定義View的過度繪制,使用clipRect()
下面我們自定義一個(gè)View用來顯示多張重疊的圖片坏挠,效果圖如下:
其onDraw()
方法也很簡單邪乍,就是遍歷所有圖片庇楞,然后繪制出來:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < imgs.length; i++) {
canvas.drawBitmap(imgs[i], i * 100, 0, mPaint);
}
}
顯示過度繪制區(qū)域:
過度繪制比較嚴(yán)重吕晌,那么如何解決?
我們先來分析一下為什么會(huì)出現(xiàn)過度繪制:以第一張圖為例烙心,上面的代碼會(huì)把整張圖都繪制出來了,第二張?jiān)诘谝粡埳厦胬^續(xù)繪制溃论,這就造成了過度繪制
那么痘昌,解決辦法也很簡單,對(duì)于前面的n-1張圖辆苔,我們只需要繪制一部分即可,對(duì)于最后一張才繪制完整的菲驴。
Canvas
中的clipRect()
方法能夠設(shè)置一個(gè)裁剪矩形赊瞬,只在這個(gè)矩形區(qū)域內(nèi)的內(nèi)容才能夠繪制出來
優(yōu)化后的代碼如下:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < imgs.size(); i++) {
canvas.save();
if (i < imgs.size() - 1) {
//前面的n-1張圖贼涩,只裁剪一部分
canvas.clipRect(i * 100, 0, (i + 1) * 100, imgs.get(i).getHeight());
} else if (i == imgs.size() - 1) {
//最后一張遥倦,完整的
canvas.clipRect(i * 100, 0, i * 100 + imgs.get(i).getWidth(), imgs.get(i).getHeight());
}
canvas.drawBitmap(imgs.get(i), i * 100, 0, mPaint);
canvas.restore();
}
}
優(yōu)化后的效果圖如下:
所有區(qū)域都是藍(lán)色的袒哥,即只有1次過度繪制。
Canvas
除了clipRect()
方法外瞎抛,還有clipPath()
等方法却紧,優(yōu)化時(shí)選擇合理的方法去裁剪即可
總結(jié)
布局加載優(yōu)化主要從IO和反射為突破口啄寡,也可以通過異步加載從側(cè)面環(huán)境這個(gè)問題。而布局繪制優(yōu)化致力于解決過度繪制問題懒浮。本系列文章(布局優(yōu)化)到此就結(jié)束了,希望對(duì)你有所幫助