Android 旗艦機標配的高幀屏(120Hz),對各位 App 開發者有什麼影響?

各大手機廠商發佈的旗艦機,都將 90Hz 甚至 120Hz 高幀率流速屏當成了標配,那這對我們實際開發的 App 會不會有影響?原本在 60Hz 下,每幀只需保證 16ms 內繪製完成就可以做到流暢,換到高幀屏中,實際留給我們繪製時間是縮短了的,是否對我們的代碼質量要求更高?

推薦一篇文章,來講講對此的影響,希望對大家有幫助。

以下文內的 "我" 爲文章原作者。

前言

昨天在 IT 之家留言說,如果應用無法滿足 120hz 的繪製會怎樣?假設如果繪製一幀的時間,如果大於 1/120 秒,哪怕是多了 1 毫秒,就會導致應用在 120Hz 的手機上也就變成了 60Hz。

後來仔細想想,這句話說的並不是特別嚴謹,爲什麼這麼說呢?

一、證明我的觀點

首先我寫一個 demo 來證明我的觀點。

1.1 滿幀的應用

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

我寫了一個只繪製 6 幀的一個界面,Activity 中包含這個 MyTextView,通過抓 Trace 可以看到,下面這個 6 幀繪製圖,都是在一個 Vsync 週期(圖中黑白相見的色塊,均是一個 Vsync 週期)繪製一幀。

爲什麼第一幀會有點延遲,主要是因爲啓動時,主線程在幹了些其他事情,好在繪製花不了太多時間。

大概繪製也就 3 毫秒左右,畢竟界面簡單。

1.2 幀數減半的應用

public class MyTextView extends TextView {
    int i = 0;
    @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 毫秒,來模擬很多應用實現的不好,導致繪製耗時的情況,例如 View 層級厲害複雜,甚至在 onDraw() 方法中進行文件讀寫。

通過抓 trace 可以看到,原本之前的例子(1.1)中 6 幀由 6 個時間週期繪製完成,現在變成了 6 幀由 12 個時間週期完成了。

1.3 小結

這就是我一直想說的一個觀點,120Hz 的手機體驗不一定有 90Hz 的手機好的原因,一旦主線程一次 traversal(一次 ViewRootImpl 的 onDraw() 的遍歷)的時間超出了 vsync 的時間週期 1/120s,就會導致幀率直接砍半,降爲 60 幀,90Hz 可以讓這個折半降幀率的現象減少。

二、繪製一幀指的是什麼

爲什麼我說我的觀點不準確?其實是我太籠統的定義了繪製一幀這個詞。

在硬件加速開啓的情況下:

  • 如果我將繪製一幀的時間定義成:主線程一次 traversal,那我說的話是正確的。

  • 如果我將繪製一幀的時間定義成:主線程一次 traversal + renderthread 一次渲染,那我說的話是不嚴謹的。

2.1 引發我思考的 trace

因爲要讓主線程一次 traversal + renderthread 超時不是特別好寫 demo,我就用一個昨天引發我思考的一個 trace 給大家講解一下。

大家從第一個黃色 F 的圓圈開始看。

UI Thread 的 doFrame + RenderThread 的 DrawFrame 的時間超出了一個 Vsync 週期,接下來每一幀的情況,都是按照第一幀繪製的情況一樣的情況運行,最好造成的現象就是雖然幀數是滿幀,但是每一幀其實都是延遲顯示的。

用一句哲學的話來概括就是:你眼睛看到任何事物,其實都是事物過去的樣子

假如主線程一次 traversal + renderthread 一次渲染時間超出了 vsync 週期不多,這樣子的應用大概率是可以滿幀運行滿幀不代表顯示正常)。

我上面一句話中用了不多,大概率這種模糊的文字,因爲這個情況下能否滿幀的臨界點比較難定義。

2.2 如何定義這個 120 變成 60 幀的臨界點

繼續下面這個圖,你會發現 UI Thread 的 doFrame 會非常 happy 的按照 Vsync 信號執行,但是下面 renderthread 已經忙的不可開交了。

其實第一幀已經延遲了,假如運氣不好導致了 drawframe 的時間橫跨了 2 個 Vsync 週期,還是會導致丟幀,這個時候就從 120 幀降爲 60 幀。

三、總結

3.1 理想中的結論

假設開啓硬件加速,一幀繪製的時間 = UI 線程(T1)+ RenderThread 線程(T2)。

那麼 Vsync 時間的週期是 1/120s,約等於 8.3ms,此時:

  • 如果 16.6ms > T1 > 8.3ms,會導致從 120 幀降爲 60 幀。

  • 如果 T1 <8.3ms,16.6ms> T1 + T2 > 8.3ms,雖然還是滿幀,但是你看到的永遠是前一幀的畫面。

  • 如果 T1 + T2 > 16.6ms,毫無疑問會導致從 120 幀降爲 60 幀。

上面理論看起來是很完美的結論,千萬別把這個當做定理去記憶,因爲現實會比這些情況複雜的多。

3.2 顯示中的結論

可能會有人說現在 CPU,GPU 那麼強,怎麼可能會超時呢?

舉個微信在 865 的手機上,滑動消息列表的例子,T1 約等於 6ms,T1 + T2 約等於 8.2ms。

這還並不是太過複雜的界面,大家會發現,6ms 已經很接近 8.3ms 了。

3.3 給開發者的建議

對於要適配 120hz 手機的應用的工程師,你們要注意以下事情。

  1. 避免 T1 > 8.3ms,也就是要減少主線程乾的活,要減少一次 draw 的時間。千萬別在 draw 的流程進行文件訪問或者 sleep。

  2. 避免在滑動響應的時候 T1+T2 > 8.3ms,雖然界面在顯示的時候,推遲一幀用戶是察覺不了的,但是在滑動的響應中如果推遲一幀,可能會影響 "跟手" 的體驗。

  3. 千萬別關閉硬件加速,一旦關閉硬件加速 T1+T2 > 8.3ms 就會導致 120 降爲 60 幀。

四、尾巴

說到這裏,如果聽得懂我在說什麼的人,應該能懂我想表達的意思。
如果不懂我說的什麼的人,希望你去補一下以下知識點,再回過頭來看這個文章。

知識點 1:

TextView.setText 到屏幕顯示文字,整個過程發生了什麼。

知識點 2:

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

知識點 3:

APP 界面和 SurfaceFlinger 之間的關係

可能會有人說,你揪那麼細緻幹嘛,對於用戶來說,掉了幾幀的影響也不大,,但是對於性能優化工程師來說,有時候就是要糾結那幾個丟幀的情況,只有做好了每一幀的完美繪製,才能給用戶帶來最完美的用戶體驗。

- End -

文章看完了,最後我換個角度補充一些觀點。

手機硬件雖然增加了高幀屏,確實看似對每一幀繪製的時間要求更短了,但別忘了,在升級屏幕的同時,對其他硬件(CPU、GPU、ROM等)也做了相應的升級。雖然留給我們的刷新時間縮短了,但計算和渲染繪製的能力,也相應的提高了。

那麼到具體的代碼上,你在舊手機上繪製一幀需要 10ms 的代碼,在 120Hz 的旗艦機上可能只需要 6ms,兩相抵消之下問題不會太大。

所以最後核心還是在日常編碼的過程中,隨時注意性能的問題。管他高幀低幀,領導覺得"跟手",咱就嘚優化。

本文對你有幫助嗎?留言、轉發、點好看是最大的支持,謝謝!

公衆號後臺回覆成長『成長』,將會得到我準備的學習資料。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章