解決SurfaceView渲染的各種疑難雜癥

作者:hao_qi
來源:CSDN
原文:https://blog.csdn.net/gfg156196/article/details/72899287
版權聲明:本文為博主原創(chuàng)文章,轉載請附上博文鏈接!

RecyclerView加載多個surfaceview覆蓋,旋轉,黑屏 ??

SurfaceView黑色背景??透明背景??多層嵌套被遮擋??

苦苦找了好多天,各個論壇問遍了,都是互相抄,痛苦的我,嘗試了好多種方式都解決不了。

翻了幾天surfaceview的源碼和API,現把解決方法總結,分享一下。

首先說:不能在list視圖中使用VideoView,因為VideoView繼承SurfaceView,

而SurfaceView不支持UI同步緩沖(UI synchronization buffer),這導致當滑動list時視頻會丟進度。

TextureView支持同步緩沖,但沒有基于TextureView的VideoView。這個問題至今我無法解決。

使用WebRTC 58 想實現一個視頻窗口列表,幾十個視頻窗口,劃出屏幕就暫停,顯示時就開始加載。仍無法無法實現WebRTC內使用的是SurfaceView。

這個問題有 愿意討論 或者 做過類似效果,或者知道如何處理的,請告知我一聲,十萬分的感謝。。。

看一下google的API都有哪些:


1111111.png

多層嵌套被遮擋:

setZOrderOnTop(boolean onTop) // 在最頂層,會遮擋一切view

setZOrderMediaOverlay(boolean isMediaOverlay)// 如已繪制SurfaceView則在surfaceView上一層繪制。

網上很多人都會告訴你第一個,幾乎都是互相抄襲,應用在游戲里還可以,多窗口視頻是不可以的。

如果在surfaceView上繪制surfaceView應該用第二個,并且必須在addview之后調用。

layout.addView(surfaceView);
surfaceView.setZOrderMediaOverlay(true); // 必須layout.addView之后使用,必須動態(tài)調用。
SurfaceView 怎么進行旋轉,透明操作的?

普通View旋轉后,View的內容也跟著同步做了旋轉.

SurfaceView在旋轉之后,其顯示內容并沒有跟著一起旋轉.

比喻:這就好比在墻上開了一個窗(Surface),通過窗口可以看外面的花花世界,但窗口無論怎樣變化,窗外面的世界是不會跟著窗口一同變化。

一般視頻播放器可以橫豎屏切換,是如何實現的?
在 Activity 中覆寫 onConfigurationChanged 方法就可以。根據橫豎屏切換,修改 SurfaceView 的 Parameter 的寬高參數即可。

android:configChanges="orientation|keyboardHidden|screenSize"
@Override  
public void onConfigurationChanged(Configuration newConfig) {  
 
    super.onConfigurationChanged(newConfig);  
 
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {  
            //變成橫屏了    
    }   else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {  
            //變成豎屏了 
    }  
}  

不繪制任何東西,SurfaceView顯示的是黑色?

每次更新視圖時都會先將背景繪制成黑色。所以在移動或者縮放過程,會更新不及時時就會看黑邊。

@Override
public void draw(Canvas canvas) {
    if (mDrawFinished && !isAboveParent()) {
        // draw() is not called when SKIP_DRAW is set
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
            // punch a whole in the view-hierarchy below us
            canvas.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創(chuàng)建工程時的默認主題:

 <!-- 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” 這個主題很通用??梢試L試一下。有問題可以留言,我們再討論。

當你發(fā)現一些能夠API對你的View不起作用,尤其是透明度,背景,圓角等,你感覺主題可以設置的一切屬性,你要先檢查一下自己設置的主題樣式是不是對他有限制。

可以嘗試換個主題試試呢,沒準會有意外收獲。。

SurfaceView的生命周期管理有三個方法:

SurfaceCreated

SurfaceChanged

SurfaceDestoryed

如何使用SurfaceView呢?

1、獲取SurfaceHolder對象,其是SurfaceView的內部類。

監(jiān)聽Surface生命周期。

只有當native層的Surface創(chuàng)建完畢之后,才可以調用lockCanvas(),否則失敗。
holder.Callback。

2、調用holder.lockCanvas()。
3、繪制
4、調用SurfaceHolder.unlockCanvasAndPost,將繪制內容post到Surface中

SurfaceView的特點有那些

具有獨立的繪圖表面Surface。

需要在宿主窗口上挖一個洞來顯示自己,z軸比普通的window要小。

它的UI繪制可以在獨立的線程中進行,這樣就可以進行復雜的UI繪制,并且不會影響應用程序的主線程響應用戶輸入。

SurfaceView的優(yōu)缺點

優(yōu)點

在一個子線程中對自己進行繪制,避免造成UI線程阻塞。

高效復雜的UI效果。

獨立Surface,獨立的Window。

使用雙緩沖機制,播放視頻時畫面更流暢。

缺點

每次繪制都會優(yōu)先繪制黑色背景,更新不及時會出現黑邊現象。

Surface不在View hierachy中,它的顯示也不受View的屬性控制,平移,縮放等變換。

不支持UI同步緩沖

SurfaceVeiw雙緩沖區(qū)

雙緩沖:在運用時可以理解為: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.unlockCanvasAndPost
SurfaceView 和普通的View的區(qū)別?

surfaceView是在一個新起的單獨線程中可以重新繪制畫面。

View必須在UI的主線程中更新畫面。

那么在UI的主線程中更新畫面可能會引發(fā)問題,比如你更新畫面的時間過長,那么你的主UI線程會被你正在畫的函數阻塞。那么將無法響應按鍵,觸屏等消息。

當使用surfaceView 由于是在新的線程中更新畫面所以不會阻塞你的UI主線程。

SurfaceView 生命周期

使用:雙緩沖
導致:需要更多的內存開銷
為了節(jié)約系統(tǒng)內存開銷:

SurfaceView 可見時 -> 創(chuàng)建 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 什么方法都不調用

橫屏錄制橫屏播放,豎屏錄制豎屏播放
通過以下方法可以獲取到視頻的寬高,根據視頻的寬高就可以知道該視頻是橫屏還是豎屏錄制的。

public void onVideoSizeChanged(MediaPlayer mp, int width, int height)
橫屏判斷:width>height
旋轉屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

豎屏錄制:height>width
旋轉屏幕:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
Log.e(TAG, "onVideoSizeChanged:WIDTH>>" + width);
Log.e(TAG, "onVideoSizeChanged:HEIGHT>>" + height);

    if (width > height) {
        //橫屏錄制
        if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }
    } else {
        //豎屏錄制
        if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
    }
}

});
View的繪制要知道的知識
View的繪制其實是在UI線程(實現onCanvas方法進行繪制)。如果進行繪制高效復雜的UI,最好不用自定義View。要用SurfaceView進行繪制。

SurfaceView 能繪制什么東西?
從下面代碼可以看到,SurfaceView 的繪制也是使用 Canvas 進行繪制的,繪制應該跟普通的 View 繪制差不多

/**

  • 繪制
    */
private void draw() {
    if (radius > getWidth()) {
        return;
    }
    Canvas canvas = mHolder.lockCanvas();
    if (canvas != null) {
        canvas.drawCircle(300, 300, radius += 10, mPaint);
        mHolder.unlockCanvasAndPost(canvas);
    }
}

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效果,

其三,使用雙緩沖機制,播放視頻時畫面更流暢。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容