架構6--SurfaceView 和 GLSurfaceView

轉自: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.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容