摘要
本文分析的是API 24版本的SurfaceView(即Android N版本)通常情况下程序的View和用户响应都是在同一个线程(即主线程)中处理的,这就要求一些耗时操作(如网络请求、数据库查询)需要放在非UI线程中造作,否则就会造成ANR。View是通过刷新来重绘视图,系统通过发送VSSYNC信号来进行屏幕的重绘,这个信号的发送间隔是16ms,如果我们16ms内不能完成了绘制,就会表现出UI上的卡段。这就使得在一些逻辑很复杂或频繁更新的视图绘制方面显得比较吃力。那么到底能不能在非UI线程中进行绘制、更新、渲染UI的操作呢?怎么应对那种复杂层级或者频繁更新的UI绘制呢?关于这两个问题,接下来的SurfaceView会给我们答案。
SurfaceView在底层实现了游戏的双缓冲技术,这就可以很轻松的应对频繁更新这种场景下的视图绘制;其持有的Surface对象会开启一个独立线程去进行UI的渲染绘制,这就说明SurfaceView可以在非UI线程中进行UI的绘制操作。
与其说是SurfaceView可以在非UI线程中进行UI相关操作,不如说是其持有的surface对象提供了这种能力。SurfaceView的官方注释(我看的是API 24版本的)已将说明的很清楚了。
Provides a dedicated drawing surface embedded inside of a view hierarchy.
You can control the format of this surface and, if you like, its size; the
SurfaceView takes care of placing the surface at the correct location on the
screen
简单翻译一下就是:提供了一个嵌入在View层级中的专门用来绘图的Surface。你可以控制这个Surface的格式甚至是大小,而我们的SurfaceView只关心如何将这个Surface放在正确的位置。
特点
- Surface是Z序的,其持有者SurfaceView位于当前视图树上(即SurfaceView位于当前的Window上),那么Surface就位于当前的Window下面;按道理他应该是被覆盖的,但SurfaceView会在当前视图树所在的窗口上”挖个洞“让其显示出来。至于SurfaceView的兄弟View(即同层级的View),有View Hierarchy来控制他们的中确显示;
- SurfaceView上面的同层级的View如果应用了post-layout变换属性的话,会导致其不能和SurfaceView的surface很好的融合在一起;
- 可以通过SurfaceView的SurfaceHolder接口来获取surface(先调用getHolder()方法来获取SurfaceHolder的实现,在进行Surface相关操作);
- 当SurfaceView的Window可见时surface就被创建了。通过实现在SurfaceHolder.Callback的surfaceCreated和surfaceDestroyed来获取Surface的穿件与销毁时机;
- 由于surface的绘制发送在另一个线程,所以在使用的时候要注意以下几个方面的线程同步:
- 所有的SurfaceView和SurfaceHolder.Callback方法都是在应用主线程中被调用的,所以需要控制好线程间的状态同步;
- 只有在SurfaceHolder.Callback的surfaceCreated和surfaceDestroyed期间获取的surface才是合法的状态;
- 在Android N 之后(包括Android N),SurfaceView的Window的位置是随着其他视图的渲染而同步更新的,所以对SurfaceView应用动画时没啥影响的;但在这之前是有影响的,因为SurfaceView的位置是异步更新的。
具体分析
SurfaceView的构造同普通View的构造,没什么好分析的,主要看看它的以下关键成员变量和关键方法;
关键变量
1 | final ArrayList<SurfaceHolder.Callback> mCallbacks |
关键方法
updateSurface()
该方法主要就是更新surface,分析部分都在注释里
1 | protected void updateSurface() { |
从上面的代码可以看出,SurfaceHolder.Callback定义的方法都在updateSurface
中被调用。
具体使用
SurfaceView在Android上有两个系统实现,分别是GLSurfaceView和VideoView,前者专门负责OpenGL渲染的,后者负责视频渲染的。当然我们也可以自定义SurfaceView。
GLSurfaceView
GLSurfaceView 通常在游戏中使用,更详细的使用可以参见OpenGL 开发指南。
特点
管理一个surface对象,该surface是一块能被组装到Android视图系统的特殊内存;
管理一个EGL display对象,该对象可以把OpenGL内容渲染到surface中;
接收一个用户自定义的渲染器来完成真正的渲染工作;
在一个专门的线程中渲染内容
支持按序(on-demand)渲染和持续(continuous)渲染;
支持调试OpenGL的调用
基本使用
创建GLSurfaceView对象(没有特需要求的话,直接使用GLSurfaceView,然后通过其提供的set方法来配置GLSurfaceView,如setReanderer());
- 初始化GLSurfaceView,在调用setRenderer之前,先进行GLSurfaceView的配置,可参见其他set方法;
- 指定Surface,默认提供的是RGB_888格式的Surface,可以通过SurfaceHolder的setFormat来更改;
- 在开始渲染之前要指定EGLConfig(因为Android设备支持不同的渲染配置,且不同的配置表现不同);
- 指定调试模式,通过调用setDebugFlags来更改要调试的选项;
- 设置渲染器;
设置渲染模式,是按需渲染还是持续渲染;
生命周期相关
在Activity stops时请调用GLSurfaceView的onPause,在Activity starts时请调用GLSurfaceView的onResume,这样GLSurfaceView就能正确的通知渲染线程的工作状态,同时更好的管理OpenGL display这个对象。
事件处理
可以采用经典的时间处理模式来处理(即继承重写相关方法),但要利用线程同步机制来处理好UI线程(姑且说是主线程)和渲染线程(暂时称作子线程)之间的通信。
VideoView
用来展示视频的一个SurfaceView的实现,之所以姚勇SurfaceView,是因为视频流在短时间内存在复杂的渲染计算,SurfaceView很适合干这个工作。
VideoView内部持有一个MediaPlayer,同时实现了MediaPlayerControl接口,可以提供基本的视频播放控制操作(如播放、暂停、拖动等)
使用VideoView时应注意:只要处理好VideoView的测量工作,它可以被使用在任意的布局管理器中,并且提供了不同的展示模式;
VideoView进入后台后并不会保存它的全部状态,如播放状态、播放位置、播放轨道,需要我们在Activity的onSaveInstanceState和onRestoreInstanceState来手动管理;
自定义SurfaceView
要自定义SurfaceView,需要满足以下条件:
- 继承SurfaceView
- 实现SurfaceHolder中的Callback接口;
注意事项
- 在单个页面中不宜有过多SurfaceView实例,毕竟单独开始一个线程去完成绘制操作时非常消耗系统资源的;
- 一定要注意访问SurfaceView的surface时机的合法性(即在SurfaceHolder.Callback的surfaceCreated和surfaceDestroyed之间);