轉自:https://source.android.com/devices/graphics/?hl=zh-cn
SurfaceView 和 GLSurfaceView
Android 應用框架界面是以使用 View 開頭的對象層次結構為基礎。所有界面元素都會經過一個復雜的測量和布局過程,該過程會將這些元素融入到矩形區域中,并且所有可見 View 對象都會渲染到一個由 SurfaceFlinger 創建的 Surface(在應用置于前臺時,由 WindowManager 進行設置)。應用的界面線程會執行布局并渲染到單個緩沖區(不考慮 Layout 和 View 的數量以及 View 是否已經過硬件加速)。
SurfaceView 采用與其他視圖相同的參數,因此您可以為 SurfaceView 設置位置和大小,并在其周圍填充其他元素。但是,當需要渲染時,內容會變得完全透明;SurfaceView 的 View 部分只是一個透明的占位符。
當 SurfaceView 的 View 組件即將變得可見時,框架會要求 WindowManager 命令 SurfaceFlinger 創建一個新的 Surface。(這個過程并非同步發生,因此您應該提供回調,以便在 Surface 創建完畢后收到通知。)默認情況下,新的 Surface 將放置在應用界面 Surface 的后面,但可以替換默認的 Z 排序,將 Surface 放在頂層。
渲染到該 Surface 上的內容將會由 SurfaceFlinger(而非應用)進行合成。這是 SurfaceView 的真正強大之處:您獲得的 Surface 可以由單獨的線程或單獨的進程進行渲染,并與應用界面執行的任何渲染隔離開,而緩沖區可直接轉至 SurfaceFlinger。您不能完全忽略界面線程,因為您仍然需要與 Activity 生命周期相協調,并且如果 View 的大小或位置發生變化,您可能需要調整某些內容,但是您可以擁有整個 Surface。與應用界面和其他圖層的混合由 Hardware Composer 處理。
新的 Surface 是 BufferQueue 的生產者端,其消費者是 SurfaceFlinger 層。您可以使用任意提供 BufferQueue 的機制(例如,提供 Surface 的 Canvas 函數)來更新 Surface,附加 EGLSurface 并使用 GLES 進行繪制,或者配置 MediaCodec 視頻解碼器以便于寫入。
合成與硬件縮放
我們來仔細研究一下dumpsys SurfaceFlinger。當在 Nexus 5 上,以縱向方向在 Grafika 的“播放視頻 (SurfaceView)”活動中播放電影時,采用以下輸出;視頻是 QVGA (320x240):
type? ? |? ? ? ? ? source crop? ? ? ? ? ? ? |? ? ? ? ? frame? ? ? ? ? name------------+-----------------------------------+--------------------------------? ? ? ? HWC | [? ? 0.0,? ? 0.0,? 320.0,? 240.0] | [? 48,? 411, 1032, 1149] SurfaceView? ? ? ? HWC | [? ? 0.0,? 75.0, 1080.0, 1776.0] | [? ? 0,? 75, 1080, 1776] com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity? ? ? ? HWC | [? ? 0.0,? ? 0.0, 1080.0,? 75.0] | [? ? 0,? ? 0, 1080,? 75] StatusBar? ? ? ? HWC | [? ? 0.0,? ? 0.0, 1080.0,? 144.0] | [? ? 0, 1776, 1080, 1920] NavigationBar? FB TARGET | [? ? 0.0,? ? 0.0, 1080.0, 1920.0] | [? ? 0,? ? 0, 1080, 1920] HWC_FRAMEBUFFER_TARGET
列表順序是從后到前:SurfaceView 的 Surface 位于后面,應用界面層位于其上,其次是處于最前方的狀態欄和導航欄。
源剪裁值表示 Surface 緩沖區中 SurfaceFlinger 將顯示的部分。應用界面會獲得一個與顯示屏的完整尺寸 (1080x1920) 一樣大的 Surface,但是由于渲染和合成將被狀態欄和導航欄遮擋的像素毫無意義,因此將源剪裁為一個矩形(上自離頂部 75 個像素,下至離底部 144 個像素)。狀態欄和導航欄的 Surface 較小,并且源剪裁描述了一個矩形(起點位于左上角 (0,0) 并且會橫跨其內容)。
框架值指定在顯示屏上顯示像素的矩形。對于應用界面層,框架會與源剪裁匹配,因為我們會將與顯示屏同樣大小的圖層的一部分復制(或疊加)到另一個與顯示屏同樣大小的圖層中的相同位置。對于狀態欄和導航欄,兩者的框架矩形大小相同,但是位置經過調整,所以導航欄出現在屏幕底部。
SurfaceView 層容納我們的視頻內容。源剪裁與視頻的大小相匹配,而 SurfaceFlinger 了解該信息,因為 MediaCodec 解碼器(緩沖區生成器)正在將同樣大小的緩沖區移出隊列??蚣芫匦尉哂型耆煌某叽纾?84x738。
SurfaceFlinger 通過縮放(根據需要放大或縮?。┚彌_區內容來填充框架矩形,以處理大小差異。之所以選擇這種特定尺寸,是因為它具有與視頻相同的寬高比 (4:3),并且由于 View 布局的限制(為了美觀,在屏幕邊緣處留有一定的內邊距),因此應盡可能地寬。
如果您在同一 Surface 上開始播放不同的視頻,底層 BufferQueue 會將緩沖區自動重新分配為新的大小,而 SurfaceFlinger 將調整源剪裁。如果新視頻的寬高比不同,則應用需要強制重新布局 View 才能與之匹配,這將導致 WindowManager 通知 SurfaceFlinger 更新框架矩形。
如果您通過其他方式(如 GLES)在 Surface 上進行渲染,則可以使用SurfaceHolder#setFixedSize()調用設置 Surface 尺寸。例如,您可以將游戲配置為始終采用 1280x720 的分辨率進行渲染,這將大大減少填充 2560x1440 平板電腦或 4K 電視機屏幕所需處理的像素數。顯示處理器會處理縮放。如果您不希望給游戲加上水平或垂直黑邊,您可以通過設置尺寸來調整游戲的寬高比,使窄尺寸為 720 像素,但長尺寸設置為維持物理顯示屏的寬高比(例如,設置為 1152x720 來匹配 2560x1600 的顯示屏)。有關此方法的示例,請參閱 Grafika 的“硬件縮放練習程序”活動。
GLSurfaceView
GLSurfaceView 類提供幫助程序類,用于管理 EGL 上下文、線程間通信以及與 Activity 生命周期的交互。這就是其功能。您無需使用 GLSurfaceView 來應用 GLES。
例如,GLSurfaceView 創建一個渲染線程,并配置 EGL 上下文。當活動暫停時,狀態將自動清除。大多數應用都不需要知道 EGL,便可通過 GESurfaceView 使用 GLES。
在大多數情況下,GLSurfaceView 非常實用,可簡化 GLES 的使用。但在某些情況下,卻會造成妨礙。請在有用時使用,無用時棄用。
SurfaceView 和 Activity 生命周期
當使用 SurfaceView 時,使用主界面線程之外的線程渲染 Surface 是很好的做法。不過,這樣就會產生一些與線程和 Activity 生命周期之間的交互相關的問題。
對于具有 SurfaceView 的 Activity,存在兩個單獨但相互依賴的狀態機:
狀態為 onCreate/onResume/onPause 的應用
已創建/更改/銷毀的 Surface
當 Activity 開始時,將按以下順序獲得回調:
onCreate
onResume
surfaceCreated
surfaceChanged
如果回擊,您將得到:
onPause
surfaceDestroyed(在 Surface 消失前調用)
如果旋轉屏幕,Activity 將被消解并重新創建,而您將獲得整個周期。您可以通過檢查isFinishing()告知屏幕快速重新啟動。啟動/停止 Activity 可能非??焖?,從而可能導致surfaceCreated()實際上是在onPause()之后發生。
如果您點按電源按鈕鎖定屏幕,則只會得到onPause()(沒有surfaceDestroyed())。Surface 仍處于活躍狀態,并且渲染可以繼續。如果您繼續請求,甚至可以持續獲得 Choreographer 事件。如果您使用強制變向的鎖屏,則當設備未鎖定時,您的 Activity 可能會重新啟動;但如果沒有,您可以使用與之前相同的 Surface 脫離屏幕鎖定。
當使用具有 SurfaceView 的單獨渲染器線程時,會引發一個基本問題:線程壽命是否依賴 Surface 或 Activity 的壽命?答案取決于鎖屏時您想要看到的情況:(1) 在 Activity 啟動/停止時啟動/停止線程,或 (2) 在 Surface 創建/銷毀時啟動/停止線程。
選項 1 與應用生命周期交互良好。我們在onResume()中啟動渲染器線程,并在onPause()中將其停止。當創建和配置線程時,會顯得有點奇怪,因為有時 Surface 已經存在,有時不存在(例如,在使用電源按鈕切換屏幕后,它仍然存在)。我們必須先等待 Surface 完成創建,然后再在線程中進行一些初始化操作,但是我們不能簡單地在surfaceCreated()回調中進行操作,因為如果未重新創建 Surface,將不會再次觸發。因此,我們需要查詢或緩存 Surface 狀態,并將其轉發到渲染器線程。
注意:在線程之間傳遞對象時要小心。最好通過處理程序消息傳遞 Surface 或 SurfaceHolder(而不僅僅是將其填充到線程中),以避免多核系統出現問題。有關詳細信息,請參閱Android SMP Primer。
選項 2 非常具有吸引力,因為 Surface 和渲染器在邏輯上互相交織。我們在創建 Surface 后啟動線程,避免了一些線程間通信問題,也可輕松轉發 Surface 已創建/更改的消息。當屏幕鎖定時,我們需要確保渲染停止,并在未鎖定時恢復渲染;要實現這一點,可能只需告知 Choreographer 停止調用框架繪圖回調。當且僅當渲染器線程正在運行時,我們的onResume()才需要恢復回調。盡管如此,如果我們根據框架之間的已播放時長進行動畫繪制,我們可能發現,在下一個事件到來前存在很大的差距;應使用一個明確的暫停/恢復消息。
注意:有關選項 2 的示例,請參閱 Grafika 的“硬件縮放練習程序”。
這兩個選項主要關注如何配置渲染器線程以及線程是否正在執行。一個相關問題是,終止 Activity 時(在onPause()或onSaveInstanceState()中)從線程中提取狀態;在此情況下,選項 1 最有效,因為在渲染器線程加入后,不需要使用同步基元就可以訪問其狀態。
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 3.0 License, and code samples are licensed under theApache 2.0 License. For details, see ourSite Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 八月 24, 2017.