一、Surfaceview概述
在Android应用开发领域,面对视频播放、游戏构建及相机实时预览等高性能需求场景,直接操控图像数据并即时展示于屏幕成为必要条件。传统View组件在此类情境下显现局限性:
- 性能瓶颈:传统View的绘制任务由UI主线程承担,如果绘制操作过于复杂或需要频繁刷新,就可能导致主线程阻塞,进而影响界面的响应速度和用户交互体验。
- 视觉瑕疵:传统View组件缺乏双缓冲技术的支持,View直接屏幕绘制易引发画面闪烁及图像撕裂。
- 效果局限:传统View组件基于视图层次结构,每个View都被视为一个矩形区域,这使得实现不规则形状、透明度变化等复杂视觉效果变得相对困难。
鉴于上述挑战,Android引入了SurfaceView
作为解决方案。这一特殊视图组件具备独立的Surface层(图形缓冲区),实现内容的离线绘制,特点如下:
- 独立渲染:
SurfaceView
的Surface层独立于主UI线程,确保复杂绘图操作不干扰UI响应,提升应用流畅性。 - 双缓冲机制:通过双缓冲机制,有效缓解画面闪烁与撕裂现象,提升视觉呈现质量。
- 透明度支持:透明的Surface设计使其能无缝融入视图层级,支持与其他View叠加或裁剪,解锁复杂视觉效果的实现潜能。
- 支持OpenGL ES:SurfaceView支持OpenGL ES库,这意味着它可以实现2D和3D图形效果,对于游戏、视频等性能要求较高的应用非常有用。
总之,SurfaceView
凭借其独特的架构优势,优化了高性能应用场景下的渲染效率与用户体验,是处理视频播放、游戏动画及实时预览等需求的理想选择,成功绕过了常规View组件的诸多障碍。
二、工作原理
核心类:
- Surface:Surface创建了一个独立的绘图表面。这个独立的Surface允许SurfaceView在一个单独的线程中进行UI绘制,从而避免占用主线程资源,提高应用性能。
- SurfaceHolder:SurfaceHolder负责管理Surface 的生命周期,并提供了一系列回调方法,以便开发者获取 Surface 的状态变化。
- SurfaceView:SurfaceView 是一个View组件,它内部封装了一个SurfaceHolder,以便将 Surface 呈现到屏幕上。
SurfaceView、Surface、和SurfaceHolder之间的关系可以类比为MVC架构。其中,Model对应数据模型,对应Surface;View对应视图,对应SurfaceView;Controller对应控制器,对应SurfaceHolder。
在Android应用程序的Activity布局中,多种View组件相互嵌套,形成了一个层次分明的View hierarchy(视图层级结构)。这个结构的最顶端是DecorView,它是整个View树与Window Manager Service(WMS)之间的桥梁,WMS仅直接与这个根视图交互,并为之分配一个WindowState对象来管理视图的显示属性。同时,在SurfaceFlinger(SF)系统框架下,DecorView同样获得一个Layer,负责在屏幕上的最终合成与显示。
不同于普通View,SurfaceView内嵌了一个独立的Surface,这是一个用于直接绘图的缓冲区。这个Surface在WMS中同样注册了一个WindowState,意味着它能独立参与窗口管理,享有与DecorView类似的管理机制。并且,在SF中,SurfaceView的Surface也会被赋予一个单独的Layer,这样的设计允许SurfaceView在不干扰UI线程的情况下,由专有线程高效地进行图形更新,非常适合处理如视频播放或复杂动画等高负载、高频率刷新的场景,从而极大提升了图形渲染的效率和应用的流畅度。
SurfaceView像是在窗口上挖一个洞,它就是显示在这个洞里,传统的View是显示在窗口上。实际上,SurfaceView的Layer在Z轴上的位置小于其宿主Activity窗口的Layer,因此它默认是被遮挡的。但SurfaceView提供了一个透明区域,使得只有在这个区域内的内容才对用户可见。
详细的原理代码,可以移步:https://www.jianshu.com/p/5e5ae2f524ce
三、应用实践
1. 创建SurfaceView
在布局文件中添加SurfaceView,或者在代码中动态创建。
2. 获取SurfaceHolder,添加和实现Callback
创建一个类实现SurfaceHolder.Callback接口,以便监听SurfaceView的创建surfaceCreated、销毁surfaceDestroyed和状态改变surfaceChanged。
在你的SurfaceView实现类中,获取到SurfaceHolder实例,并使用addCallback方法添加之前创建的Callback实例。
3. 绘图
在绘图线程中,通过SurfaceHolder获取Canvas对象,并在其上绘制图形。
Canvas canvas= mSurfaceHolder.lockCanvas();
创建一个新的线程,在这个线程里不断地执行绘图逻辑。通常会在这个线程中锁定Canvas,然后进行绘制,最后解锁并提交绘制结果。
if (canvas != null){
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
4. 处理Surface变化
在surfaceChanged回调中处理Surface尺寸或格式的变化。
5. 清理资源
在surfaceDestroyed回调中,停止绘图线程并释放所有相关资源。
示例
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private DrawThread drawThread;
public MySurfaceView(Context context) {
super(context);
init();
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
holder = getHolder();
holder.addCallback(this);
// 设置SurfaceView的格式,使其支持透明度
holder.setFormat(PixelFormat.TRANSPARENT);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
drawThread = new DrawThread(holder);
drawThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 重新配置绘图环境
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
drawThread.running = false;
while (retry) {
try {
drawThread.join();
retry = false;
} catch (InterruptedException e) {
// nothing to do
}
}
}
class DrawThread extends Thread {
private SurfaceHolder mSurfaceHolder;
volatile boolean running = true;
public DrawThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
}
@Override
public void run() {
Canvas canvas;
while (running) {
canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
// 在这里执行具体的绘图操作
}
} finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
}
四、注意事项
-
线程同步问题:因为SurfaceView的绘图通常在单独的线程中进行,所以确保线程间的同步至关重要。不当的同步可能导致绘制错误、图像撕裂或应用崩溃。使用锁机制(如synchronized关键字)或条件变量来协调资源访问。
-
资源泄漏:忘记在surfaceDestroyed中正确清理资源,特别是线程和绘图相关的资源,会导致内存泄漏。确保在不再需要时及时停止和回收所有资源。
-
绘制效率:频繁的锁住和解锁Canvas会降低性能。尽量减少这些操作的次数,比如通过批量绘制或者优化绘图逻辑来提高效率。
-
屏幕旋转与配置变更:当设备旋转或系统配置变更时,SurfaceView可能会被销毁和重建。需要在onSaveInstanceState中保存必要的状态,并在onCreate或onRestoreInstanceState中恢复,确保平滑过渡。
-
触摸事件处理:默认情况下,触摸事件可能不会传递给SurfaceView。需要重写onTouchEvent方法,并可能需要调整视图的setZOrderOnTop属性,确保能够正确接收并处理触摸事件。
-
后台绘制与电池消耗:即使应用退至后台,如果绘图线程没有正确暂停,也可能持续消耗CPU和电池资源。确保在onPause和onResume中管理绘图线程的状态。
-
Surface生命周期管理:正确处理SurfaceHolder.Callback中的生命周期方法,尤其是surfaceCreated、surfaceChanged和surfaceDestroyed。例如,避免在surfaceDestroyed后继续尝试访问Surface。surfaceView可见时才被创建,隐藏时就被销毁。
-
硬件加速与兼容性:硬件加速可能与SurfaceView的某些特性不兼容,导致渲染问题。理解何时开启或关闭硬件加速,并测试不同设备和Android版本的兼容性。
-
内存管理:大图或过多的Bitmap操作可能导致OutOfMemoryError。合理使用Bitmap的采样策略、及时回收Bitmap对象,以及考虑使用更高效的图像加载库(如Glide或Picasso)。
-
与Activity/Fragment生命周期的协调:确保SurfaceView的生命周期与包含它的Activity或Fragment保持一致,避免在Activity或Fragment销毁后继续运行,引发内存泄漏或异常。