[053]一條評(píng)論引發(fā)的思考

前言

昨天在IT之家留言說(shuō)如果應(yīng)用無(wú)法滿足120hz的繪制,假設(shè)如果繪制一幀的時(shí)間如果大于1/120秒,哪怕是多了1毫秒,就會(huì)導(dǎo)致應(yīng)用在120hz的手機(jī)上也就變成了60hz。

后來(lái)仔細(xì)想想這句話說(shuō)的并不是特別嚴(yán)謹(jǐn),為什么這么說(shuō)呢?

一、證明我的觀點(diǎn)

首先我寫(xiě)一個(gè)demo來(lái)證明我的觀點(diǎn)意思

1.1 滿幀的應(yīng)用

ublic class MyTextView extends TextView {

    int i = 0;

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (i < 6) {
            this.post(new Runnable() {
                @Override
                public void run() {
                    MyTextView.this.setText(i++ + "");
                }
            });
        }
    }
}

我寫(xiě)了一個(gè)只繪制6幀的一個(gè)界面,activity中包含這個(gè)TextView,通過(guò)抓trace可以看到,下面這個(gè)漂亮6幀繪制,都是在一個(gè)vsync周期(圖中黑白背景,一個(gè)白色或者黑色就是一個(gè)vsync周期)繪制一幀。

為什么第一幀會(huì)有點(diǎn)延遲是因?yàn)橹骶€程在干其他事情,好在繪制花不了太多時(shí)間。
大概繪制也就3毫秒左右,畢竟界面簡(jiǎn)單。


1.2 幀數(shù)減半的應(yīng)用

public class MyTextView extends TextView {

    int i = 0;

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        try {
            Thread.sleep(16);
        } catch (Exception e) {
        }
        if (i < 6) {
            this.post(new Runnable() {
                @Override
                public void run() {
                    MyTextView.this.setText(i++ + "");
                }
            });
        }
    }
}

我人為在MyTextView.onDraw方法中sleep了16毫秒,來(lái)模擬很多應(yīng)用寫(xiě)的不好,例如View層級(jí)厲害復(fù)雜,甚至在onDraw方法中進(jìn)行文件讀寫(xiě)。通過(guò)抓trace可以看到,原本1.1中6幀6個(gè)時(shí)間周期繪制完成,現(xiàn)在變成了6幀12個(gè)時(shí)間周期完成了。


1.3 總結(jié)

這就是我一直想說(shuō)的一個(gè)觀點(diǎn),120hz的手機(jī)體驗(yàn)不一定有90hz的手機(jī)好的原因,一旦主線程一次traversal(一次ViewRootImpl的ondraw的遍歷)的事情超出了vsync的時(shí)間周期1/120s,就會(huì)導(dǎo)致幀率直接砍半,降為60幀,90hz可以讓這個(gè)折半降幀率的現(xiàn)象減少。

二、繪制一幀指的是什么

為什么我的觀點(diǎn)不準(zhǔn)確的原因,就是我太籠統(tǒng)的定義了繪制一幀這個(gè)詞。

在硬件加速開(kāi)啟的情況下:
如果我將繪制一幀的時(shí)間定義成:主線程一次traversal,那我說(shuō)的話是正確的。
如果我將繪制一幀的時(shí)間定義成:主線程一次traversal + renderthread一次渲染,那我說(shuō)的話是不嚴(yán)謹(jǐn)?shù)摹?/p>

2.1 引發(fā)我思考的trace

因?yàn)橐屩骶€程一次traversal + renderthread超時(shí)不是特別好寫(xiě)demo,我就用一個(gè)昨天引發(fā)我思考的一個(gè)trace給大家講解一下。



大家從第一個(gè)黃色F的圓圈開(kāi)始看,UI Thread的doFrame + RenderThread的DrawFrame的時(shí)間超出了一個(gè)Vsync周期,接下來(lái)每一幀的情況都是按照第一幀繪制的情況一樣的情況運(yùn)行,最好造成的現(xiàn)象就是雖然幀數(shù)是滿幀,但是每一幀其實(shí)都是延遲顯示的。

用一句哲學(xué)的話來(lái)概括就是:你眼睛看到任何事物,其實(shí)都是事物過(guò)去的樣子
假如主線程一次traversal + renderthread一次渲染時(shí)間超出了vsync周期不多,這樣子的應(yīng)用大概率是可以滿幀運(yùn)行(滿幀不代表顯示正常)。

我上面一句話中用了不多,大概率這種模糊的文字,因?yàn)檫@個(gè)情況下能否滿幀的臨界點(diǎn)比較難定義。

2.2 如何定義這個(gè)120變成60幀的臨界點(diǎn)

繼續(xù)下面這個(gè)圖,你會(huì)發(fā)現(xiàn)UI Thread的doFrame會(huì)非常happy的按照vsync信號(hào)執(zhí)行,但是下面renderthread已經(jīng)忙的不可開(kāi)交了。

其實(shí)第一幀已經(jīng)延遲了,假如運(yùn)氣不好導(dǎo)致了drawframe的時(shí)間橫跨了2個(gè)vsync周期,還是會(huì)導(dǎo)致丟幀,這個(gè)時(shí)候就從120幀降為60幀。

三、總結(jié)

3.1 理想中的結(jié)論

假設(shè)開(kāi)啟硬件加速,一幀繪制的時(shí)間 = UI線程(T1)+ RenderThread線程(T2)
Vsync時(shí)間的周期是1/120s,約等于8.3ms
如果 16.6ms > T1 > 8.3ms,會(huì)導(dǎo)致從120幀降為60幀。
如果T1 < 8.3ms,16.6ms > T1 + T2 > 8.3ms,雖然還是滿幀,但是你看到的永遠(yuǎn)是前一幀的畫(huà)面。
如果 T1 + T2 > 16.6ms,毫無(wú)疑問(wèn)會(huì)導(dǎo)致從120幀降為60幀。

上面理論看起來(lái)是很完美的結(jié)論,千萬(wàn)別把這個(gè)當(dāng)做定理去記憶,因?yàn)槠鋵?shí)現(xiàn)實(shí)會(huì)比這些情況復(fù)雜的多。
3.2 顯示中的結(jié)論

可能會(huì)有人說(shuō)現(xiàn)在CPU,GPU那么強(qiáng),怎么可能會(huì)超時(shí)呢?
舉個(gè)微信在865的手機(jī)的滑動(dòng)消息列表的例子,T1 約等于6ms,T1 + T2約等于8.2ms。
這還是不怎么復(fù)雜的界面,大家會(huì)發(fā)現(xiàn),6ms已經(jīng)很接近8.3ms了。

3.3 給開(kāi)發(fā)者的建議

對(duì)于要適配120hz手機(jī)的應(yīng)用的工程師,你們要注意以下事情。
1.避免T1 > 8.3ms,也就是要減少主線程干的活,要減少一次draw的時(shí)間。千萬(wàn)別再draw的流程進(jìn)行文件訪問(wèn)或者sleep。
2.避免在滑動(dòng)響應(yīng)的時(shí)候T1+T2 > 8.3ms,雖然界面在顯示的時(shí)候,推遲一幀用戶是察覺(jué)不了的,但是在滑動(dòng)的響應(yīng)中如果推遲一幀,可能會(huì)有跟手性的問(wèn)題出現(xiàn)了。
3.千萬(wàn)別關(guān)閉硬件加速,一旦關(guān)閉硬件加速T1+T2 > 8.3ms就會(huì)導(dǎo)致120降為60幀。

四、尾巴

說(shuō)到這里,如果聽(tīng)得懂我在說(shuō)什么的人,應(yīng)該能懂我想表達(dá)的意思。
如果不懂我說(shuō)的什么的人,希望你去補(bǔ)一下以下知識(shí)點(diǎn),再回過(guò)頭來(lái)看這個(gè)文章。

知識(shí)點(diǎn)1:

TextView.setText到屏幕顯示文字,整個(gè)過(guò)程發(fā)生了什么。

知識(shí)點(diǎn)2:

硬件加速之后,主線程(UI Thread)和RenderThread之間的協(xié)同工作的方式。

知識(shí)點(diǎn)3:

APP界面和SurfaceFlinger之間的關(guān)系

可能會(huì)有人說(shuō),你揪那么細(xì)致干嘛,對(duì)于用戶來(lái)說(shuō),掉了幾幀的影響也不大,,但是對(duì)于性能優(yōu)化工程師來(lái)說(shuō),有時(shí)候就是要糾結(jié)那幾個(gè)丟幀的情況,只有做好了每一幀的完美繪制,才能給用戶帶來(lái)最完美的用戶體驗(yàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容