SurfaceView浅析
什么是SurfaceView呢?
为什么是SurfaceView呢?Surface的意思是表层,表面的意思,那么SurfaceView就是指一个在表层的View对象。为什么 说是在表层呢,这是因为它有点特殊跟其他View不一样,其他View是绘制在表层外,而它就是充当表层对象。假设你要在一个球上画画,那么球的表层就当 做你的画布对象,你画的东西会挡住它的表层,我们默认没使用SurfaceView,那么球的表层就是空白的,如果我们使用了SurfaceView,我
们可以理解为我们拿来的球本身表面就具有纹路,你是画再纹路之上的,如果你画的是半透明的,那么你将可以透过你画的东西看到球面本身的纹路。SDK的文档 说到:SurfaceView就是在Window上挖一个洞,它就是显示在这个洞里,其他的View是显示在Window上,所以View可以显式在 SurfaceView之上,你也可以添加一些层在SurfaceView之上。
SurfaceView还有其他的特性,上面我们讲了它可以控制帧数,那它是什么控制的呢?这就需要了解它的使用机制。一般在很多游戏设计中,我们都是开辟一个后台线程计算游戏相关的数据,然后根据这些计算完的新数据再刷新视图对象,由于对View执行绘制操作只能在UI线程上,
所以当你在另外一个线程计算完数据后,你需要调用View.invalidate方法通知系统刷新View对象,所以游戏相关的数据也需要让UI线程能访 问到,这样的设计架构比较复杂,要是能让后台计算的线程能直接访问数据,然后更新View对象那改多好。我们知道View的更新只能在UI线程中,所以使 用自定义View没办法这么做,但是SurfaceView就可以了。它一个很好用的地方就是允许其他线程(不是UI线程)绘制图形(使用Canvas),根据它这个特性,你就可以控制它的帧数,你如果让这个线程1秒执行50次绘制,那么最后显示的就是50帧。
如何使用SurfaceView?
首先SurfaceView也是一个View,它也有自己的生命周期。因为它需要另外一个线程来执行绘制操作,所以我们可以在它生命周期的初始化阶 段开辟一个新线程,然后开始执行绘制,当生命周期的结束阶段我们插入结束绘制线程的操作。这些是由其内部一个SurfaceHolder对象完成的。 SurfaceHolder,顾名思义,它里面保存了一个队Surface对象的引用,而我们执行绘制方法就是操作这个
Surface,SurfaceHolder因为保存了对Surface的引用,所以使用它来处理Surface的生命周期,说到底 SurfaceView的生命周期其实就是Surface的生命周期,因为SurfaceHolder保存对Surface的引用,所以使用 SurfaceHolder来处理生命周期的初始化。首先我们先看看建立一个SurfaceView的大概步骤,先看看代码:
import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.view.View; public class AnimateViewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new AnimateView(this)); } class AnimateView extends View{ float radius = 10; Paint paint; public AnimateView(Context context) { super(context); paint = new Paint(); paint.setColor(Color.YELLOW); paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { canvas.translate(200, 200); canvas.drawCircle(0, 0, radius++, paint); if(radius > 100){ radius = 10; } invalidate();//通过调用这个方法让系统自动刷新视图 } } }
上面代码我们在SurfaceView的构造方法中执行了init初始化方法,在这个方法里,我们先获取SurfaceView里的 SurfaceHolder对象,然后通过它设置Surface的生命周期回调方法,使用DemoSurfaceView类本身作为回调方法代理类。 surfaceCreated方法,是当SurfaceView被显示时会调用的方法,所以你需要再这边开启绘制的线
程,surfaceDestroyed方法是当SurfaceView被隐藏会销毁时调用的方法,在这里你可以关闭绘制的线程。上面的例子运行后什么也不 显示,因为还没定义一个执行绘制的线程。下面我们修改下代码,使用一个线程绘制一个逐渐变大的圆圈:
import android.content.Context; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class DemoSurfaceView extends SurfaceView implements Callback{ public DemoSurfaceView(Context context) { super(context); init(); //初始化,设置生命周期回调方法 } private void init(){ SurfaceHolder holder = getHolder(); holder.addCallback(this); //设置Surface生命周期回调 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
上面代码编写了一个使用SurfaceView制作的动画效果,它的效果跟上面自定义View的一样,但是这边的SurfaceView可以控制动 画的帧数。在SurfaceView中内置一个LoopThread线程,这个线程的作用就是用来绘制图形,在SurfaceView中实例化一个 LoopThread实例,一般这个操作会放在SurfaceView的构造方法中。然后通过在SurfaceView中的SurfaceHolder的
生命周期回调方法中插入一些操作,当Surface被创建时(SurfaceView显示在屏幕中时),开启LoopThread执行绘 制,LoopThread会一直刷新SurfaceView对象,当SurfaceView被隐藏时就停止改线程释放资源。这边有几个地方要注意下:
1.因为SurfaceView允许自定义的线程操作Surface对象执行绘制方法,而你可能同时定义多个线程执行绘制,所以当你获取 SurfaceHolder中的Canvas对象时记得加同步操作,避免两个不同的线程同时操作同一个Canvas对象,当操作完成后记得调用 SurfaceHolder.unlockCanvasAndPost方法释放掉Canvas锁。
2.在调用doDraw执行绘制时,因为SurfaceView的特点,它会保留之前绘制的图形,所以你需要先清空掉上一次绘制时留下的图形。(View则不会,它默认在调用View.onDraw方法时就自动清空掉视图里的东西)。
3. 记得在回调方法:onSurfaceDestroyed方法里将后台执行绘制的LoopThread关闭,这里是使用join方法。这涉及到线程如何关闭 的问题,多数人建议是通过一个标志位:isRunning来判断线程是否该停止运行,如果你想关闭线程只需要将isRunning改成false即可,线
程会自动执行完run方法后退出。
总结:
通过上面的分析,现在大家应该会简单使用SurfaceView了,总的归纳起来SurfaceView和View不同之处有:
1. SurfaceView允许其他线程更新视图对象(执行绘制方法)而View不允许这么做,它只允许UI线程更新视图对象。
2. SurfaceView是放在其他最底层的视图层次中,所有其他视图层都在它上面,所以在它之上可以添加一些层,而且它不能是透明的。
3. 它执行动画的效率比View高,而且你可以控制帧数。
4. 因为它的定义和使用比View复杂,占用的资源也比较多,除非使用View不能完成,再用SurfaceView否则最好用View就可以。(贪吃蛇,俄罗斯方块,棋牌类这种帧数比较低的可以使用View做就好)
5. SurfaceView的挖洞过程
SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,也就是说,它的Z轴位置是小于其宿主窗口的Z位置的。为了保证SurfaceView的UI是可见的,SurfaceView就需要在其宿主窗口的上面挖一个洞出来,实际上就是在其宿主窗口的绘图表面上设置一块透明区域,以便可以将自己显示出来。
从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,会请求在宿主窗口上设置透明区域,而每当其宿主窗口刷新自己的UI的时候,就会将所有嵌入在它里面的SurfaceView所设置的透明区域收集起来,然后再通知WindowManagerService服务为其设置一个总的透明区域。
从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,SurfaceView类的成员函数onAttachedToWindow就会被调用。SurfaceView类的成员函数onAttachedToWindow在被调用的期间,就会请求在宿主窗口上设置透明区域。
结来说,就是SurfaceView有以下三个特点:
A. 具有独立的绘图表面;
B. 需要在宿主窗口上挖一个洞来显示自己;
C. 它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入。
什么是双缓冲?
不用画布,直接在窗口上进行绘图叫做无缓冲绘图。用了一个画布,将所有内容都先画到画布上,在整体绘制到窗口上,就该叫做单缓冲绘图,那个画布就是一个缓冲区。用了两个画布,一个进行临时的绘图,一个进行最终的绘图,这样就叫做双缓冲绘图。
surfaceView自身实现了双缓冲,而View没有。其实view你也可以自己实现,但是实现的结构不如surfaceView好。
surfaceView通过 surfaceHolder.lockCanvas 锁定画布,实现下一张图片的绘制,再通过另外的线程刷新界面,绘制图片。
view则是直接在ondraw里绘制图片,刷新界面。其实view也可以实现双缓冲机制,你可以在另个出ondraw的方法中绘制下一张bitmap(参见:http://blog.csdn.net/liubingzhao/article/details/5563113),也可以另开一个线程,处理除了绘制图片以外的操作(参见:http://topic.csdn.net/u/20110901/23/e283f805-20dc-40c3-8381-403dd1ca69b0.html),就实现了view的双缓冲。
为什么动态绘图surfaceView要比View好?
因为View是在UI主线程中进行绘制的,绘制时会阻塞主线程,如果ontouch事件又处理的比较多的话会导致界面卡。而surfaceView是另开了一个线程绘制的,再加上双缓冲机制,所以要高效。不会卡。其实现在一般实现view的时候一般都会在其他出先生成bitmap在给ondraw去画,所以双缓冲的作用不是那么明显了。
SurfaceView浅析的更多相关文章
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 高性能IO模型浅析
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- netty5 HTTP协议栈浅析与实践
一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler
熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...
- SurfaceView 绘制分形图
之前一直做的是应用类,这次抽时间,参考网上资料实践了下SurfaceView.目标是在页面上画一个科赫曲线的分形图. 代码如下: package com.example.fredric.demo02; ...
- 【深入浅出jQuery】源码浅析2--奇技淫巧
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 浅析匿名函数、lambda表达式、闭包(closure)区别与作用
浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...
随机推荐
- spark下使用submit提交任务后报jar包已存在错误
使用spark submit进行任务提交,离线跑数据,提交后的一段时间内可以application可以正常运行.过了一段时间后,就抛出以下错误: org.apache.spark.SparkExcep ...
- 全文检索 Lucene(3)
看完前两篇博客之后,想必大家对于Lucene的使用都有了一个比较清晰的认识了.如果对Lucene的知识点还是有点模糊的话,个人建议还是先看看这两篇文章. 全文检索 Lucene(1) 全文检索 Luc ...
- 全文检索Lucene (1)
Lucene是apache开源的一个全文检索框架,很是出名.今天先来分享一个类似于HelloWorld级别的使用. 工作流程 依赖 我们要想使用Lucene,那就得先引用人家的jar包了.下面列举一下 ...
- Python optparser库详解
一直以来对optparser不是特别的理解,今天就狠下心,静下心研究了一下这个库.当然了,不敢说理解的很到位,但是足以应付正常的使用了.废话不多说,开始今天的分享吧. 简介 optparse模块主要用 ...
- Dynamics CRM 自定义上传附件的图片悬浮层显示
CRM中的附件是以流的形式保存在了数据库中,这样做的一个坏处是一旦系统运行时间久,附件上传的多了势必会导致数据库极速扩大,即影响系统的运行效率也对后期的迁移维护带来了不必要的麻烦.所以很多的客户都会要 ...
- Spring Boot微服务架构入门
概述 还记得在10年毕业实习的时候,当时后台三大框架为主流的后台开发框架成软件行业的标杆,当时对于软件的认识也就是照猫画虎,对于为什么会有这么样的写法,以及这种框架的优势或劣势,是不清楚的,Sprin ...
- Dynamics CRM 2015Online Update1 new feature之表单页Tabs切换
CRM2011的界面相对于CRM4.0进行了比较大的改动,N久没见过4.0的界面了所以忘了表单是什么样子的了,但2011的表单中若含有多个tab的话,是可以通过左侧栏进行切换,话说2013的界面相对2 ...
- 初探linux子系统集之timer子系统(三)
因为现在的linux虽然还是可以使用低精度的timer,但是趋势是高精度hrtimer,所以上一篇试着翻译一下hrtimer的一些介绍,翻译的不是很好,看来英语还得好好学习啊,下面还是好好学习下lin ...
- J2EE进阶(六)SSH框架工作流程项目整合实例讲解
J2EE进阶(六)SSH框架工作流程项目整合实例讲解 请求流程 经过实际项目的进行,结合三大框架各自的运行机理可分析得出SSH整合框架的大致工作流程. 首先查看一下客户端的请求信息: 对于一个Web项 ...
- View绘制流程
1. View 树的绘图流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 dr ...