Android开发系列:高性能视图组件Surfaceview

2024-06-16 22:12

本文主要是介绍Android开发系列:高性能视图组件Surfaceview,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、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);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {drawThread = new DrawThread(holder);drawThread.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {// 重新配置绘图环境}@Overridepublic 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;}@Overridepublic 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销毁后继续运行,引发内存泄漏或异常。

这篇关于Android开发系列:高性能视图组件Surfaceview的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1067687

相关文章

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

四种Flutter子页面向父组件传递数据的方法介绍

《四种Flutter子页面向父组件传递数据的方法介绍》在Flutter中,如果父组件需要调用子组件的方法,可以通过常用的四种方式实现,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录方法 1:使用 GlobalKey 和 State 调用子组件方法方法 2:通过回调函数(Callb

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要