在博主大大的基础上引入一些自己认为的重要的点
然后有一个知识点基于自己的理解修正一下(带有删除线),大家一起探讨:
------------以下为转载SurfaceView和普通view的区别及简单使用_lidongxiu0714的博客-CSDN博客
1 SurfaceView介绍
SurfaceView第一印象它是一个view,因为它继承了View,有两个直接子类GLSurfaceView,VideoView。但根据SDK文档SurfaceView和普通的view又有较大区别。
最显著的区别就是普通view和它的宿主窗口共享一个绘图表面(Surface),SurfaceView虽然也在View的树形结构中,但是它有属于自己的绘图表面,Surface 内部持有一个Canvas,可以利用这个Canvas绘制。
SurfaceView提供一个直接的绘图表面(Surface)嵌入到视图结构层次中。你可以控制这个Surface的格式,大小,SurfaceView负责在屏幕上正确的摆放Surface。简单说就是SurfaceView拥有自己的Surface,它与宿主窗口是分离的。
我们知道窗口中的view共享一个window,window又对应一个Surface,所以窗口中的view共享一个Surface,而SurfaceView拥有自己的Surface。SurfaceView会创建一个置于应用窗口之后的新窗口,SurfaceView相当于在Window上挖一个洞,它就是显示在这个洞里,其他的View是显示在Window上,所以View可以显示在 SurfaceView之上,也可以添加一些层在SurfaceView之上。
SurfaceView的窗口刷新的时候不需要重绘应用程序的窗口而android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次。
对于普通的view,Android中的窗口界面包括多个View组成的View Hierachy的树形结构,只有最顶层的DecorView才对WMS可见,这个DecorView在WMS中有一个对应的WindowState,此时APP请求创建Surface时,会在SurfaceFlinger内部建立对应的Layer。而对于SurfaceView它自带一个Surface,这个Surface在WMS有自己对应的WindowState,在SurfaceFlinger中有自己对应的layer。SurfaceView从APP端看它仍然在View hierachy结构中,但在WMS和SurfaceFlinger中它与宿主窗口是分离的。因此SurfaceView的Surface的渲染可以放到单独线程去做,不会影响主线程对事件的响应。但因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换(对SurfaceView进行ScrollBy,ScrollTo操作没有效果(还有透明度,旋转),普通View进行平移操作,内部内容会移动,SurfaceView进行这些操作内部不会移动。如果对包裹它的ViewGroup进行平移旋转等操作,也无法达到我们想要的效果。同时SurfaceView不能放在类似RecyclerView或ScrollView中,一些View中的特性也无法使用。
SurfaceView不支持平移,缩放,旋转等动画,但是当我们利用SurfaceView测试这些不支持的动画时,如果使用的是7.0 甚至更高版本的Android系统,会发现SurfaceView也支持平移,缩放的动画操作。
View和SurfaceView的区别:
View适用主动更新,SurfaceView 适用被动更新,如频繁的刷新。
这里修正为
View:必须在UI的主线程中更新画面,用于被动更新画面。
SurfaceView:UI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。
View在UI线程更新,在非UI线程更新会报错,当在主线程更新view时如果耗时过长也会出错, SurfaceView在子线程刷新不会阻塞主线程,适用于界面频繁更新、对帧率要求较高的情况。
SurfaceView可以控制刷新频率。
SurfaceView底层利用双缓存机制,绘图时不会出现闪烁问题。
双缓冲技术是游戏开发中的一个重要的技术,主要是为了解决 反复局部刷屏带来的闪烁。游戏,视频等画面变化较频繁,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。双缓冲技术会把要处理的图片在内存中处理好之后,把要画的东西先画到一个内存区域里,然后整体的一次性画出来,将其显示在屏幕上。
2 SurfaceView 使用步骤
首先要继承SurfaceView,实现SurfaceHolder.Callback接口。
重写方法:
surfaceChanged:surface大小或格式发生变化时触发,在surfaceCreated调用后该函数至少会被调用一次。
surfaceCreated:Surface创建时触发,一般在这个函数开启绘图线程(新的线程,不要再这个线程中绘制Surface)。
surfaceDestroyed:销毁时触发,一般不可见时就会销毁。
利用getHolder()获取SurfaceHolder对象,调用SurfaceHolder.addCallback添加回调
SurfaceHolder.lockCanvas 获取Canvas对象并锁定画布,调用Canvas绘图,SurfaceHolder.unlockCanvasAndPost 结束锁定画布,提交改变。
3 SurfaceHolder
SurfaceView的双缓冲的机制非常消耗系统内存,Android规定SurfaceView不可见时,会立即销毁SurfaceView的SurfaceHolder,以达到节约系统资源的目的,所以需要利用SurfaceHolder的回调函数对SurfaceHolder进行维护。
提供了三个回调函数让我们知道SurfaceHolder的创建、销毁或者改变
void surfaceDestroyed(SurfaceHolder holder):当SurfaceHolder被销毁的时候回调。
void surfaceCreated(SurfaceHolder holder):当SurfaceHolder被创建的时候回调。
void surfaceChange(SurfaceHolder holder):当SurfaceHolder的尺寸或格式发生变化的时候被回调。
abstract Canvas lockCanvas():
获取一个Canvas对象,并锁定,得到的Canvas对象,就是Surface中一个成员。
** abstract Canvas lockCanvas(Rectdirty):**
仅仅锁定dirty所指定的矩形区域。
abstract void unlockCanvasAndPost(Canvascanvas)
当改动Surface中的数据后,释放同步锁,并提交改变,然后将新的数据进行展示,同一时候Surface中相关数据会被丢失。
public abstract void setType (int type):
设置Surface的类型,高版本中,setType这种方法已经被depreciated了,系统会自动设置。
SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
SURFACE_TYPE_GPU:适用于GPU加速的Surface
SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包括原生数据,Surface用到的数据由其它对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,生成图像更流畅。
兼容性:
SurfaceView的兼容性
Android4.0以下SurfaceView不会自动维护缓冲区,播放视频时,如果使用SurfaceView开发游戏应用,就需要我们自己维护这个缓冲区了。
// 4.0版本之下需要设置的属性
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
4 SurfaceView的简单使用
public class SurfaceViewDemo extends SurfaceView implements SurfaceHolder.Callback{ private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private Paint paint; public SurfaceViewDemo(Context context) { this(context,null,0); } public SurfaceViewDemo(Context context, AttributeSet attrs) { this(context, attrs,0); } public SurfaceViewDemo(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.RED); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); } @Override public void surfaceCreated(SurfaceHolder holder) { System.out.println("=========surfaceCreated========"); new Thread(new Runnable() { @Override public void run() { draw(); } }).start(); } private void draw() { try { System.out.println("============draw========"); mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawCircle(500,500,300,paint); mCanvas.drawCircle(100,100,20,paint); } catch (Exception e) { e.printStackTrace(); } finally { if (mCanvas != null) mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { System.out.println("=========surfaceChanged========"); } @Override public void surfaceDestroyed(SurfaceHolder holder) { System.out.println("=========surfaceDestroyed========"); }}
回调函数的调用:
首次进入:
=surfaceCreated
=surfaceChanged
====draw
点击Home键:
=surfaceDestroyed
再次回到原来页面:
=surfaceCreated
=surfaceChanged
====draw
所以当SurfaceView不可见时会销毁SurfaceHolder,再次进入会重新调用surfaceCreated生成新的SurfaceHolder。surfaceChanged函数在surfaceCreated调用后该函数至少会被调用一次。
SurfaceView播放视频,不要忘记存储权限
mediaPlayer.setDisplay(getHolder());
视频资源为利用模拟器录制的mp4视频
public class SurfaceViewDemo2 extends SurfaceView implements SurfaceHolder.Callback{ private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private Paint paint; private MediaPlayer mediaPlayer; public SurfaceViewDemo2(Context context) { this(context,null,0); } public SurfaceViewDemo2(Context context, AttributeSet attrs) { this(context, attrs,0); } public SurfaceViewDemo2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); setZOrderOnTop(true); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.RED); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); } @Override public void surfaceCreated(SurfaceHolder holder) { System.out.println("=========surfaceCreated========"); new Thread(new Runnable() { @Override public void run() { //draw(); play(); } }).start(); } private void draw() { try { System.out.println("============draw========"); mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawCircle(500,500,300,paint); mCanvas.drawCircle(100,100,20,paint); } catch (Exception e) { e.printStackTrace(); } finally { if (mCanvas != null) mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { System.out.println("=========surfaceChanged========"); } @Override public void surfaceDestroyed(SurfaceHolder holder) { System.out.println("=========surfaceDestroyed========"); if (mediaPlayer != null ){ stop(); } } protected void stop() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } } protected void play() { String path = "/sdcard/DCIM/Camera/VID_20190110_102218.mp4"; File file = new File(path); if (!file.exists()) { return; } try { mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置播放的视频源 mediaPlayer.setDataSource(file.getAbsolutePath()); // 设置显示视频的SurfaceHolder mediaPlayer.setDisplay(getHolder()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mediaPlayer.start(); } }); mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { replay(); } }); mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { play(); return false; } }); } catch (Exception e) { e.printStackTrace(); } } protected void replay() { if (mediaPlayer!=null){ mediaPlayer.start(); }else{ play(); } } protected void pause() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); }else{ mediaPlayer.start(); } }}
补充 对SurfaceView进行平移,旋转等操作
添加 mSurfaceView.scrollBy(10,10);
效果如下:完全没有效果
对包裹它的viewgroup添加缩放动画。
mContainer.animate().scaleX(0.4f).scaleY(0.7f);
此时拍摄出来的照片为:
拍摄出来的照片完全没有受缩放的影响。
---------以上为转载
为什么使用SurfaceView
SurfaceView的目的:提供了一个Surface,非UI(主)线程(即Render线程)通过此Surface可以把内容绘制到屏幕上。
我们知道View是通过刷新来重绘视图,系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。
另外,对一些游戏画面,或者摄像头,视频播放等,UI都比较复杂,要求能够进行高效的绘制,因此,他们的UI不适合在主线程中绘制。这时候就必须要给那些需要复杂而高效的UI视图生成一个独立的绘制表面Surface,并且使用独立的线程来绘制这些视图UI。
SurfaceView为什么可以直接子线程绘制
通常View更新的时候都会调用ViewRootImpl中的performXXX()方法,在该方法中会首先使用checkThread()检查是否当前更新位于主线线程;
SurfaceView提供了专门用于绘制的Surface,可以通过SurfaceView来控制Surface的格式和尺寸,SurfaceView更新就不需要考虑线程的问题,它既可以在子线程更新,也可以在主线程更新。
Surface的优缺点:
优点:Surface的渲染可以放到单独线程去做,渲染复杂的动画不会影响主线程的的响应。
缺点:因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。
TextureView
因为上面所说的 SurfaceView 不在主窗口中,它没法做动画没法使用一些 View 的特性方法,所以在 Android 4.0中引入了 TextureView,它是一个结合了 View 和 SurfaceTexture 的 View 对象。
TextureView 是一个可以把内容流作为外部纹理输出在上面的 View,和 SurfaceView 不同,它不会在 WMS 中单独创建窗口,而是作为 View hierachy 中的一个普通 view,因此它可以和其他普通 View 一样进行平移、旋转、缩放等动画。但是 TextureView 必须在硬件加速的窗口中,它显示的内容流数据可以来自 App 进程或者远程进程。
TextureView 继承自 View,它与其它的 View 一样在 View hierachy 中管理与绘制。TextureView 重载了 draw() 方法,其中主要 SurfaceTexture 中收到的图像数据作为纹理更新到对应的 HardwareLayer 中。
SurfaceTexture.OnFrameAvailableListener 用于通知 TextureView 内容流有新图像到来。SurfaceTextureListener 接口用于让 TextureView 的使用者知道 SurfaceTexture 已准备好,这样就可以把 SurfaceTexture 交给相应的内容源。
Surface 为 BufferQueue 的 Producer 接口实现类,使生产者可以通过它的软件或硬件渲染接口为 SurfaceTexture 内部的 BufferQueue 提供 graphic buffer。
SurfaceTexture 可以用作非直接输出的内容流,这样就提供二次处理的机会。与 SurfaceView 直接输出相比,这样会有若干帧的延迟。同时,由于它本身管理 BufferQueue,因此内存消耗也会稍微大一些。
与TextureView比较
TextureView优点:支持移动、旋转、缩放等动画,支持截图
TextureView缺点:必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。
TextureView和SurfaceView的优缺点汇总
SurfaceView | TextureView | |
内存 | 低 | 高 |
绘制效率 | 及时 | 1 ~ 3帧的延迟 |
耗电 | 低 | 高 |
截图 | 不支持 | 支持 |
动画 | 不支持 | 支持 |