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);
    }

    @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销毁后继续运行,引发内存泄漏或异常。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/714507.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何充分利用 Postgres 的内存设置

为了充分利用 PostgreSQL 的内存设置,你需要调整多个参数以优化数据库性能。这些参数包括共享缓冲区(shared_buffers)、工作内存(work_mem)、维护工作内存(maintenance_work_mem)、有效缓存大小…

命令词:引导行动的语言工具

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…

《全职猎人》

《全职猎人》 [1-2]是日本漫画家富坚义博的作品。 1999年版改编电视动画由日本动画公司负责动画制作,于1999年10月16日-2001年3月30日在富士电视台播出,该动画的故事至贪婪之岛篇章结束,全92话。 该作在富坚义博老师天马行空的想…

markupsafe,一个神奇的 Python 库!

更多Python学习内容:ipengtao.com 大家好,今天为大家分享一个神奇的 Python 库 - markupsafe。 Github地址:https://github.com/pallets/markupsafe 在 Web 开发和模版渲染中,处理用户输入的数据时,防止 HTML 注入是至…

【Java】Object、Objects、包装类、StringBuilder、StringJoiner

目录 1.API2.Object类3.Objects类4.包装类4.1包装类概述4.2包装类的其他常见操作 5.StringBuilder 可变字符串5.1概述5.2StringBuilder案例 6.StringJoiner 1.API API:应用程序编程接口,全称application programing interface,即Java已经写好…

3分钟带手把手带你了解 FL Studio v21.2.3.4004 中文免费版(附中文设置教程)安装指南

3分钟带手把手带你了解 FL Studio v21.2.3.4004 中文免费版(附中文设置教程)安装指南,大家我是兔八哥爱分享,今天你带来的安装FL Studio 21破解版,纯正简体中文支持! FL Studio 21 简称FL21,全称Fruity Loops Studio&a…

消息队列-Rabbit运行机制

Producer(生产者) 和 Consumer(消费者) Producer(生产者) :生产消息的一方(邮件投递者)Consumer(消费者) :消费消息的一方(邮件收件人) 消息一般由 2 部分组成:消息头(或者说是标签 Label)和 …

keystone认证服务

keystone认证服务 1、keystone管理用户 1-1、简介: 在OpenStack云计算平台中,Keystone是一个核心组件,主要用于提供统一的认证服务。其功能包括: 身份验证:Keystone负责验证用户的身份,通常通过用户名和…

记录一个flink跑kafka connector遇到的问题

【报错】 D:\Java\jdk1.8.0_231\bin\java.exe "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2022.2.3\lib\idea_rt.jar56647:D:\Program Files\JetBrains\IntelliJ IDEA 2022.2.3\bin" -Dfile.encodingUTF-8 -classpath D:\Java\jdk1.8.0_231\jre\lib\cha…

本学期嵌入式期末考试的综合项目,我是这么出题的

时间过得真快,临近期末,又到了老师出卷的时候。作为《嵌入式开发及应用》这门课的主讲教师,今年给学生出的题目有一点点难度,最后的综合项目要求如下所示,各位学生朋友和教师同行可以评论一下难度如何,单片…

DataWhale - 吃瓜教程学习笔记(一)

学习视频:第1章-绪论_哔哩哔哩_bilibili 西瓜书对应章节: 第一章 机器学习三观 What:什么是机器学习? 关键词:“学习算法” Why: 为什么要学机器学习? #### 1. 机器学习理论研究#### 2. 机器学习系统开…

[240615] X-CMD 发布 v0.3.11,增加对 elvish 的支持

目录 X-CMD 发布 v0.3.11,增加对 elvish 的支持,并优化对 nushell,fish,xonsh,tcsh 的支持✨ co 模块 - copilot✨ elv 模块✨ hub X-CMD 发布 v0.3.11,增加对 elvish 的支持,并优化对 nushell&…

Python合并文件(dat、mdf、mf4)

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

LabVIEW与C#的区别及重新开发自动测试程序的可行性分析

LabVIEW和C#是两种广泛使用的编程语言,各自有不同的应用领域和特点。本文将详细比较LabVIEW与C#在自动测试程序开发中的区别,并分析将已完成的LabVIEW自动测试程序重新用C#开发的合理性。本文帮助评估这种转换的必要性和潜在影响。 LabVIEW与C#的区别 开…

怎么把三列数据相同的号码一起求和?

可以使用excel的合并计算功能。 一、合并计算 将三列求和的数字列标题改成相同的,示例中全改成B1,这个是使用合并计算的关键一步,不改列标题,计算结果会是分开的。 2. 然后选中任意空白单元格作为输入结果的起始位置,…

Python学习笔记11:入门终结篇

前言 入门知识到这里基本结束了,这里主要讲一下input和range。这两个讲完,讲讲后面进阶学些啥。 range函数 之前将循环的时候讲过一点,这个函数是Python内置的函数,主要用来生成一系列数字,简单方便。 这里重新&…

Java17 --- SpringSecurity之前后端分离处理

目录 一、实现前后端分离 1.1、导入pom依赖 1.2、认证成功处理 1.3、认证失败处理 1.4、用户注销处理 1.5、请求未认证处理 1.6、跨域处理 1.7、用户认证信息处理 1.8、会话并发处理 一、实现前后端分离 1.1、导入pom依赖 <dependency><groupId>co…

工程设计问题---多盘离合器制动器设计问题

这个问题的主要目的是使多片式离合器制动器的质量最小化。在这个问题中&#xff0c;使用了五个整数决策变量&#xff0c;它们是内半径&#xff08;x1&#xff09;、外半径&#xff08;x2&#xff09;、盘厚度&#xff08;x3&#xff09;、致动器的力&#xff08;x4&#xff09;…

QT属性系统,简单属性功能快速实现 QT属性的简单理解 属性学习如此简单 一文就能读懂QT属性 QT属性最简单的学习

4.4 属性系统 Qt 元对象系统最主要的功能是实现信号和槽机制&#xff0c;当然也有其他功能&#xff0c;就是支持属性系统。有些高级语言通过编译器的 __property 或者 [property] 等关键字实现属性系统&#xff0c;用于提供对成员变量的访问权限&#xff0c;Qt 则通过自己的元对…

mysql-connector下载教程(手把手)

下载一个第三方库主要有三种途径&#xff1a; 去官方网站 Oracle 官网去github去Maven中央仓库 前两个方法比较麻烦&#xff0c;你还需要去找。 这里就只介绍maven的方法 Maven类似于手机app的应用商店。 操作步骤&#xff1a; 点击右边进入官网Maven中央仓库 在搜索框中…