本文首發於微信公衆號——世界上有意思的事,搬運轉載請註明出處,不然將追究版權責任。微信號:a1018998632,交流qq羣:859640274 java
你們好,新的一年又正式開始了,筆者在這裏給你們拜個晚年。最近寫的文章須要涉及到的知識不少,因此更新比較慢,望你們海涵。最近申請了一個微信公衆號:世界上有意思的事 ,二維碼在文章底部,之後個人文章首發都會在這個公衆號上面。簡書、掘金會在幾個小時以後同步,因此但願你們能來關注個人公衆號,大量乾貨在這裏等着你。進入微信公衆號 世界上有意思的事 發送消息:**Android繪製機制以及Surface家族源碼全解析,**便可獲取本文的 pdf 版。android
本篇文章分爲如下章節,讀者能夠按需閱讀 ios
1.Android繪製機制概覽
2.Android繪製機制源碼分析
3.Surface家族源碼全解析
4.總結
閱讀須知 c++
1.進入微信公衆號 世界上有意思的事 發送消息:**Android繪製機制以及Surface家族源碼全解析,**便可獲取本文的 pdf 版。
2.本文分析的源碼版本是 Android 7.0 ,建議結合源碼閱讀本文
3.推薦一個 Android 源碼閱讀網站:Android 源碼閱讀網站
4.由於不少 java 層和 c++ 層的類命名都相同,因此後續例如:Surface.lockCanvas 表示 java 層的調用,Surface::lockCanvas 表示 c++ 層的調用
5.本文的一些縮寫:SF——>SurfaceFlinger、WMS——>WindowManagerService、MessageQueue——>MQ、GL——>OpenGL、BQ——>BufferQueue、ST——>SurfaceTexture、TV——>TextureView、SV——>SurfceView
6.本文是視頻編輯SDK開發的重要前置知識,建議讀透
1、Android繪製機制概覽
在深刻各類源碼以前,咱們須要瞭解 Android 繪製機制的總體架構,以及一些概念git
1.Android屏幕刷新機制
圖1就是 Android 屏幕顯示的抽象示意圖,這裏我來解釋一下: 程序員
1.首先圖的橫軸是時間,縱軸從下到上分別表示:CPU 處理、GPU 處理、屏幕顯示,這三個步驟也就是咱們寫的代碼到圖像顯示在屏幕上的流程。
2.咱們都知道要讓手機不卡頓一個顯而易見的標準就是:**屏幕上每隔必定的 ms 就能顯示下一幀圖像。**在這裏這個時間由底層控制,也就是圖中兩個 VSync 的間隔時間——16ms 。
3.Android 中引入了下面這些特性來保證屏幕上的數據每隔 16ms 來刷新一次。
1.一個固定的脈衝信號——VSync ,這個東西由底層保證,每一次 VSync 信號來了 CPU 就開始運行繪製代碼(例如運行 View.draw 之類的方法 ),當 CPU 的數據準備好了,就將這些數據交給 GPU 讓其在一塊內存緩衝區上進行圖像的繪製。當 GPU 繪製好了就將圖像顯示到屏幕上。
2.三緩衝 ,圖中的 A、B、C 表示的是三塊內存緩衝區。由於不是每次 CPU、GPU 的數據處理都能在16ms 內完成的,因此爲了避免浪費時間並且不丟棄以前作完的工做。CPU、GPU 的工做會依次反應在 A、B、C 三塊內存緩衝區中。而屏幕每次都取當前已經準備好的內存緩衝區。三緩衝較雙緩衝的問題就是:咱們的操做最終顯示到屏幕上的時候會延遲16ms,這可能也是 Android 不如 ios 」跟手「的一個緣由
4.由咱們能夠得出兩個簡單的結論:
1.ui線程太忙了,使得 CPU 16ms 內沒有處理好數據會致使丟幀。
2.須要繪製的圖像太複雜,致使 GPU 16ms 沒有繪製好圖像也會致使丟幀。
2.Android圖像繪製方式
問你們一個問題:平時咱們開發過程當中能夠用哪些工具在屏幕上繪製圖像?你們必定能夠回答出不少東西:View、Drawable、xml、SV、GLSurfaceView、TV、Canvas等等。其實 Android 上面一共只有兩種經常使用的繪圖機制,上面列舉出來的東西都是由這兩種機制演變而來的。這一節我就簡單概括介紹一下。github
Android 的兩種經常使用繪圖機制: 編程
1.Skia圖形庫: Skia官網 ,Skia是一個開源的二維圖形庫,提供各類經常使用的API,並可在多種軟硬件平臺上運行。在沒開啓硬件加速 的時候用到 Canvas 的地方在底層都會調用到 Skia 庫中去。在上面咱們列舉的方式裏面:View、Drawable、xml、SV、Canvas 最終使用的都是 Skia 庫。另外 Skia 最終是使用 CPU 來對圖像進行最終繪製,因此效率比較低。
2.GL:GL 是用於渲染 2D、3D 矢量圖形的跨語言、跨平臺的應用程序編程接口。在 開啓硬件加速 的時候用到 Canvas 的地方最終會調用到 GL ES 庫中。沒開啓硬件加速的時候上面咱們列舉的方式中:GLSurfaceView、TV 最終使用的是 GL ES ,另外 GL 使用的是 GPU 來對圖像進行繪製,因此效率比較高。
3.在後續的章節裏我會從源代碼上分析上面各類繪圖方式的調用鏈。
4.其實在 7.0 以後 Android 中添加了 Vulkan。Vulkan 是用於高性能 3D 圖形的低開銷、跨平臺 API。與 GL ES 同樣,Vulkan 提供多種用於在應用中建立高質量的實時圖形的工具。不過目前不多用到,因此本篇文章中咱們不討論它。
3.Android繪製中的生產者和消費者
android 的繪製機制中存在着一系列生產者 和消費者 ,這一節我將介紹一下這個機制中相關的概念。後端
1.BQ:如圖2所示,BQ 是一個存儲着內存塊 的隊列。
1.生產者 :它可使用兩個 api,queue 和 dequeue 。
1.**dequeue:**須要一塊內存來繪製圖像的時候,能夠從隊列的尾部拿出一塊內存進行繪製。
2.**queue:**當圖像繪製完畢的時候,能夠將該內存添加到隊列的頭部
2.**消費者:**它也可使用兩個 api,acquire 和 release 。
1.**acquire:**當須要一塊已經繪製完成的內存再對其進行處理的時候,能夠從隊列的頭部拿出一塊內存。
2.**release:**當對圖像內存處理完畢的時候,能夠將內存重置而後放回隊列的尾部
2.圖像內存的生產者:
1.Surface :Surface 是 BQ 的生產者。當咱們使用 lockCanvas 和 unlockCanvasAndPost 的時候,就是先從 BQ 中取出一塊內存而後調用 Canvas/GL 的 api 對內存進行繪製,最後將內存放回 BQ 中。
2.咱們知道了 Surface 是生產者,那麼像 View、SV、GLSurfaceView 這些間接或者直接用到了 Surface 的東西就都是生產者了。
3.圖像內存的消費者:
1.SF :它有本身的進程 ,在 Android 系統啓動的時候就像其餘各類 Service 同樣被建立了。它的做用是接受來自多個來源的內存緩衝區,對它們進行合成,而後發送到顯示設備。大多數應用一般在屏幕上有三個層:屏幕頂部的狀態欄、底部或側面的導航欄以及應用的界面。因此應用的 Surface 生產的內存就會被它所消耗。
2.**ST:**這個東西是經常用在 TV 中。它能夠在咱們的應用中使用。它在建立的時候會創建一個本身的 BQ。咱們能夠經過 ST 來建立一個 Surface 而後經過 Surface 向 BQ 中提供圖像內存。此時 ST 就能夠消耗這些圖像內存。它可使用 GL 來對這些被消耗的圖像內存進行二次處理,而後讓這些被處理以後的圖像內存在經過 GLSurfaceView 之類的東西顯示到屏幕上。
2、Android繪製機制源碼分析
這一章咱們來從源碼上分析 View 是如何繪製到屏幕上面的,前面的 measure、layout、draw 等等 framework 層的東西我不會着重分析,我主要分析 cpp 層的東西。api
其實源碼的主要流程都在圖3中,我下面講的東西算是對圖3的補充和說明。另外強烈建議結合 Android 源碼閱讀本章節。
1.首先咱們的入口是 ViewRootImpl.scheduleTraversals。看過源碼的同窗應該知道,相似 invalidate、requestLayout 等等須要改變 View 的顯示的操做,最終都會層層向上調用最終調用到這個方法上。
1.在這個方法裏主要調用到的方法就是 Choreographer.postCallback 這個方法傳入了 mTraversalRunnable,表示在某個時間點會調用這個 Runnable 中的 run()。咱們在第一章中講過 VSync 的相關知識,而 Choreographer 就是 VSync 在代碼層面的實現。
1.postCallback 根據 scheduleFrameLocked——>scheduleVsyncLocked——>scheduleVsyncLocked 的調用鏈,最終會調用到 DisplayEventReceiver.nativeScheduleVsync 向native 層申請下一個 VSync 信號。
2.16ms 後 VSync 信號會從 native 層向上觸發,這裏是想 ui 的 loop 中添加了一個 msg。這樣的話調用鏈就是:DisplayEventReceiver.dispatchVsync——>DisplayEventReceiver.onVsync——>Choreographer.doFrame。
1.到了 doFrame 這裏就會調用前面 postCallback 中傳入的 mTraversalRunnable 的 run()。咱們知道 run() 中會調用 ViewRootImpl.doTraversal 方法。
1.這樣就會調用到 ViewRootImpl.performTraversals 中去。我想你們應該對這個方法很熟悉,這個方法就是調用 measure、layout、draw 的方法。已經分析爛了的東西這裏我就不說了。
2.咱們直接看 ViewRootImpl.draw 方法,這裏會有兩種繪製方式。就像咱們在第一章中說的那樣。若是沒有開啓硬件加速 那麼就使用 Skia 庫以 CPU 來繪製圖像,這裏的入口方法是 drawSoftware。若是開啓了硬件加速 那麼就是用 GL ES 來以 GPU 繪製圖像,這裏入口方法是 ThreadedRenderer.draw。
1.drawSoftware:這個方法比較簡單就是建立一個 Surface,而後 lockCanvas 獲得一個 Canvas,而後在各個層級的 View 中調用 Canvas 最終調用 Skia 庫來在 Surface 上提供的圖像內存中繪製圖像,畫完以後調用 unlockCanvasAndPost 來提交圖像內存。這裏更詳細的流程我會在第三章中分析 Surface 的時候分析。
2.ThreadedRenderer.draw:這個方法是硬件加速下的圖像繪製入口,裏面最終都是調用到 GL 的 api。在深刻以前咱們先了解一下硬件加速繪製的幾個階段:
1.硬件加速繪製的五個階段:
1.APP在UI線程使用 Canvas 遞歸構建 GL 渲染須要的命令及數據
2.CPU 將準備好的數據共享給 GPU
3.CPU 通知GPU渲染,這裏通常不會阻塞等待GPU渲染結束,由於效率很低。CPU 通知結束後就返回繼續執行其餘任務。固然使用 glFinish 能夠阻塞執行。
4.swapBuffers,並通知 SF 圖層合成
5.SF 開始合成圖層,若是以前提交的GPU渲染任務沒結束,則等待GPU渲染完成,再合成(Fence機制),合成依然是依賴GPU
2.硬件加速繪製代碼分析:
1.咱們先看 draw() 裏面調用的 updateRootDisplayList:
1.這個方法的第一個調用鏈是這樣的 updateViewTreeDisplayList——>View.updateDisplayListIfDirty——>View.draw。如圖4,這裏聰明的同窗一看就知道是一個遞歸操做。View 的 draw 會遞歸到子 View 中。而後各個 View 會調用 Canvas 的 api 將繪製操做儲存在 Canvas 中。那麼這裏的 Canvas 是怎麼來的呢?其實在每一個 View 建立的時候內部會建立一個 RenderNode 。這個對象能夠建立一個 DisplayListCanvas 來做爲 Canvas 給各個 View 在繪製的時候使用。而每一個子 View 調用 draw(Canvas, ViewGroup, long) 的時候都會獲得 parentView 傳遞下來的 DisplayListCanvas,而後在本 View.draw(Canvas) 調用結束以後,將DisplayListCanvas 的操做儲存到本 View 的 RenderNode 中。最後調用 parentView 的 DisplayListCanvas.drawRenderNode 將本 View 的 RenderNode 存入 parentView 的 RenderNode 中。如此遞歸,最終將全部繪製操做存入 RootView 的 RenderNode 中。至此 RootView 中就包含了一個 DrawOp 樹。
2.咱們回到 updateRootDisplayList 這裏後續就是將 RootView 的 DrawOp 樹 交給 ViewRootImpl 的 RenderNode 方便後面進行操做。
2.再回到 draw() 中,這裏下一個調用的重要的方法是 nSyncAndDrawFrame:
1.這個方法最終會調用到 c++ 層的 RenderProxy::syncAndDrawFrame 方法。在瞭解這裏的調用鏈以前。我先介紹一下 Android 5.0 以後出現的渲染線程的概念,再來說解調用鏈。
1.首先渲染線程的實現類是 RenderThread.cpp 它是和 ui 線程相似,是一個事件驅動的 Loop。它的也有隊列,隊列中儲存着 DrawFrameTask.cpp 對象。RenderProxy.cpp 是 RenderThread.cpp 給 java 層的代理。ThreadRender 全部關於渲染線程的請求都會交給 RenderProxy.cpp 而後由它向 RenderThread.cpp 提交 task。
2.瞭解了渲染線程的概念,咱們再來說講調用鏈:syncAndDrawFrame——> DrawFrameTask::drawFrame——>DrawFrameTask::postAndWait。這裏作的事情很簡單,向 RenderTheas.cpp 的隊列中插入一個 DrawFrameTask.cpp,而後阻塞當前的 ui 線程。
3.ui 線程被阻塞以後,渲染線程會調用到上一次插入到隊列裏的 DrawFrameTask.run 方法。
1.run() 這裏會先調用 syncFrameState,這個方法主要是用於同步 java 層的各類數據。
1.第一步是先用 mLayers::apply 來同步數據,這個 mLayers 在 java 層的體現是 TV。這裏的分析咱們會在第三章中着重分析,這裏先略過。
2.第二步是調用 CanvasContext::prepareTree 來將前面在 java 層構建的 DrawOp 樹同步到 c++ 層,以便後續運行 OpengGL 的命令。這裏關鍵的調用鏈是:CanvasContext::prepareTree——>RenderNode::prepareTree——>RenderNode::prepareTreeImpl。由前面咱們能夠知道 RenderNode.java 已經構建了一個 DrawOp 樹。可是以前只是調用 RenderNode::setStagingDisplayList 暫存在 RenderNode::mStagingDisplayListData 中的。由於 java 層在運行過程當中還會出現屢次meausre、layout的,還有數據還可能發生改變。因此當走到這裏的時候數據已經肯定了,因此能夠開始同步數據。prepareTreeImpl 同步數據主要有三個步驟:
1.調用 pushStagingDisplayListChanges 同步當前 RenderNode.cpp 的屬性,也就是把 mStagingDisplayListData 賦值給 mDisplayListData
2.調用 prepareSubTree 遞歸處理子 RenderNode.cpp。
3.這裏會有一個同步成功和同步失敗的問題,通常來講這裏的數據都會同步成功的。可是在 RenderNode::prepareSubTree 中會有一個步驟是把 RenderNode 用到的 Bitmap 封裝成紋理,一旦這裏 Bitmap 太大或者數量太多那麼同步就會失敗。注意這裏同步失敗只是會與 Bitmap 有關,其餘的 DrawOp 數據不管如何都會同步成功的。
3.若是這裏同步成功了的話,那麼 ui thread 就會被喚醒,反之則暫時不喚醒。
2.run() 中將數據同步完成以後,就會調用 CanvasContext.draw,這個方法主要有三個操做:
1.mEglManager::beginFrame,實際上是標記當前上下文,而且申請繪製內存,由於一個進程中可能存在多個window,也就是多個 EglSurface,那麼咱們首先須要標記處理哪一個,而後向 SF 申請內存,這裏 EglSurface 是生產者,SF 是消費者。
2.根據前面的 RenderNode 的 DrawOp 樹,遞歸調用 OpenGLRender 中的 GL API,進行繪製。
3.經過 swapBuffers 將繪製好的數據提交給 SF 去合成,值得注意的是此時可能 GPU 並無完成當前的渲染任務,可是爲了提升效率,這裏能夠不用阻塞渲染線程。
3.當全部的繪製操做都經過 GL 提交給了 GPU 的時候,若是前面數據同步失敗了,那麼這個時候須要喚醒 ui thread。
3、Surface家族源碼全解析
上一章咱們講了 Android 的整個繪製機制,可是其中涉及到 Surface 的部分都是簡單帶過。因此這一章我就來解析一下 Surface 家族中各個成員的源碼。
1.Surface的建立與繪製
(1).Surface的建立
**這裏咱們以 View 的建立流程爲例,講述一下 Surface 在這個過程當中的建立流程,Surface 的建立流程如圖5所示。 **
1.首先咱們須要知道的是 ViewRootImpl 在建立的時候會使用 new Surface 來建立一個 java 層的空殼。這個空殼會在後面被初始化。
2.而後入口的調用鏈是這樣的:ViewRootImpl.performTraversals——>ViewRootImpl.relayoutWindow——>WindowManagerService.relayoutWindow——>WindowManagerService.createSurfaceControl。
1.createSurfaceControl 這個方法裏首先會 WindowStateAnimator.createSurfaceLocked——>new WindowSurfaceController——>new SurfaceControlWithBackground——>new SurfaceControl——>SurfaceControl.nativeCreate——>android_view_SurfaceControl::nativeCreate 來建立一個SurfaceControl.cpp 這個東西是初始化 Surface 的參數。
1.nativeCreate 的第一步是建立一個 SurfaceComposerClient.cpp 它實際上是 SF 所在進程的代理,咱們能夠經過這個類對 SF 進行操做。在調用 new SurfaceComposerClient.cpp 的構造函數以後,首先會觸發 onFirstRef,這裏面則會使用 ComposerService::getComposerService() 獲取 SF 的服務。而後遠程調用 ComposerService::createConnection 也就是 SF::createConnection 來建立一個 ISurfaceComposer 做爲 SurfaceComposerClient 與 SF 進程交互的接口,這裏用到的是 Binder 機制 。
2.SurfaceComposerClient 建立完畢以後,就能夠調用 SurfaceComposerClient::createSurface——>Client::createSurface 來向 SF 進程發送建立 Surface 的請求了。SF 進程也是事件驅動模式,因此 Client::createSurface 中調用 SF::postMessageSync 發送了一個調用 SF::createLayer 方法的消息給事件隊列。 而 createLayer 最終會調用 createNormalLayer 中。這個方法會返回一個 IGraphicBufferProducer 給 SurfaceControl.cpp。**記得咱們在前面講的 生產者——消費者 模式嗎?**這裏的 IGraphicBufferProducer 就是 SF 的 BQ 分離出來的生產者,咱們後續就能夠經過這個 IGraphicBufferProducer 向 SF 的 BQ 中添加經過 Surface 繪製好的圖像內存了。
2.回到 createSurfaceControl 這裏建立了一個 SurfaceControl.java 以後,下一步就是初始化 Surface.java。這裏就比較簡單了,就是經過調用鏈:SurfaceController.getSurface(Surface)——>Surface.copyFrom(SurfaceControl)——>Surface.nativeCreateFromSurfaceControl——>SurfaceControl::getSurface。將 SurfaceControl.cpp 中的 IGraphicBufferProducer 做爲參數建立一個 Surface.cpp 交給 Surface.java。
(2).Surface的繪製
咱們在第二章裏面說到 View 的繪製根據是否硬件加速 分爲,軟件繪製 與硬件繪製 兩種。當時咱們分析了硬件繪製,軟件繪製略過了。其實軟件繪製與硬件繪製的區別就在因而使用 CPU 進行繪製計算仍是使用 GPU 進行繪製計算。這一小節的 Surface 繪製其實就是軟件繪製,也就是 ViewRootImpl.drawSoftware 中的內容。
咱們都知道 Surface 能夠經過 lockCanvas 和 unlockCanvasAndPost 這兩個 api 來再經過 Canvas 來繪製圖像,這一節我就經過這兩個 api 來說講 Surface 的繪製流程,整個流程如圖6所示。
1.首先咱們從 lockCanvas 這個入口開始調用鏈是:lockCanvas——>nativeLockCanvas——>Surface::lock——>Surface::dequeueBuffer,這裏最終會使用咱們在 Surface 建立的時候獲得的 BufferQueueProducer(IGraphicBufferProducer) 來想 SF 請求一塊空白的圖像內存。獲得了圖像內存以後,將內存傳入 new SkBitmap.cpp 中建立對應對象,一共後面使用 Skia 庫繪製圖像。這裏的 SkBitmap.cpp 會被交給 SkCanvas.cpp 而 SkCanvas.cpp 對象就是 Canvas.java 在 c++ 層的操做對象。
2.上面咱們經過 lockCanvas 獲取到了一個 Canvas 對象。當咱們調用 Canvas 的各類 api 的時候其實最終會代用到 c++ 層的 Skia 庫,經過 cpu 對圖像內存進行繪製。
3.當咱們繪製完以後就能夠調用 unlockCanvasAndPost 來通知 SF 合成圖像,調用鏈是:unlockCanvasAndPost——>nativeUnlockCanvasAndPost——>Surface::queueBuffer,與 lockCanvas 中相反這裏是最終是經過 BufferQueueProducer::queueBuffer 將圖像內存放回到隊列中,除此以外這裏還調用 IConsumerListener::onFrameAvailable 來通知 SF 進程來刷新圖像,調用鏈是:onFrameAvailable——>SF.signalLayerUpdate——>MQ.invalidate——>MQ.Handler.dispatchInvalidate——>MQ.Handler.handleMessage——>onMessageReceived:由於 SF 進程採用的也是事件驅動模型,因此這裏和 ui thread 相似也是經過 looper + 事件 的形式觸發 SurfaceFinger 對圖像的刷新的。注意:這裏的 IConsumerListener 是在 createNormalLayer 的時候建立的 Layer.cpp 。
2.SurfaceView的建立與使用
其實只要瞭解了 Surface 的建立與使用,那麼 SV 就很簡單了,SV.updateSurface 中會建立或者更新 Surface。在 SV 上繪製也是調用 Surface 的兩個 api。這裏我就簡單將 View 與 SV 比較一下。
1.原理:通常的 Activity 包含的多個 View 會組成 View Hierachy 的樹形結構,只有最頂層的 DecorView,也就是根結點視圖,纔是對 WMS 可見的。這個 DecorView 在 WMS 中有一個對應的 WindowState。相應地,在 SF 中有對應的 Layer。而 SV 自帶一個 Surface,這個 Surface 在 WMS 中有本身對應的 WindowState,在 SF 中也會有本身的 Layer。
2.好處:在 App 端 Surface 仍在 View Hierachy 中,但在 Server 端(WMS 和 SF)中,它與宿主窗口是分離的。這樣的好處是對這個 Surface 的渲染能夠放到單獨線程去作,渲染時能夠有本身的 GL context。這對於一些遊戲、視頻等性能相關的應用很是有益,由於它不會影響主線程對事件的響應。
3.壞處:由於這個 Surface 不在 View Hierachy 中,它的顯示也不受 View 的屬性控制,因此不能進行平移,縮放等變換,也不能放在其它 ViewGroup 中,一些 View 中的特性也沒法使用。
3.SurfaceTexture的建立與使用
ST 咱們平時可能用的不是不少,可是其實它是 TV 的核心組件。它能夠將 Surface 提供的圖像流轉換爲 GL 的紋理,而後對該內容進行二次處理,最終被處理的內容能夠被交給其餘消費者消費。這一節咱們就來從源碼層次解析一下 ST。
圖7是 ST 與 Surface、SV、TV 等等組件結合的概覽圖,我這裏簡單解釋一下:
1.最左邊表示原始圖像流的生成方式:Video(本地/網絡視頻流)、Camera(攝像頭拍攝的視頻流)、Software/Hardware Render(使用 Skia/GL 繪製的圖像流)。
2.最左邊的原始圖像流能夠被交給 Surface,而 Surface 是 BQ 的生產者,GLConsumer(java層的體現就是 ST) 是 BQ 的消費者。
3.GLConsumer 拿到了 Surface 的原始圖像流,能夠經過 GL 轉化爲 texture。最終以合理的方式消耗掉。
4.消耗 GLConsumer 中的 texture 的方式就是使用 SV、TV 等等方式或者顯示在屏幕上或者用於其餘地方。
我將根據圖8的流程來說解 ST 的建立與使用
1.首先咱們從 ST.java 的建立開始,也就是圖中的黃色方框。注意 ST 的建立須要傳入一個 GLES20.glGenTextures 建立的 Texture 的 Id。Surface 提供的圖像內存最終也是會被掛靠在這個 Texture 中的。
1.建立的的調用鏈:SurfaceTexture.nativeInit——>SurfaceTexture::SurfaceTexture_init。
2.SurfaceTexture_init 這個方法裏關鍵步驟以下:
1.建立 IGraphicBufferProducer 和 IGraphicBufferConsumer 根據前面咱們知道這兩個類是 BQ 的生產者和消費者。
2.調用 BQ.createBufferQueue 將上面建立的生產者和消費者傳入,建立一個 BQ。
3.建立一個 GLConsumer 這個東西至關於 IGraphicBufferConsumer 封裝類也是 ST 在 c++ 層的最終實現類。
4.調用 SurfaceTexture_setSurfaceTexture 爲 ST.java 的 mSurfaceTexture 賦值,這裏被賦值的對象就是上面建立的 GLConsumer。
5.調用 SurfaceTexture_setProducer 一樣爲 ST 的 mProducer 賦值。
6.調用 SurfaceTexture_setFrameAvailableListener 爲 GLConsumer 添加一個 java 層的 OnFrameAvailableListener 做爲監聽圖像流的回調。這個 OnFrameAvailableListener 是調用 ST.setOnFrameAvailableListener 添加的。
2.ST 初始化完畢以後,咱們此時須要一個 Surface 來做爲生產者爲 ST 提供圖像流。
1.還記得 Surface.java 有個構造函數是須要以 ST 做爲參數的嗎?咱們就從這個函數入手,調用鏈以下:new Surface(ST)——>Surface.nativeCreateFromSurfaceTexture——>android_view_Surface::
nativeCreateFromSurfaceTexture。
2.nativeCreateFromSurfaceTexture 中的關鍵步驟以下:
1.調用 SurfaceTexture_getProducer 獲取 ST.java 中儲存的 mProducer 對象在 c++ 層的指針。
2.經過上面獲取的 IGraphicBufferProducer 對象來建立 Surface.cpp 對象。
3.建立了一個負責提供圖像流的 Surface 以後咱們就可使用它的兩個 api 來繪製圖像以提供圖像流了。
1.lockCanvas 和 unlockCanvasAndPost 這兩個 api 我在這裏就不重複解析了。
2.根據前面講解的 Surface 的繪製 咱們知道調用了 unlockCanvasAndPost 以後會觸發一個 IConsumerListener.onFrameAvailable 的回調。在 View 的繪製流程中咱們知道這裏的回調最終會觸發 SF 的圖像合成。那麼這裏的回調的實現類是誰呢?你猜的沒錯此時的實現類是 GLConsumer 中被設置的 java 層的 OnFrameAvailableListener,咱們在 Surface.cpp 建立的時候會傳入一個 IGraphicBufferProducer 它最終會經過調用鏈: BQ::ProxyConsumerListener::onFrameAvailable——>ConsumerBase::onFrameAvailable——>FrameAvailableListener::onFrameAvailable——>JNISurfaceTextureContext::onFrameAvailable——>OnFrameAvailableListener.onFrameAvailable 調用到 java 層。
3.固然 OnFrameAvailableListener 回調的東西是由咱們本身來寫的。
1.咱們知道 SurfaceTexture 的最終目的是將圖像流綁定到咱們最開始定義的 texture 中去。SurfaceTexture 正好提供了 updateTexImage 方法來刷新 texture。
2.那麼咱們就在 OnFrameAvailableListener 中直接調用 updateTexImage 嗎?這裏實際上是不必定的。由於咱們知道 Surface 的繪製能夠在任意線程,也就是說 OnFrameAvailableListener 的回調也是在任意線程中觸發的。而 texture 的更新須要在當初建立這個 texture 的 GL 環境中進行 。此外 Android 中 GL 環境和線程是一一對應的 。因此只有 Surface 繪製的線程與 GL 環境線程爲同一個的時候,咱們才能在回調中調用 updateTexImage。
3.無論如何最終仍是要調用 updateTexImage 的,咱們再來看看它內部是怎麼個實現。首先調用鏈是這樣的:SurfaceTexture.updateTexImage——>SurfaceTexture::nativeUpdateTexImage——>GLConsumer::updateTexImage。GLConsumer::updateTexImage 中重要的步驟以下:
1.調用 acquireBufferLocked 獲取 BQ 頭部最新的圖像內存。
2.調用 glBindTexture 將獲取到的圖像內存綁定到 texture 中。
3.調用 updateAndReleaseLocked 重置前面獲取到的圖像內存,而後將其放回到 BQ 的尾部
4.至此咱們在 Surface 中繪製的圖像流就被 SurfaceTexture 轉化成了一個 texture。至於繼續對 texture 進行顯示和處理之類的事情就能夠交給 TV 或者 GLSurfaceView 了。
4.TextureView和建立與使用
它能夠將內容流直接投影到 View 中,能夠用於實現視頻預覽等功能。和 SV 不一樣,它不會在WMS中單首創建窗口,而是做爲 View Hierachy 中的一個普通 View,所以能夠和其它普通 View 同樣進行移動,旋轉,縮放,動畫等變化。值得注意的是 TV 必須在硬件加速的窗口中。它顯示的內容流數據能夠來自 App 進程或是遠端進程。這一節咱們就來從源碼上分析它。由於 TV 須要硬件加速,因此它最終也是由渲染線程繪製的,而咱們在第一章中講述了 ThreadRender.java、RenderProxy.cpp、RenderThread.cpp 等等類在渲染線程中的做用,因此這一小節關於渲染線程的東西就直接使用而再也不贅述了。
和前面同樣,本小節接下來的分析也都是順着圖9來的
1.由於 TV 咱們能夠將其當作一個普通的 View,因此這裏咱們能夠直接從 TV 的 draw 方法開始分析。draw 中有下面這些關鍵的步驟:
1.調用 getHardwareLayer 來獲取硬件加速層:
1.ThreadRender.createTextureLayer——>ThreadRender.nCreateTextureLayer——>RenderProxt::createTextureLayer——>new DeferredLayerUpdater.cpp:這裏第一個調用鏈建立了一個 DeferredLayerUpdater.cpp 對象,這個對象會在後面用於 ST 的 texture 更新。
2.ThreadRender,createTextureLayer 中建立完 DeferredLayerUpdater.cpp 還須要調用 HardwareLayer.adoptTextureLayer 將 ThreadRender,java 和 DeferredLayerUpdater.cpp 在 java層封裝一下,以便後續使用。
3.建立了 HardwareLayer 後,若是 ST 還沒建立的話,那麼就會去建立一個 ST。這裏的建立流程上一節講過了也再也不贅述。同理這裏會調用 nCreateNativeWindow 在 c++ 層建立一個 ANativeWindow(也就是 java 層的 Surface.java)。這個 Surface 主要是爲了讓 TV 可以提供 lockCanvas 和 unlockCanvasAndPost 這裏兩個 Api 而建立的。
4.除了初始化 ST 的一系列操做,這裏還會調用 HardwareLayer.setSurfaceTexture 爲 DeferredLayerUpdater 設置 ST。由於後面 DeferredLayerUpdater 更新 ST 的 texture 的時候須要用到 ST。
5.接下來會調用 applyUpdate 可是由於是初次建立,因此這裏先略過。
6.而後會調用 ST.setOnFrameAvailableListener(mUpdateListener) 來爲圖形流添加監聽,而這裏的 mUpdateListener 實現就在 TV 裏面,這裏的實現等咱們調用到了再說。
7.至此 ST 其實已經初始化完成了,因此能夠調用 mListener.onSurfaceTextureAvailable 來回調外部的代碼了。
2.調用 DisplayListCanvas.drawHardwareLayer 在當前 View Hierachay 的 Surface 上面添加繪製一個獨立的 layer(簡單來講就是一個繪圖的區域)。**注意:這裏的 layer 與 咱們在前面說的 SV 在 SF 中存在的 Layer 是兩個不一樣的概念。 **
2.TV 建立好了,接下來咱們就能夠爲其提供圖像流。這裏咱們就用 lockCanvas 和 unlockCanvasAndPost 來提供吧。注意:其實 ST 和這裏的 TV 不只僅可使用上面兩個 api 來提供圖像流,還能夠將 Surface 轉化成 EGLSurface 使用 GL 來提供圖像流,更常見還有攝像頭提供的圖像流,這些我在這裏就不展開了,交給讀者本身去探索。
1.首先 lockCanvas 和 unlockCanvasAndPost 這兩個 api 的源碼流程和回調流程我就再也不贅述了,在 ST 和 Surface 的解析中都有。
2.咱們直接看 TV 中 OnFrameAvailableListener 的實現代碼。
1.這裏先調用 updateLayer 將 mUpdateLayer 置爲 true,表示下一次 VSync 信號過來的時候 TV 須要刷新。注意 TV 是在 View Hierarchy 中的,因此須要與 View 一塊兒刷新。
2.調用 invalidate 觸發 ViewRootImpl 的重繪流程。
3.16ms 以後 VSync 信號來了,通過一系列方法以後調用又回到了 TV.draw 中。此時由於 ST 已經建立,因此最主要的代碼就是 TV.applyUpdate 方法。
1.先調用 HardwareLayer.nPrerare——>RenderProxt::pushLayerUpdate 將前面建立的 DeferredLayerUpdater 存入 RenderProxt.cpp 中。
2.調用 HardwareLayer.nUpdateSurfaceTexture 將 DeferredLayerUpdater 中的 mUpdateTexImage 置爲 true。爲啥不在這裏就直接更新 texture 呢?由於此時的線程仍是 ui thread,而 texture 的更新須要在渲染線程作。
4.咱們回憶一下第二章分析 Android繪製機制 源碼,當時咱們講過當 View Hierarchy 的 draw 操做完成以後,通過一系列調用就會進入到渲染線程中。這裏咱們也再也不贅述了,咱們直接看 RenderProxt::syncAndDrawFrame——>DrawFrameTask::drawFrame——>DrawFrameTask::syncFrameState 就是在渲染線程中運行的。它會觸發咱們存儲在 RenderProxy.cpp 中的 DeferredLayerUpdater::apply 方法,這個方法有這些關鍵步驟
1.GLConsumer::attachToContext,將當前的 ST 與 texture 綁定。
2.GLConsumer::updateTexImage,像前面講的 ST 同樣將圖像流更新到 texture 中
3.LayerRenderer::updateTextureLayer
3.到這裏 TV 從建立到圖像流轉換的源代碼都解析完了,剩下的就是 Android繪製機制 中講的那樣使用 GL 進行真正的繪製了。
4、總結
這篇文章真是寫死我了!!過年有三分之二的時間花在這個上面,感受這篇文章真的是筆者有史以來寫過的最有乾貨的一篇文章了,但願可以對你們有所幫助。另外看在我這麼辛苦的份上,但願你們能順手關注一下個人公衆號:世界上有意思的事 ,二維碼就在文章底部,跪謝!!
連載文章
不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限於:科幻、科學、科技、互聯網、程序員、計算機編程。下面是個人微信公衆號:世界上有意思的事 ,乾貨多多等你來看。
參考文獻