======================== 2020.3.24 修改部分过时API =============================
RecyclerView加载多个surfaceview覆盖,旋转,黑屏 ??
SurfaceView黑色背景??透明背景??多层嵌套被遮挡??
苦苦找了好多天,各个论坛问遍了,都是互相抄,痛苦的我,尝试了好多种方式都解决不了。
翻了几天surfaceview的源码和API,现把解决方法总结,分享一下。
首先说:不能在list视图中使用VideoView,因为VideoView继承SurfaceView,
而SurfaceView不支持UI同步缓冲(UI synchronization buffer),这导致当滑动list时视频会丢进度。
TextureView支持同步缓冲,但没有基于TextureView的VideoView。这个问题至今我无法解决。
使用WebRTC 58 想实现一个视频窗口列表,几十个视频窗口,划出屏幕就暂停,显示时就开始加载。仍无法无法实现WebRTC内使用的是SurfaceView。
赠送源码:https://github.com/yugu88/MagicWX。
《最完整的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 的宽高参数即可。
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创建工程时的默认主题:
<!-- 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不起作用,尤其是透明度,背景,圆角等,你感觉主题可以设置的一切属性,你要先检查一下自己设置的主题样式是不是对他有限制。
可以尝试换个主题试试呢,没准会有意外收获。。
赠送源码:https://github.com/yugu88/MagicWX。
《最完整的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 什么方法都不调用
横屏录制横屏播放,竖屏录制竖屏播放 通过以下方法可以获取到视频的宽高,根据视频的宽高就可以知道该视频是横屏还是竖屏录制的。
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效果,
其三,使用双缓冲机制,播放视频时画面更流畅。
找到的一些小项目源码:https://github.com/yugu88/webRTC_Library
很值得学习的一个视频直播项目 https://github.com/littleMeng/video-live
赠送源码:https://github.com/yugu88/MagicWX。
《最完整的Android逆向知识体系》