畫面更新率

影格速率 API 可讓應用程式向 Android 平台告知其預期的影格速率,並適用於指定 Android 11 (API 級別 30) 以上版本的應用程式。一般來說,大多數裝置都僅支援單一螢幕刷新率。 通常為 60 Hz,但現在已經改變。許多裝置現在支援 刷新率,例如 90Hz 或 120Hz。某些裝置支援流暢的刷新率 其他切換器則會短暫顯示黑色畫面,通常維持一秒。

API 的主要用途是讓應用程式能更有效地利用所有支援的螢幕更新率。例如,應用程式會播放 24Hz 影片 呼叫 setFrameRate() 可能會導致裝置切換顯示畫面 刷新率從 60Hz 到 120Hz。這項新刷新率可讓 24 Hz 影片流暢播放,且不會出現抖動,也不需要在 60 Hz 螢幕上播放相同影片時進行 3:2 拉降。這可以帶來更好的使用者 無須專人管理

基本用法

Android 提供多種存取與控制介面的方式, 多個版本的 setFrameRate() API。每個 API 版本都會使用相同的參數,並以相同方式運作:

應用程式不需考量實際支援的螢幕刷新率 取得解決方案時,您可以呼叫 Display.getSupportedModes()、 以便安全地呼叫 setFrameRate()。例如 支援 60Hz,請使用應用程式偏好的畫面更新率呼叫 setFrameRate()。 如果裝置無法找到更適合應用程式影格速率的設定,則會維持目前的顯示器刷新率。

查看呼叫 setFrameRate() 是否會改變螢幕重新整理方式 率,請呼叫 DisplayManager.registerDisplayListener()AChoreographer_registerRefreshRateCallback()

呼叫 setFrameRate() 時,請盡量傳入確切的幀率,而非四捨五入為整數。例如,轉譯在某個 29.97Hz,以 29.97 傳遞,而非四捨五入至 30。

針對影片應用程式,傳遞至 setFrameRate() 的相容性參數應設為 Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,以便向 Android 平台提供額外提示,指出應用程式會使用下拉功能,以便配合不相符的螢幕更新率 (這會導致畫面抖動)。

在某些情況下,影片介面會停止提交影格,但不會影響 顯示在螢幕上一段時間常見的情況包括播放影片結束時,或使用者暫停播放時。在這些情況下 呼叫 setFrameRate(),並將畫面更新率參數設為 0,以清除途徑的 畫面更新率會改回預設值。在銷毀途徑時,或當使用者切換至其他應用程式而導致途徑隱藏時,就不需要清除這類影格速率設定。只有在途徑仍可見但未使用時,才需要清除影格速率設定。

非流暢的畫格速率切換

在某些裝置上,切換刷新率可能會造成視覺中斷,例如畫面會黑一兩秒。這通常發生在機上盒、電視面板 和類似裝置根據預設,Android 架構不會在呼叫 Surface.setFrameRate() API 時切換模式,以避免發生這類視覺中斷情形。

有些使用者比較喜歡一開始使用 放在長篇影片結尾。這樣一來,畫面的刷新率就能一致 影片影格速率,避免使用影格速率轉換失真問題,例如 3:2 播放電影的下拉式選單。

因此,如果使用者和應用程式都選擇加入,即可啟用非無縫的螢幕更新率切換功能:

  • 使用者:如要啟用這項功能,使用者可以啟用「Match content frame rate」使用者設定。
  • 應用程式:如要選擇加入,應用程式可以將 CHANGE_FRAME_RATE_ALWAYS 傳遞至 setFrameRate()

建議您一律使用 CHANGE_FRAME_RATE_ALWAYS 播放長時間的影片,例如電影。這是因為比對、 影片影格速率高於 重新整理頻率。

其他建議

請按照下列建議處理常見情境。

多個表面

Android 平台可正確處理有多個介面且設定的幀率不同的情境。如果您的應用程式有多個 具有不同影格速率的介面,請用正確的值呼叫 setFrameRate() 計算每個表面的影格速率即使裝置同時執行多個應用程式,使用分割畫面或子母畫面模式,每個應用程式都能安全地為自己的途徑呼叫 setFrameRate()

平台未變更應用程式的影格速率

即使裝置支援應用程式在呼叫時指定的影格速率, setFrameRate(),在某些情況下,裝置就無法將螢幕切換為 重新整理頻率舉例來說,優先順序較高的途徑可能會有不同的 畫面更新率設定,或是裝置可能處於省電模式 (設定 限制螢幕刷新率以節省電力)。即使裝置在正常情況下會切換顯示器更新率,應用程式仍必須在裝置未將顯示器更新率切換為應用程式的影格速率設定時正常運作。

應用程式螢幕刷新率如何做出回應,是由應用程式決定 就不符合應用程式畫面更新率對於影片,影格速率會固定為來源影片的速率,且必須下拉才能顯示影片內容。A 罩杯 遊戲可能會改為嘗試以螢幕刷新率執行 維持遊戲所需的畫面更新率應用程式不應根據平台的運作方式,變更傳遞至 setFrameRate() 的值。保持設定 符合應用程式偏好的影格速率,無論應用程式如何處理 平台未配合應用程式的要求進行調整這麼一來 條件有所變化,允許使用額外的螢幕刷新率, 平台包含切換至應用程式偏好影格的正確資訊 頻率。

如果應用程式無法或無法以螢幕刷新率執行,則應用程式會執行 應使用 平台設定呈現時間戳記的機制:

使用這些時間戳記也能讓平台一併停止顯示應用程式影格 否則將導致不必要的判決。正確使用影格呈現時間戳記有點棘手。如要進一步瞭解如何避免卡頓,請參閱我們的影格間隔指南,並考慮使用 Android Frame Pacing 程式庫

在某些情況下,平台可能會切換為應用程式在 setFrameRate() 中指定的影格速率的倍數。舉例來說,應用程式可能會呼叫 setFrameRate() 在 60Hz 的時間內,裝置可以把螢幕切換到 120 Hz。造成這種情況的可能原因 假如其他應用程式的介面採用 24 Hz 的畫面更新率,就會發生這類情況。於 以 120 Hz 執行螢幕時,將可同時支援 60Hz 途徑和 執行 24Hz 的表面,不需下拉。

如果螢幕以應用程式影格速率的倍數執行,應用程式 應為每個影格指定顯示時間戳記,避免不必要的時間 也非常有幫助如果是遊戲,使用 Android Frame Pacing 程式庫時,可以 設定影格顯示時間戳記

setFrameRate() 與 PreferredDisplayModeId 的差異

WindowManager.LayoutParams.preferredDisplayModeId 是應用程式向平台指出影格速率的另一種方式。有些應用程式只想變更螢幕更新率,而非變更其他顯示模式設定,例如螢幕解析度。一般來說,請使用 setFrameRate() 而非 preferredDisplayModeIdsetFrameRate() 功能更加好用,因為應用程式不需要在 顯示模式清單,找出採用特定影格速率的模式。

在有多個途徑以不同影格速率執行的情況下,setFrameRate() 可讓平台有更多機會選擇相容的影格速率。舉例來說,假設有兩個應用程式在 Pixel 4 上以分割畫面模式執行,其中一個應用程式播放 24Hz 影片,另一個應用程式則向使用者顯示可捲動的清單。Pixel 4 支援兩種螢幕刷新率:60 Hz 和 90 Hz。使用 preferredDisplayModeId API 時,影片介面會強制選取 60Hz 或 90Hz。透過以 24 Hz 呼叫 setFrameRate(),影片介面會向平台提供更多來源影片影格速率的資訊,讓平台選擇 90 Hz 做為顯示刷新率,這在這種情況下比 60 Hz 更為理想。

不過,在某些情況下,應使用 preferredDisplayModeId 而非 setFrameRate(),例如:

  • 如果應用程式想要變更解析度或其他顯示模式設定,請使用 preferredDisplayModeId
  • 平台只會在回應呼叫 如果模式切換輕量且不太可能,則為 setFrameRate() 第一,更容易注意到如果應用程式偏好切換螢幕重新整理功能 即使需要使用重型模式開關 (例如在 Android TV 上) 也一樣 裝置),請使用 preferredDisplayModeId
  • 如果應用程式無法處理以應用程式影格速率的倍數執行的顯示作業,就必須在每個影格上設定呈現時間戳記,因此應使用 preferredDisplayModeId

setFrameRate() 與 PreferredRefreshRate 的比較

WindowManager.LayoutParams#preferredRefreshRate 為應用程式視窗設定偏好的畫面更新率 修正視窗裡的所有介面應用程式應指定偏好的設定 不考慮裝置支援的刷新率 (與 setFrameRate(),為排程器提供更明確的應用程式預期提示 畫面更新率

對於使用 setFrameRate() 的介面,系統會忽略 preferredRefreshRate。於 請盡量使用 setFrameRate()

preferredRefreshRate 與 preferredDisplayModeId

如果應用程式只想變更偏好的刷新率,則建議使用 preferredRefreshRate,而不是 preferredDisplayModeId

避免太常呼叫 setFrameRate()

雖然 setFrameRate() 呼叫在效能方面並不會造成太大負擔,但應用程式應避免在每個影格或每秒多次呼叫 setFrameRate()。呼叫 setFrameRate() 可能會導致 螢幕刷新率,可能導致影格在轉場期間遭到捨棄。 您應事先確定正確的影格速率,並呼叫 setFrameRate()一次。

用於遊戲或其他非影片應用程式

雖然影片是 setFrameRate() API 的主要用途,但也可以用於其他應用程式。舉例來說,如果遊戲不打算以超過 60Hz 的頻率執行 (以減少電力消耗並延長遊戲時段),則可以呼叫 Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)。在本 因此,預設以 90Hz 執行的裝置將改以 60Hz 執行, 遊戲進行,即可避免在 遊戲以 60Hz 的速度執行,而螢幕以 90Hz 的速度執行。

使用 FRAME_RATE_COMPATIBILITY_FIXED_SOURCE 的情形

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE 僅適用於影片應用程式。適用對象 非影片使用,請使用 FRAME_RATE_COMPATIBILITY_DEFAULT

選擇可變更影格速率的策略

  • 強烈建議應用程式在顯示長時間播放的影片 (例如電影) 時,呼叫 setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS),其中 fps 是影片的幀率。
  • 如果您預期影片播放時間為數分鐘或更短,強烈建議應用程式不要使用 CHANGE_FRAME_RATE_ALWAYS 呼叫 setFrameRate()

影片播放應用程式的整合範例

如要在影片播放應用程式中整合螢幕更新率切換器,建議您採取下列步驟:

  1. 決定 changeFrameRateStrategy
    1. 如果播放長時間的影片 (例如電影),請使用 MATCH_CONTENT_FRAMERATE_ALWAYS
    2. 如要播放短片 (例如電影預告片),請使用 CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. 如果 changeFrameRateStrategyCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS ,前往步驟 4。
  3. 檢查是否即將進行不流暢刷新率切換,方法是檢查 以下兩項敘述都成立:
    1. 流暢模式切換功能無法在目前的刷新率下達 將其命名為 C) 與影片的畫面更新率如果 C 和 V 不同,且 Display.getMode().getAlternativeRefreshRates 不包含 V 的倍數,就會發生這種情況。
    2. 使用者已選擇啟用非無縫的螢幕更新率變更。您可以偵測 方法是檢查 DisplayManager.getMatchContentFrameRateUserPreference 傳回 MATCH_CONTENT_FRAMERATE_ALWAYS
  4. 如果要順利完成切換,請執行以下操作:
    1. 呼叫 setFrameRate,並傳入 fpsFRAME_RATE_COMPATIBILITY_FIXED_SOURCEchangeFrameRateStrategy,其中 fps 是影片的畫面更新率。
    2. 開始播放影片
  5. 如果即將進行非無縫模式變更,請執行下列操作:
    1. 顯示使用者體驗,通知使用者。請注意,我們建議您在步驟 5.d 中實作一種方法,讓使用者關閉此使用者體驗,並略過額外的延遲時間。這是 我們的建議延遲超過了 能縮短切換時間
    2. 呼叫 setFrameRate 並向其傳送 fpsFRAME_RATE_COMPATIBILITY_FIXED_SOURCECHANGE_FRAME_RATE_ALWAYS, 其中 fps 是影片的畫面更新率。
    3. 請等待 onDisplayChanged 回呼。
    4. 等待 2 秒鐘,讓模式切換完成。
    5. 開始播放影片

支援無縫切換的虛擬程式碼如下:

SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setFrameRate(surfaceControl,
    contentFrameRate,
    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
    CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();

如上述所述,支援無縫和非無縫切換的虛擬程式碼如下:

SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
if (isSeamlessSwitch(contentFrameRate)) {
  transaction.setFrameRate(surfaceControl,
      contentFrameRate,
      FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
      CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
  transaction.apply();
  beginPlayback();
} else if (displayManager.getMatchContentFrameRateUserPreference()
      == MATCH_CONTENT_FRAMERATE_ALWAYS) {
  showRefreshRateSwitchUI();
  sleep(shortDelaySoUserSeesUi);
  displayManager.registerDisplayListener();
  transaction.setFrameRate(surfaceControl,
      contentFrameRate,
      FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
      CHANGE_FRAME_RATE_ALWAYS);
  transaction.apply();
  waitForOnDisplayChanged();
  sleep(twoSeconds);
  hideRefreshRateSwitchUI();
  beginPlayback();
}