解决SurfaceView渲染的各种疑难杂症
======================== 2020.3.24 修改部分過時API =============================
Pangu-Immortal (Pangu-Immortal) · GitHub
RecyclerView加載多個surfaceview覆蓋,旋轉,黑屏 ??
SurfaceView黑色背景??透明背景??多層嵌套被遮擋??
苦苦找了好多天,各個論壇問遍了,都是互相抄,痛苦的我,嘗試了好多種方式都解決不了。
翻了幾天surfaceview的源碼和API,現把解決方法總結,分享一下。
首先說:不能在list視圖中使用VideoView,因為VideoView繼承SurfaceView,
而SurfaceView不支持UI同步緩沖(UI synchronization buffer),這導致當滑動list時視頻會丟進度。
TextureView支持同步緩沖,但沒有基于TextureView的VideoView。這個問題至今我無法解決。
使用WebRTC 58 想實現一個視頻窗口列表,幾十個視頻窗口,劃出屏幕就暫停,顯示時就開始加載。仍無法無法實現WebRTC內使用的是SurfaceView。
贈送源碼:GitHub - Pangu-Immortal/MagicWX: 🔥免root實現 Android改機(一鍵新機)技術解密,微信無限多開等。。
Pangu-Immortal (Pangu-Immortal) · GitHub
《最完整的Android逆向知識體系》
這個問題有 愿意討論 或者 做過類似效果,或者知道如何處理的,請告知我一聲,十萬分的感謝。。。
看一下google的API都有哪些:
多層嵌套被遮擋:
setZOrderOnTop(boolean onTop) // 在最頂層,會遮擋一切view
setZOrderMediaOverlay(boolean isMediaOverlay)// 如已繪制SurfaceView則在surfaceView上一層繪制。
網上很多人都會告訴你第一個,幾乎都是互相抄襲,應用在游戲里還可以,多窗口視頻是不可以的。
如果在surfaceView上繪制surfaceView應該用第二個,并且必須在addview之后調用。
- layout.addView(surfaceView);
- surfaceView.setZOrderMediaOverlay(true); ? // 必須layout.addView之后使用,必須動態調用。
SurfaceView 怎么進行旋轉,透明操作的?
-
普通View旋轉后,View的內容也跟著同步做了旋轉.
-
SurfaceView在旋轉之后,其顯示內容并沒有跟著一起旋轉.
比喻:這就好比在墻上開了一個窗(Surface),通過窗口可以看外面的花花世界,但窗口無論怎樣變化,窗外面的世界是不會跟著窗口一同變化。
一般視頻播放器可以橫豎屏切換,是如何實現的?
在 Activity 中覆寫 onConfigurationChanged 方法就可以。根據橫豎屏切換,修改 SurfaceView 的 Parameter 的寬高參數即可。
不繪制任何東西,SurfaceView顯示的是黑色?
每次更新視圖時都會先將背景繪制成黑色。所以在移動或者縮放過程,會更新不及時時就會看黑邊。
@Override public void draw(Canvas canvas) {if (mDrawFinished && !isAboveParent()) {// draw() is not called when SKIP_DRAW is setif ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {// punch a whole in the view-hierarchy below uscanvas.drawColor(0, PorterDuff.Mode.CLEAR);}}super.draw(canvas); }//這句話表示PorterDuff.Mode.CLEAR會將像素設置為0,也就是黑色 //Destination pixels covered by the source are cleared to 0. public enum Mode {// these value must match their native equivalents. See SkXfermode.h/*** <p>* <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />* <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>* </p>* <p>\(\alpha_{out} = 0\)</p>* <p>\(C_{out} = 0\)</p>*/CLEAR (0), }SurfaceView背景問題:
很多人介紹了好多種方法改變他的黑色背景為透明。我初步加載時就直接看到了桌面,直接透過去了。并沒有遇到很多人說的默認黑色背景。
原因是設置的主題問題,由于我自定義了dialog。有無法去除的背景白條,所以設置了主題,這個白條背景也是主題引起的。然而這個主題
也造成了透明的效果。
嘗試好多天,翻遍各種API,才突然意識到可能是主題不同引起的。
studio創建工程時的默認主題:
<!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item></style>這個主題是默認SurfaceView為黑色背景,當然dialog也是有無法去除的背景,想設置半透明就要設置一個主題。
<style name="Theme.AppStartLoadTranslucent" parent="android:Theme"><item name="android:windowIsTranslucent">true</item><item name="android:windowNoTitle">true</item></style>注意看主題的值:parent="android:Theme" ? ,item不重要。如果是:DarkActionBar 就是黑色背景。
我換成了這個主題,dialog的背景就成功去除了,當然surfaceView默認加載就是透到桌面的,沒有背景,如果設置背景會遮蓋住畫面。必須換一個主題。
網上你會搜到很多更改背景為透明的方法,比如:
- surfaceView.setZOrderOnTop(true);
- surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); ? // 設置畫布 ?背景透明
這是在默認的主題下設置的。多次嘗試后“Theme.Design.NoActionBar” 這個主題很通用。可以嘗試一下。有問題可以留言,我們再討論。
當你發現一些能夠API對你的View不起作用,尤其是透明度,背景,圓角等,你感覺主題可以設置的一切屬性,你要先檢查一下自己設置的主題樣式是不是對他有限制。
可以嘗試換個主題試試呢,沒準會有意外收獲。。
贈送源碼:GitHub - Pangu-Immortal/MagicWX: 🔥免root實現 Android改機(一鍵新機)技術解密,微信無限多開等。。
《最完整的Android逆向知識體系》
SurfaceView的生命周期管理有三個方法:
-
SurfaceCreated
-
SurfaceChanged
-
SurfaceDestoryed
如何使用SurfaceView呢?
1、獲取SurfaceHolder對象,其是SurfaceView的內部類。
-
監聽Surface生命周期。
-
只有當native層的Surface創建完畢之后,才可以調用lockCanvas(),否則失敗。
holder.Callback。
2、調用holder.lockCanvas()。
3、繪制
4、調用SurfaceHolder.unlockCanvasAndPost,將繪制內容post到Surface中
SurfaceView的特點有那些
-
具有獨立的繪圖表面Surface。
-
需要在宿主窗口上挖一個洞來顯示自己,z軸比普通的window要小。
-
它的UI繪制可以在獨立的線程中進行,這樣就可以進行復雜的UI繪制,并且不會影響應用程序的主線程響應用戶輸入。
SurfaceView的優缺點
優點
-
在一個子線程中對自己進行繪制,避免造成UI線程阻塞。
-
高效復雜的UI效果。
-
獨立Surface,獨立的Window。
-
使用雙緩沖機制,播放視頻時畫面更流暢。
缺點
-
每次繪制都會優先繪制黑色背景,更新不及時會出現黑邊現象。
-
Surface不在View hierachy中,它的顯示也不受View的屬性控制,平移,縮放等變換。
-
不支持UI同步緩沖
SurfaceVeiw雙緩沖區
雙緩沖:在運用時可以理解為:SurfaceView在更新視圖時用到了兩張 Canvas,一張 frontCanvas 和一張 backCanvas ,每次實際顯示的是 frontCanvas ,backCanvas 存儲的是上一次更改前的視圖。當你在播放這一幀的時候,它已經提前幫你加載好后面一幀了,所以播放起視頻很流暢。
當使用lockCanvas()獲取畫布時,得到的實際上是backCanvas 而不是正在顯示的 frontCanvas ,之后你在獲取到的 backCanvas 上繪制新視圖,再 unlockCanvasAndPost(canvas)此視圖,那么上傳的這張 canvas 將替換原來的 frontCanvas 作為新的frontCanvas ,原來的 frontCanvas 將切換到后臺作為 backCanvas 。例如,如果你已經先后兩次繪制了視圖A和B,那么你再調用 lockCanvas()獲取視圖,獲得的將是A而不是正在顯示的B,之后你將重繪的 A 視圖上傳,那么 A 將取代 B 作為新的 frontCanvas 顯示在SurfaceView 上,原來的B則轉換為backCanvas。
相當與多個線程,交替解析和渲染每一幀視頻數據。
surfaceholder.lockCanvas--surfaceholder.unlockCanvasAndPostSurfaceView 和普通的View的區別?
-
surfaceView是在一個新起的單獨線程中可以重新繪制畫面。
-
View必須在UI的主線程中更新畫面。
-
那么在UI的主線程中更新畫面可能會引發問題,比如你更新畫面的時間過長,那么你的主UI線程會被你正在畫的函數阻塞。那么將無法響應按鍵,觸屏等消息。
-
當使用surfaceView 由于是在新的線程中更新畫面所以不會阻塞你的UI主線程。
SurfaceView 生命周期
使用:雙緩沖
導致:需要更多的內存開銷
為了節約系統內存開銷:
SurfaceView 可見時 -> 創建 SurfaceHolder
SurfaecView 不可見時 -> 摧毀 SurfaceHolder
1、程序打開
Activity 調用順序:onCreate()->onStart()->onResume()
SurfaceView 調用順序: surfaceCreated()->surfaceChanged()
2、程序關閉(按 BACK 鍵)
Activity 調用順序:onPause()->onStop()->onDestory()
SurfaceView 調用順序: surfaceDestroyed()
3、程序切到后臺(按 HOME 鍵)
Activity 調用順序:onPause()->onStop()
SurfaceView 調用順序: surfaceDestroyed()
4、程序切到前臺
Activity 調用順序: onRestart()->onStart()->onResume()
SurfaceView 調用順序: surfaceChanged()->surfaceCreated()
5、屏幕鎖定(掛斷鍵或鎖定屏幕)
Activity 調用順序: onPause()
SurfaceView 什么方法都不調用
6、屏幕解鎖?
Activity 調用順序: onResume()
SurfaceView 什么方法都不調用
橫屏錄制橫屏播放,豎屏錄制豎屏播放
通過以下方法可以獲取到視頻的寬高,根據視頻的寬高就可以知道該視頻是橫屏還是豎屏錄制的。
橫屏判斷:width>height
旋轉屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
豎屏錄制:height>width
旋轉屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
View的繪制要知道的知識
View的繪制其實是在UI線程(實現onCanvas方法進行繪制)。如果進行繪制高效復雜的UI,最好不用自定義View。要用SurfaceView進行繪制。
SurfaceView 能繪制什么東西?
從下面代碼可以看到,SurfaceView 的繪制也是使用 Canvas 進行繪制的,繪制應該跟普通的 View 繪制差不多
View的繪畫三要素
-
Canvas (畫布,繪制BitMap操作)
-
Paint (繪制的畫筆Paint,顏色、樣式)
-
Path (路徑)
一、Canvas
-
如果直接extends View 可以重寫onDraw(Canvas canvas)方法,直接用里面的canvas進行繪制。
-
可以直接利用Activity的繪制機制,用lockCanvas()方法來獲得當前的Activity的Canvas。
-
在SurfaceView中,同2可以利用SurfaceHolder的對象的lockCanvas()方法來Canvas。
二、Paint
直接通過new關鍵字來實例化,然后通過Paint對象來對畫筆進行相應的設置:
如:
-
1.1 去鋸齒setAntiAlia(true)
-
1.2 去抖動setDither(true)
-
1.3 設置圖層混合模式setXfermode(Xfermode,xfermode)
三、 Path
-
1、Path路徑 直接用new來實例化
-
2、通過path對象設置想要畫圖的軌跡或路線,如:矩形 、三角形 、圓、曲線等
綜上所述:
為什么視頻技術入門要先了解圖片繪制,那么圖片繪制的API也有多種,為什么選擇用SurfaceView這個API,
因為:
其一,繪制是在子線程中進行繪制的,
其二,可能繪制出高效復雜的UI效果,
其三,使用雙緩沖機制,播放視頻時畫面更流暢。
找到的一些小項目源碼:Pangu-Immortal (Pangu-Immortal) · GitHub
很值得學習的一個視頻直播項目?https://github.com/littleMeng/video-live
贈送源碼:GitHub - Pangu-Immortal/MagicWX: 🔥免root實現 Android改機(一鍵新機)技術解密,微信無限多開等。。
《最完整的Android逆向知識體系》
沉溺于編程不可自拔時,也要記得多多鍛煉。。
總結
以上是生活随笔為你收集整理的解决SurfaceView渲染的各种疑难杂症的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Android高性能音频--Open
- 下一篇: Android studio无法连接识别