Android开发之播放器中涉及的Surface,SurfaceView,SurfaceHolder基础

一、什么是Surface

简单的说Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都要画在Surface的Canvas上(后面有原因解释)。传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行。

在SDK的文档中,对Surface的描述是这样的:“Handle onto a raw buffer that is being managed by the screen compositor”,翻译成中文就是“由屏幕显示内容合成器(screen compositor)所管理的原始缓冲区的句柄”,这句话包括下面两个意思:

1、通过Surface(因为Surface是句柄)就可以获得原生缓冲器以及其中的内容。就像在C++语言中,可以通过一个文件的句柄,就可以获得文件的内容一样。

2、 原始缓冲区(a raw buffer)是用于保存当前窗口的像素数据的。

引伸地,可以认为Android中的Surface就是一个用来画图形(graphics)或图像(image)的地方。

根据Java方面的常规知识,我们知道通常画图是在一个Canvas对象上面进行的,由此,可以推知一个Surface对象中应该包含有一个Canvas(画布)对象。因此,在前面提及的两个意思的基础上,可以再加上一条:

3、Surface中有一个Canvas成员,专门用于画图的。

由以上的概括,我们总结如下:Surface中的Canvas成员,是专门用于供程序员画图的场所,就像黑板一样;其中的原始缓冲区是用来保存数据的地方;Surface本身的作用类似一个句柄,得到了这个句柄就可以得到其中的Canvas、原始缓冲区以及其它方面的内容。

      Surface是用来管理数据的。(句柄)

二、什么是SurfaceView

说SurfaceView是一个View也许不够严谨,然而从定义中pubilc classSurfaceView extends View{.....}显示SurfaceView确实是派生自View,但是SurfaceView却有自己的Surface,请看SurfaceView的源码:

[java] view plaincopy

  1. if (mWindow == null) {
  2. mWindow = new MyWindow(this);
  3. mLayout.type = mWindowType;
  4. mLayout.gravity = Gravity.LEFT|Gravity.TOP;
  5. mSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
  6. mVisible ? VISIBLE : GONE, mContentInsets);
  7. }

很明显,每个SurfaceView创建的时候都会创建一个MyWindow,new MyWindow(this)中的this正是SurfaceView自身,因此将SurfaceView和window绑定在一起,由第一部分我们知道,一个window对应一个Surface,因此SurfaceView也就内嵌了一个自己的Surface,可以认为SurfaceView是用来控制Surface中View的位置和尺寸的。

         SurfaceView就是展示Surface中数据的地方,同时可以认为SurfaceView是用来控制Surface中View的位置和尺寸的。

大家都知道,传统View及其派生类的更新只能在UI线程,然而UI线程还同时处理其他交互逻辑,这就无法保证View更新的速度和帧率了,而SurfaceView可以用独立的线程进行绘制,因此可以提供更高的帧率,例如游戏,摄像头取景等场景就比较适合SurfaceView来实现。

三、什么是SurfaceHolder

SurfaceHolder是一个接口,其作用就像一个关于Surface的监听器,提供访问和控制SurfaceView内嵌的Surface 相关的方法。它通过三个回调方法,让我们可以感知到Surface的创建、销毁或者改变。

在SurfaceView中有一个方法getHolder,可以很方便地获得SurfaceView内嵌的Surface所对应的监听器接口SurfaceHolder。

除下面将要提到的SurfaceHolder.Callback外,SurfaceHolder还提供了很多重要的方法,其中最重要的就是:

1、abstract void addCallback(SurfaceHolder.Callbackcallback):为SurfaceHolder添加一个SurfaceHolder.Callback回调接口。

2、abstract Canvas lockCanvas():获取一个Canvas对象,并锁定之。所得到的Canvas对象,其实就是Surface中一个成员。

3、abstract Canvas lockCanvas(Rect  dirty):同上。但只锁定dirty所指定的矩形区域,因此效率更高。

4、abstract void unlockCanvasAndPost(Canvas  canvas):当修改Surface中的数据完成后,释放同步锁,并提交改变,然后将新的数据进行展示,同时Surface中相关数据会被丢失。

2、3、4中的同步锁机制的目的,就是为了在绘制的过程中,Surface中的数据不会被改变。lockCanvas是为了防止同一时刻多个线程对同一canvas写入。

总结:从设计模式的高度来看,Surface、SurfaceView和SurfaceHolder实质上就是广为人知的MVC,即Model-View-Controller。Model就是模型的意思,或者说是数据模型,或者更简单地说就是数据,也就是这里的Surface;View即视图,代表用户交互界面,也就是这里的SurfaceView;SurfaceHolder很明显可以理解为MVC中的Controller(控制器)。

四、什么是SurfaceHolder.Callback

SurfaceHolder.Callback主要是当底层的Surface被创建、销毁或者改变时提供回调通知,由于绘制必须在Surface被创建后才能进行,因此SurfaceHolder.Callback中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。

SurfaceHolder.Callback中定义了三个接口方法:

1、abstract void surfaceChanged(SurfaceHolder holder,
int format, int width, int height):当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用。

2、abstract void surfaceCreated(SurfaceHolder holder):当surface对象创建后,该方法就会被立即调用。

3、abstract void  surfaceDestroyed(SurfaceHolder holder):当surface对象在将要销毁前,该方法会被立即调用。

五、实例演示

下面,我们通过一个非常简单例子来实际感受一下,请留意代码中的注释:

1、在Eclipse中创建一个Android Project项目TestSurfaceView,并选择生成缺省的Activity TestSurfaceViewActivity

2、创建一个绘制线程如下:

[java] view plaincopy

  1. import android.graphics.Canvas;
  2. import android.graphics.Color;
  3. import android.graphics.Paint;
  4. import android.graphics.Rect;
  5. importandroid.view.SurfaceHolder;
  6. // 绘制线程
  7. public class MyThread extends Thread
  8. {
  9. private SurfaceHolder holder;
  10. private boolean run;
  11. public  MyThread(SurfaceHolder holder)
  12. {
  13. this.holder = holder;
  14. run = true;
  15. }
  16. @Override
  17. public void run()
  18. {
  19. int counter = 0;
  20. Canvas canvas = null;
  21. while(run)
  22. {
  23. // 具体绘制工作
  24. try
  25. {
  26. // 获取Canvas对象,并锁定之
  27. canvas= holder.lockCanvas();
  28. // 设定Canvas对象的背景颜色
  29. canvas.drawColor(Color.WHITE);
  30. // 创建画笔
  31. Paint p = new Paint();
  32. // 设置画笔颜色
  33. p.setColor(Color.BLACK);
  34. // 设置文字大小
  35. p.setTextSize(30);
  36. // 创建一个Rect对象rect
  37. Rect  rect = new Rect(100, 50, 380, 330);
  38. // 在canvas上绘制rect
  39. canvas.drawRect(rect,p);
  40. // 在canvas上显示时间
  41. canvas.drawText("Interval = " + (counter++) + " seconds.", 100, 410, p);
  42. Thread.sleep(1000);
  43. }
  44. catch(Exception e)
  45. {
  46. e.printStackTrace();
  47. }
  48. finally
  49. {
  50. if(canvas != null)
  51. {
  52. // 解除锁定,并提交修改内容
  53. holder.unlockCanvasAndPost(canvas);
  54. }
  55. }
  56. }
  57. }
  58. public boolean isRun()
  59. {
  60. return run;
  61. }
  62. public void setRun(boolean run)
  63. {
  64. this.run = run;
  65. }
  66. }

3、自定义一个SurfaceView类如下:

[java] view plaincopy

  1. import android.content.Context;
  2. import android.view.SurfaceHolder;
  3. import android.view.SurfaceView;
  4. public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback
  5. {
  6. private SurfaceHolder holder;
  7. private MyThread myThread;
  8. public  MySurfaceView(Context context)
  9. {
  10. super(context);
  11. // 通过SurfaceView获得SurfaceHolder对象
  12. holder = getHolder();
  13. // 为holder添加回调结构SurfaceHolder.Callback
  14. holder.addCallback(this);
  15. // 创建一个绘制线程,将holder对象作为参数传入,这样在绘制线程中就可以获得holder
  16. // 对象,进而在绘制线程中可以通过holder对象获得Canvas对象,并在Canvas上进行绘制
  17. myThread = new MyThread(holder);
  18. }
  19. // 实现SurfaceHolder.Callback接口中的三个方法,都是在主线程中调用,而不是在绘制线程中调用的
  20. @Override
  21. public void  surfaceChanged(SurfaceHolder holder, int format, int width, int height)
  22. {
  23. }
  24. @Override
  25. public void  surfaceCreated(SurfaceHolder holder)
  26. {
  27. // 启动线程。当这个方法调用时,说明Surface已经有效了
  28. myThread.setRun(true);
  29. myThread.start();
  30. }
  31. @Override
  32. public void surfaceDestroyed(SurfaceHolder holder)
  33. {
  34. // 结束线程。当这个方法调用时,说明Surface即将要被销毁了
  35. myThread.setRun(false);
  36. }
  37. }

4、修改TestSurfaceViewActivity.java代码,使之如下:

[java] view plaincopy

  1. import android.app.Activity;
  2. import android.os.Bundle;
  3. public class TestSurfaceViewActivity extends Activity
  4. {
  5. @Override
  6. public void onCreate(Bundle savedInstanceState)
  7. {
  8. super.onCreate(savedInstanceState);
  9. //setContentView(R.layout.main);
  10. setContentView(new MySurfaceView(this));
  11. }
  12. }

运行结果:

android view,surfaceview,glsurfaceview的区别:

答:SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView

SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。

那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。

当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。

所以基于以上,根据游戏特点,一般分成两类。

1)被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。

2)主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。

时间: 2024-10-09 09:11:55

Android开发之播放器中涉及的Surface,SurfaceView,SurfaceHolder基础的相关文章

Xamarin.Android开发音乐播放器

最近.Net开源着实让C#火了一把,好久就听说Mono for Android一直没静下心来看,上周末找来看看,确实不错,前台界面axml编写跟Java安卓开发毫无区别,后台用C#其实很多windows下的方法都可以用,做一个安卓音乐播放器,主要实现音乐播放,上一曲下一曲,音乐播放列表,随机播放,扫描SD卡内所有音乐. 先show一组界面啊,话说有图有真相啊: 项目结构如图: Lrc是歌词处理方法 MusicService是安卓服务播放音乐,服务Broadcast,以及界面接收Receiver

android 在音乐播放器中设置一首歌曲为来电铃声,设置不起作用

AudioProfile,情景模式,多卡设置铃声 系统开启Multi_ringtone_support.进入菜单--音乐--歌曲列表--长按一首歌曲选择设为手机铃声,提示已成功设置,但是进入设置中情景模式查看,仍然是默认铃声.打电话后铃声还为默认铃声. 修改: AudioProfileService.java mRingtoneObserver = new ContentObserver(new Handler())函数里面的 case GENERAL: case OUTDOOR: if (mE

android求助:关于播放器中的“播放/暂停”键: 用ImageButton来设置点击效果时的问题

============问题描述============ 大家好,我最近遇到个问题: 大家都知道,播放器中的播放/暂停键是这样的逻辑:当用户点击了这个键,如果当前显示的是"播放"图案,会变为"暂停"图案:反之,如果当前是"暂停"图案,会变为"播放"图案 , 如此反复... 现在我正在实现这个效果,我是这样做的: 在以上的基本效果的基础上,我还添加了点击过程中的效果,就是当点下还木有松开时,会有一个点击效果(比如给当前图片加个光

Android实现音乐播放器(一)

Graphical User Interface 本篇文章记录了我实现Android简单音乐播放器的过程,(一)中介绍了怎么构建音乐播放器的前端页面.首先大家看一下,界面最后是这样的(界面有点粗陋) 音乐文件列表是从SD卡中读取出来的. 首先我们先在Layout里面创建Music Player的GUI,diagram如下图所示: 根据diagram我们在activity_main.xml写出如下代码: <LinearLayout xmlns:android="http://schemas.

[Android应用]音乐播放器

Service是一个不可见的进程在后台执行. Android中的服务,它与Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序,如果我们退出应用时,Service进程并没有结束,它仍然在后台运行,例如我们打开一个音乐播放器来听音乐,在听音乐的同时也想做下其它的事情,比如上网聊Q.或者上网浏览新闻之类的事情.这样的话,我们就需要用到Service服务了.下面我们以一个简单的音乐播放器的实例来说明下Service的生命周期和Service的使用. 下面是音乐播放器Demo的程

最简单的基于FFmpeg的移动端样例附件:Android 自带播放器

===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:Android HelloWorld 最简单的基于FFmpeg的移动端样例:Android 视频解码器 最简单的基于FFmpeg的移动端样例:Android 视频解码器-单个库版 最简单的基于FFmpeg的移动端样例:Android 推流器 最简单的基于FFmpeg的移动端样例:Android 视频转

android调用音乐播放器,三种方

小弟想请问一下,如何在自己写的程序中调用系统的音乐播放器呢. 我在google上搜索了,主要是有两种方法,但是都不是我想要的. 第一种是,使用mp3音乐文件的uri,和intent,进行调用,但是这种是针对某一首歌曲的播放使用的. /** * 播放指定名称的歌曲 * @param audioPath 指定默认播放的音乐 */ public static void playAudio(String audioPath){ Intent mIntent = new Intent(); mIntent

【媒体应用:Android平台Vitamio播放器探索之路系列】之二:Vitamio API:核心类

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWells [媒体应用:Android平台Vitamio播放器探索之路系列]章节索引 [媒体应用:Android平台Vitamio播放器探索之路系列]之二:Vitamio API 注意:为了方便大家对照源码阅览,Vitamio API的介绍按照Vitamio源码程序包名来划分类别.如下图所示: 一 MediaPl

【媒体应用:Android平台Vitamio播放器探索之路系列】之一:Vitamio开篇

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWells [媒体应用:Android平台Vitamio播放器探索之路系列]章节索引 [媒体应用:Android平台Vitamio播放器探索之路系列]之一:Vitamio开篇 一 Vitamio简介 对于媒体播放器的开发而言,Android提供了内置的VideoView组件和MediaPlayer组件,但是功能和