日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

SurfaceView 和 GLSurfaceView

發布時間:2024/4/11 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SurfaceView 和 GLSurfaceView 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Android 應用框架 UI 是基于一個從 View 開始的對象層次體系的。所有的 UI 元素經歷一個復雜的測量和布局過程來將它們適配入一個矩形區域,所有可見的 View 對象被渲染進一個 SurfaceFlinger 創建的 Surface,而后者由 WindowManager 在應用程序回到前臺時建立。應用程序的 UI 線程執行布局并渲染進一個單獨的緩沖區(無論 Layouts 和 Views 的個數,也不管 View 是否是硬件加速的)。

SurfaceView 如同其它 views 一樣接收相同的參數,因此你可以給它一個位置和大小,然后圍繞它適配其它元素。當它要渲染時,然而,其內容完全是透明的;SurfaceView 的View 部分只是一個透視占位符。

當 SurfaceView 的 View 組件即將可見時,框架請求 WindowManager 請求 SurfaceFlinger 創建一個新的 Surface。(這不是同步發生的,這也是為什么你應該提供一個回調,在 Surface 創建完成時通知你。)默認情況下,新的 Surface 位于應用程序 UI Surface 的后面,但默認的 Z-ordering 可以被覆蓋來將 Surface 放到頂部。

無論你向 Surface 渲染了什么,它們都將由 SurfaceFlinger 合成,而不是由應用程序。這正是 SurfaceView 真正強大的地方:你獲得的 Surface 可以由單獨的線程或單獨的進程渲染,完全與由應用程序 UI 執行的任何渲染隔離,而緩沖區直接進入 SurfaceFlinger。你不能完全忽略 UI 線程 - 你依然不得不與 Activity 的生命周期合作,且如果 View 的大小或位置改變了你可能不得不調整一些東西 - 但你自己完全擁有整個 Surface。與應用程序 UI 和其它 layers 的混合由 Hardware Composer 處理。

新的 Surface 是 BufferQueue 的生產者端,其消費者是 SurfaceFlinger 的 layer。你可以以任何能夠喂養 BufferQueue 的機制更新 Surface,如 Canvas 函數提供的 surface,附上一個 EGLSurface 并用 GLES 繪制它,或配置一個 MediaCodec 視頻解碼器寫入它。

合成和硬件 Scaler

讓我們更近地看一下 dumpsys SurfaceFlinger。下面的輸出是在豎屏的 Nexus 設備上,在 Grafika 的 "Play video (SurfaceView)" activity 播放一個電影時獲取的;視頻是 QVGA (320x240) 的:

type | source crop | frame name ------------+-----------------------------------+--------------------------------HWC | [ 0.0, 0.0, 320.0, 240.0] | [ 48, 411, 1032, 1149] SurfaceViewHWC | [ 0.0, 75.0, 1080.0, 1776.0] | [ 0, 75, 1080, 1776] com.android.grafika/com.android.grafika.PlayMovieSurfaceActivityHWC | [ 0.0, 0.0, 1080.0, 75.0] | [ 0, 0, 1080, 75] StatusBarHWC | [ 0.0, 0.0, 1080.0, 144.0] | [ 0, 1776, 1080, 1920] NavigationBarFB TARGET | [ 0.0, 0.0, 1080.0, 1920.0] | [ 0, 0, 1080, 1920] HWC_FRAMEBUFFER_TARGET
  • 排列順序 是自后向前的:SurfaceView 的 Surface 在后邊,應用程序 UI layer 位于它的頂部,然后是狀態欄和導航欄在所有其它東西上面。

  • source crop 值表示 SurfaceFlinger 將顯示的 Surface 的緩沖區的部分。應用程序UI 被賦予了一個等于顯示器全尺寸(1080x1920)的 Surface,但是由于被狀態欄和導航欄遮擋的像素不會被渲染和合成,所以源被裁剪為一個矩形,從距頂部 75 像素的開始并在距底部144個像素的地方結束。狀態欄和導航欄具有更小的 Surface,且 source crop 描述了一個矩形,從左上角的 (0,0) 開始并跨越它們的內容。

  • frame 值描述了像素在顯示器上顯示的矩形。對于應用程序 UI layer,frame 與 source crop 匹配,因為我們正在將顯示大小的 layer 的一部分復制(或疊加)到另一個顯示大小的 layer 中的相同位置。對于狀態欄和導航欄,幀矩形的大小是相同的,但位置要調整以使導航欄出現在屏幕的底部。

  • SurfaceView layer 持有我們的視頻內容。source crop 與視頻大小匹配,SurfaceFlinger 知道該大小,因為 MediaCodec 解碼器(緩沖區的生產者)在獲取該大小的緩沖區。幀矩形具有完全不同的大小 —— 984x738。

SurfaceFlinger 通過放縮緩沖區的內容來處理大小的差異以適配幀矩形,放大還是縮小則根據需要。之所以選擇這個特定的大小是由于它與視頻有著相同的長寬比 (4:3),且在滿足 View 布局約束的條件下盡可能的寬(這包括為了美學原因而在屏幕邊緣包含的一些填充)。

如果你在相同的 Surface 上起動播放了不同的視頻,則底層的 BufferQueue 將自動地重新分配緩沖區為新的大小,SurfaceFlinger 將調整 source crop。如果新視頻的長寬比不同,應用將需要強制 re-layout 該 View 來與之匹配,這將導致 WindowManager 告知 SurfaceFlinger 去更新幀矩形。

如果你正在通過一些其它的方式(比如 GELS)在 Surface 上渲染,你可以使用 SurfaceHolder#setFixedSize() 調用設置 Surface 的大小。比如,你可以配置一個游戲總是以 1280x720 渲染,這將顯著地減少填充 2560x1440 的平板或 4K 電視的屏幕必須處理的像素數。顯示處理器處理放縮。如果你不想使你的游戲在固定大小的渲染,你可以通過設置大小來調整游戲的長寬比比,使窄尺寸為 720 像素,但長尺寸設置為保持物理顯示器的長寬比 (比如,1152x720 匹配一個 2560x1600 的顯示器)。使用這種方法的一個例子,請參考 Grafika 的 "Hardware scaler exerciser" activity。

GLSurfaceView

GLSurfaceView 類提供了一些輔助類來管理 EGL contexts,線程間通信,及與 Activity 生命周期的交互。僅此而已。你不需要通過使用 GLSurfaceView 來使用 GLES。

比如,GLSurfaceView 創建一個線程來渲染并在那兒配置一個 EGL context。當 Activity pause 時,狀態被自動地清除。通過 GLSurfaceView,大多數應用在使用 GLES 時將無需知道任何與 EGL 有關的東西。

大多數情況下,GLSurfaceView 是非常有幫助的,且可以簡化對 GLES 的使用。在某些情況下,它會阻礙你的發展。如果它有幫助就使用它,否則就不用。

SurfaceView 和 Activity 生命周期

當使用 SurfaceView 時,在一個主 UI 線程之外的線程中渲染 Surface 被認為是一個良好的實踐。這產生了一些關于那個線程和 Activity 生命周期交互的問題。

對于一個使用了 SurfaceView 的 Activity,有兩個分開但相互依賴的狀態機:

  • 應用程序的 onCreate/onResume/onPause

  • Surface 的 created/changed/destroyed

  • 當 Activity 啟動時,你以這樣的順序得到回調:

    • onCreate

    • onResume

    • surfaceCreated

    • surfaceChanged

    如果你按了返回鍵你得到:

    • onPause

    • surfaceDestroyed (在 Surface 消失之前被調用)

    如果你旋轉屏幕,Activity 被銷毀并創建,并將經歷一個完整的周期。你可以通過檢查 isFinishing() 來快速重新啟動。有可能 start/stop Activity 太快,以至于 onPause() 之后 surfaceCreated() 實際可能還沒有發生。

    如果你按下電源鍵來鎖住屏幕,只有 onPause() 被調用 - 沒有 surfaceDestroyed()。Surface 保持活躍,渲染可以繼續進行。如果你請求它們的話你甚至可以繼續獲得 Choreographer 事件。如果你的鎖屏強制強制不同的方向,則當設備解鎖時,你的
    Activity 可能會重新啟動;如果不是,你可以使用與之前的
    Surface 相同的屏幕區域。

    當使用一個單獨的渲染線程來使用 SurfaceView 時,這引發了一個基本的問題:那個線程的生命周期應該與 Surface 還是 Activity 掛鉤?答案依賴于當屏幕空白時你想要讓什么發生:(1). 當 Activity start/stop 時 start/stop 該線程,或者當 Surface create/destroy 時 start/stop 該線程。

    選項 1 與應用的生命周期交互良好。我們在 onResume() 中啟動渲染線程并在 onPause() 停止它。在創建和配置線程時,它有點尷尬,因為有時 Surface 將已經存在,有時還沒有(比如在通過電源鍵關閉屏幕時它依然存活)。我們在該線程中執行一些初始化之前不得不等待 surface 創建,但我們不能簡單地在 surfaceCreated() 回調中完成它,因為如果 Surface 沒有被重建的話那將不會再次被調用。因此我們需要查詢或者緩存 Surface 狀態,并把它轉發給渲染線程。

    注意: 在線程之間傳遞對象時要小心。最好通過一個 Handler 消息來傳遞 Surface 或 SurfaceHolder(而不是僅僅把它填入線程)以避免多核系統的問題。更多細節,請參考 Android SMP Primer。

    選項 2 之所以吸引人,是因為 Surface 和渲染器在邏輯上是交織在一起的。我們在 Surface 創建之后啟動線程,這將避免一些線程間通信問題,Surface created/changed 消息僅僅被簡單的轉發。我們需要確保在屏幕空白時渲染停止并在屏幕恢復時恢復;告訴 Choreographer 停止調用幀繪制調用可能是一個額簡單的問題。當且僅當渲染器線程運行時,我們的 onResume() 將需要恢復該調用。事情可能沒有那么簡單 - 如果我們根據幀之間的時間來做動畫,當下一個事件到達時我們可能有一個巨大的間隙;顯式的暫停/恢復消息可能是可取的。

    注意: 選項 2 的一個例子可以參考 Grafika 的 "Hardware scaler exerciser"。

    兩種選項主要都被渲染器線程如何配置,以及是否執行所困繞。一個相關的問題是在 Activity 被殺掉時 (在onPause() 或 onSaveInstanceState() 中) 從渲染線程提取狀態;在這種情況下,選項 1 最好,因為在渲染器線程已經 joined 之后它的狀態無需同步原語就可以訪問。

    原文

    總結

    以上是生活随笔為你收集整理的SurfaceView 和 GLSurfaceView的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。