Android布局優(yōu)化(五)繪制優(yōu)化—避免過度繪制

如需轉(zhuǎn)載請?jiān)u論或簡信,并注明出處凶硅,未經(jīng)允許不得轉(zhuǎn)載

目錄

前言

本系列的前面幾篇文章我們介紹了布局加載的原理及優(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)畫和輸入處理

  1. 測量、布局啄枕、繪制過程都會(huì)存在自頂而下遍歷過程频祝,所以如果布局的層級(jí)過多,這會(huì)占用額外的CPU資源
  2. 當(dāng)屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次(Overdraw)常空,這會(huì)浪費(fèi)大量的CPU以及GPU資源
  3. 在繪制過程,也就是onDraw()方法內(nèi)漓糙,我們應(yīng)該盡量避免局部對(duì)象的創(chuàng)建昆禽,因?yàn)?code>onDraw()方法在繪制過程中會(huì)多次調(diào)用,大量的局部變量可能會(huì)造成內(nèi)存抖動(dòng)
  4. 合理使用動(dòng)畫捡硅,這個(gè)本章不做討論盗棵,有興趣的可以自己了解動(dòng)畫的相關(guān)知識(shí)
  5. 不應(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è)問題

  1. 在xml中設(shè)置
 <item name="android:windowBackground">@null</item>

通過代碼設(shè)置

 getWindow().setBackgroundDrawable(null);

移除控件中不需要的背景

例子:

  1. 列表頁(RecyclerView) 與 其內(nèi)子控件(Item)的背景相同淤年,故可移除子控件(Item)布局中的背景
  2. 對(duì)于1個(gè)ViewPager+多個(gè) Fragment 組成的首頁界面逻炊,若每個(gè)Fragment 都設(shè)有背景色,即 ViewPager 則無必要設(shè)置豹休,可移除

所以對(duì)于控件背景顏色的設(shè)置基本可以歸納為以下兩個(gè)原則:

  1. 對(duì)于子控件桨吊,如果其背景顏色跟父布局一致视乐,那么就不用再給子控件添加背景了
  2. 如果子控件背景五顏六色,且能夠完全覆蓋父布局留美,那么父布局就可以不用添加背景了

減少透明度的使用

對(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)簽

  1. 自定義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>
  1. 有時(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)ViewStubinflate到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):

  1. ViewStub標(biāo)簽不支持merge標(biāo)簽斩祭。因此這有可能導(dǎo)致加載出來的布局存在著多余的嵌套結(jié)構(gòu)乡话,具體如何去取舍就要根據(jù)各自的實(shí)際情況來決定了

  2. ViewStubinflate只能被調(diào)用一次,第二次調(diào)用會(huì)拋出異常

  3. 雖然ViewStub是不占用任何空間的,但是每個(gè)布局都必須要指定layout_widthlayout_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ì)你有所幫助

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載次伶,如需轉(zhuǎn)載請通過簡信或評(píng)論聯(lián)系作者稽穆。
  • 序言:七十年代末舌镶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哟楷,更是在濱河造成了極大的恐慌卖擅,老刑警劉巖墨技,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣汪,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)统刮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門侥蒙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鞭衩,“玉大人,你說我怎么就攤上這事瑞佩∨魈ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵稠炬,是天一觀的道長首启。 經(jīng)常有香客問我,道長褒纲,這世上最難降的妖魔是什么疾嗅? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任代承,我火速辦了婚禮论悴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘幔亥。我一直安慰自己察纯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著具则,像睡著了一般博肋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膊畴,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天巴比,我揣著相機(jī)與錄音,去河邊找鬼采记。 笑死政勃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的既棺。 我是一名探鬼主播懒叛,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼薛窥,長吁一口氣:“原來是場噩夢啊……” “哼诅迷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趟畏,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤滩租,失蹤者是張志新(化名)和其女友劉穎赋秀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體律想,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猎莲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜘欲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片益眉。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晌柬,死狀恐怖姥份,靈堂內(nèi)的尸體忽然破棺而出年碘,到底是詐尸還是另有隱情澈歉,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布屿衅,位于F島的核電站埃难,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涡尘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一忍弛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧考抄,春花似錦细疚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贫途,卻和暖如春吧彪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丢早。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工姨裸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人香拉。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓啦扬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凫碌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扑毡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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