简述SurfaceView及常见问题

  在Android开发中,SurfaceView平常并不常用,但是遇到一些视频播放或者拍照等情况,就需要用到。下面对该控件进行简单的介绍,并列举出使用过程中遇到的问题进行QA形式的解答!

声明:  关于SurfaceView的原理的介绍主要参考文章:https://blog.csdn.net/luoshengyang/article/details/8661317

一、运用场景:

  普通的Android控件,它们的UI都是在应用程序的主线程中进行绘制的。而应用程序除了绘制外,还需要及时响应用户输入,否则,会引起ANR。为了避免ANR,对于一些游戏画面,摄像预览、视频播放等UI比较复杂,而且要求能够进行高效绘制的视图,它们的UI就不适合在应用程序的主线程中进行绘制。这时需要给此类视图生成一个独立的绘图表面,并使用一个独立的线程来绘制这些视图的UI,此时就必须使用SurfaceView进行开发。

二、SurfaceView是什么?

1、定义:

  继承自View,该类内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。SurfaceView控制这个Surface的绘制位置。

  Surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。Surfaceview提供了一个可见区域,只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。Surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面 有透明控件,那么它的每次变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。

2、负责绘制UI的SurfaceFlinger:

  讲到Surface,在此需要简要介绍一下SurfaceFlinger,一个负责绘制Android应用程序UI的服务。SurfaceFlinger服务运行在Android系统的System进程中,它负责管理Android系统的帧缓冲区(Frame Buffer)。Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,就必须要与SurfaceFlinger服务进行通信.具体流程概括为:

    (1) Android应用程序请求SurfaceFlinger服务创建Surface;

    (2) Surface创建后,Android应用程序在上面绘制自己的UI;

     (3) 再请求SurfaceFlinger服务将已经绘制好UI的Surface渲染到设备显示屏上。

    

  一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。

  由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面 可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。

3、单独介绍SurfaceView的一个成员变量:(仅做了解)

  SurfaceView类的成员变量mRequestedType描述的是SurfaceView的绘图表面的类型,有以下四个值:摘自源码。  

SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
SURFACE_TYPE_GPU:适用于GPU加速的Surface
SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。

  需要说明的是,虽然SurfaceHolder中已经不建议使用setType()方法了,我们自己写demo也可以看到该方法已经被声明为@Deprecated

    /**
     * Sets the surface‘s type.
     *
     * @deprecated this is ignored, this value is set automatically when needed.
     */
    @Deprecated
    public void setType(int type);

  但是,在使用自定义的SurfaceView实现视频播放时,还为了兼容低版本,仍需要设置setType:

  //设置SurfaceHolder类型,为了兼容低版本,需要设置此类型,否则播放时,只有声音,而没有图像
    getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

4、关于View的setWillNotDraw():

  查看SurfaceView的源码可知,在其构造函数中调用了setWillNotDraw(true);该方法会导致draw()、onDraw()都不执行。

 /**
     * If this view doesn‘t do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

  自定义SurfaceView,运行结果如下:

(1)默认执行结果:

(2)在CommSurfaceView的构造函数中,手动设置setWillNotDraw(false),运行结果如下:

三、SurfaceView的实现过程:(详情请参看原文)

  SurfaceView的整个实现过程分为三步:绘图表面的创建、在宿主窗口设置一块透明区域用于显示、在独立线程中进行自己UI的绘制。

  1.、创建独立的绘图表面;

  2、在宿主窗口上设置一块透明区域来显示自己(使绘制的UI可见);【SurfaceView的窗口类型所表示的Z轴位置小于Activity窗口的Z轴位置】

  3、在独立的线程中进行其UI绘制。

四、SurfaceView的使用:

  由于SurfaceView的底层实现过程已经进行了封装,并为开发者提供了上层使用接口,即:系统给SurfaceView提供了一个专门绘图的Surface,嵌入在了SurfaceView视图层中,画面在Surface中绘制完成,在SurfaceView中通过获得SurfaceHolder的对象,管理并展示Surface的数据内容。所以,开发时只需按照下述步骤即可:  

1、一般都是重新自定义SurfaceView,起到封装的作用:

(1)继承SurfaceView;

(2)利用getHolder()获取SurfaceHolder对象;

(3)给SurfaceHolder对象添加实现SurfaceHolder.Callback接口的对象,即添加回调函数SurfaceHolder.addCallback(callback);

(4)重写Callback的三个方法:surfaceChanged,surfaceCreated,surfaceDestroyed;

(5)利用SurfaceHolder对象设置其类型、格式、大小等。

  (一)在SurfaceView上进行UI绘制的流程如下:  

(1). 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。

(2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。

(3). 将已经填充好UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去。

  代码格式:
  Canvas c=surfaceHolder.lockCanvas();
  .....具体画图操作......
  surfaceHolder.unlockCanvasAndPost(c);

  (二)利用SurfaceView播放视频:

    Android中有三种实现形式,均可实现视频的播放:

    • 原生VideoView(继承SurfaceView)
    • 直接使用SurfaceView
    • 自定义播放控件(继承SurfaceView)

具体视频的播放均是使用的MediaPlayer,只是在自定义及VideoView中,被封装在各自的类中,对调用者不可见。

视频的画面一般由两部分组成:视频内容画面+上层操作布局(标题、播放进度、时长、暂停等控件)。

为了配合上层操作布局,一般都会实现MediaPlayerControl接口(当然,完全可以自定义),获取视频播放相关信息。

    以原生VideoView为例,展现代码格式:
    VideoView  nativeVideoView = (VideoView) findViewById(R.id.nativeVideoView);
    MediaController  nativeMediaController = new MediaController(this);
     //设置播放路径:实际内部封装了MediaPlayer的创建及数据源、监听事件、播放类型、画面显示等的设置,即视频播放前的准备工作,     //其中 mMediaPlayer.setDisplay(mSurfaceHolder);即是画面相关的设置
    nativeVideoView.setVideoPath(path);   

    nativeVideoView.setMediaController(nativeMediaController);
    nativeVideoView.requestFocus();
    nativeVideoView.start();//内部执行mediaPlayer.start()

五、常见问题:(以VideoView为例)

  1、在播放视频时,滑动进度,会出现闪屏(画面一直在变)?

  原因:原生VideoView使用SeekBar,在滑动进度的过程中,在OnSeekBarChangeListener的onProgressChanged()中调用了MediaPlayerControl的seekTo(),即实时更新视频画面,出现闪屏。

  

 1 源码:frameworks\base\core\java\android\widget\MediaController.java
 2
 3     private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
 4         @Override
 5         public void onStartTrackingTouch(SeekBar bar) {
 6             show(3600000);
 7
 8             mDragging = true;
 9
10             // By removing these pending progress messages we make sure
11             // that a) we won‘t update the progress while the user adjusts
12             // the seekbar and b) once the user is done dragging the thumb
13             // we will post one of these messages to the queue again and
14             // this ensures that there will be exactly one message queued up.
15             removeCallbacks(mShowProgress);
16         }
17
18          @Override
19         public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
20             if (!fromuser) {
21                 // We‘re not interested in programmatically generated changes to
22                 // the progress bar‘s position.
23                 return;
24             }
25
26             long duration = mPlayer.getDuration();
27             long newposition = (duration * progress) / 1000L;
28             mPlayer.seekTo( (int) newposition);   //实时更新播放画面
29             if (mCurrentTime != null)
30                 mCurrentTime.setText(stringForTime( (int) newposition));
31         }
32
33         @Override
34         public void onStopTrackingTouch(SeekBar bar) {
35             mDragging = false;
36             setProgress();
37             updatePausePlay();
38             show(sDefaultTimeout);
39
40             // Ensure that progress is properly updated in the future,
41             // the call to show() does not guarantee this because it is a
42             // no-op if we are already showing.
43             post(mShowProgress);
44         }
45     };    

  处理:解决此问题,可将视频画面的更新放在滑动结束(即onStopTrackingTouch()中)即可。

  引入新的问题:若用户看视频,需要从30分钟开始播放,但是具体位置不确定,因此需要在30分钟附近查看,此时则需要随着用户的滑动,画面切换,
但是,又不能出现上述闪屏问题。

  新问题的解决方案:可以监听两次滑动的时间间隔,然后画面更新(例:两次间隔500ms更新一次画面)

  测试结果:证实可以。具体为在滑动过程中即onProgressChanged()中进行如下操作:  

//startTouchTime:首次为拖动开始的时间,之后为每次更新时重新赋值更新时的时间
//changeTouchTime:时刻获取滑动变化的时间

            if(mDragging && changeTouchTime - startTouchTime >= 500){
                Log.e(TAG, "prepare fromuser onProgressChanged ++++++++++++++  ");
                startTouchTime = changeTouchTime;                    //重置,最近一次修改
                mPlayer.seekTo(newPosition);
                if (currentTime != null) {
                    currentTime.setText(stringForTime(newPosition));
                }
            }

  2、横竖屏切换时,视频从头播放,不会连续。

  原因:默认情况下, Activity在横竖屏切换时会重新创建,因此视频重播.结合是否设置属性对Activity的生命周期的影响,即可明白。

  解决方案:横竖屏切换必须在AndroidManifest.xml中设置属性android:configChanges="screenSize|orientation"。

  3、播放完成,停止时,播放进度未显示在最后?(该问题是我的项目中出现的,并非通用问题)

  现象:由于视频播放完成,做退出处理,一般看不到此现象。  

  原因:根据视频“剪辑”时,进度可以完全显示。查找原因,发现剪辑视频时,整个过程中进度条可见,一直在实时更新界面进度,因此可完全显示;而普通播放视频时,进度条等播放布局是可隐藏的,而代码中在MoviePlayer的setProgress()中,判断当进度等布局不可见时,直接返回,而不更新界面显示进度,因此,在播放完成时,界面显示的是最近一次布局可见时的进度(一般不在最后)。

  4、视频播放过程中,操作暂停播放后点击Home键退回桌面,再次进入会出现黑屏?【注:播放情况下正常是由于播放再次进入执行了继续播放,即刷新了界面】  

  原因:点击Home键时,SurfaceView中的Surface被销毁(从执行回调函数surfaceDestroy()即可看出),播放画面显示为黑色。

  测试现象:

      (1) 看到黑屏效果,是因为Activity背景被设置为黑色,在去掉背景色之后,发现仅有播放视频区域显示黑色;

      (2)将窗体设置为白色,按上述操作,发现仅视频播放区域为黑色;将主题设置为android:Theme.Translucent,按上述操作,发现仅视频播放区域为透明,即可看见MainActivity。这也可以解释SurfaceView的实现原理中的第二条,在宿主窗口中设置一块透明区域显示自己。运行效果图如下:MainActivity界面(左侧)、右侧为视频播放界面。

        

  

  得出结论:Surface被销毁后,播放区域显示的背景为当前主题Theme中的配置。

  5、其他问题:SurfaceView、GLSurfaceView、SurfaceTexture、TextureView的区别:

  SurfaceView:Android1.0就有,继承自View,因为有单独的Surface【不在View hierachy】中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。

  GLSurfaceView:Android1.5引入,继承自SurfaceView,在SurfaceView的基础上,它加入了EGL的管理,并自带了渲染线程。另外它定义了用户需要实现的Render接口,提供了用Strategy pattern更改具体Render行为的灵活性。作为GLSurfaceView的Client,只需要将实现了渲染函数的Renderer的实现类设置给GLSurfaceView即可。

  SurfaceTexture:Android 3.0(API level 11)加入。单独的类,和SurfaceView不同的是,它对图像流的处理并不直接显示,而是转为GL外部纹理,因此可用于图像流数据的二次处理(如Camera滤镜,桌面特效等)。比如Camera的预览数据,变成纹理后可以交给GLSurfaceView直接显示,也可以通过SurfaceTexture交给TextureView作为View heirachy中的一个硬件加速层来显示。

  TextureView:在4.0(API level 14)中引入。继承自View,它可以将内容流直接投影到View中,可以用于实现Live preview【实时预览】等功能.和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。它显示的内容流数据可以来自App进程或是远端进程。

六、总结:

  SurfaceView是android系统中特殊的视图,继承自View。它具有独立的绘图表面,但是,由于其窗口类型所表示的Z轴位置小于Activity窗口的Z轴位置,因此,为了使其可见,需要在宿主窗口申请设置一块透明区域来显示。一切条件就绪后,就可以在独立的线程中进行复杂的UI绘制,且不会影响应用程序的主线程响应用户输入。

  

原文地址:https://www.cnblogs.com/sparrowlhl/p/11227135.html

时间: 2024-08-30 17:47:34

简述SurfaceView及常见问题的相关文章

LoadRunner常见问题整理(转)

首先要感谢群友的无私分享,才能得到这篇好的学习资料,整理得太好了,所以收藏保存,方便以后学习. 一:LoadRunner常见问题整理 1.LR 脚本为空的解决方法: 1.去掉ie设置中的第三方支持取消掉 2.在系统属性-高级-性能-数据执行保护中,添加loadrunner安装目录中的vugen.exe文件. 有可能是由于录制的URL地址采用的是localhost的问题,改成分配的IP地址或127.0.0.1试试. 3.插入文本检查点步骤时,使用web_reg_find,通常TextPfx和Tex

SurfaceView部分译

SurfaceView 一.简述 The SurfaceView is a special subclass of View that offers a dedicated drawing surface within the View hierarchy. The aim is to offer this drawing surface to an application's secondary thread, so that the application isn't required to

【读书笔记《Android游戏编程之从零开始》】17.游戏开发基础(游戏适屏的简述和作用、让游戏主角动起来)

1.游戏适屏的简述和作用 由于市面上安装 Android 系统的手机不断增多,出现了各种分辨率.各种屏幕尺寸的Android 系统手机.为了保证一个游戏或者一个软件能在所有的 Android 手机上正常显示,常用的适屏做法有:利用屏幕宽高.位图宽高来设置一些游戏元素的位置:字体的适屏做法最好的使用字体图,这样文字不会因为手机分辨率不同而不同,毕竟图片大小是固定不变的. 2.让游戏主角动起来实例演示将一张由多行多列的动作帧组成的图片实现动态效果. 新建项目,游戏框架为SurfaceView 框架,

将标准demo视频部分代码移植到工程中常见问题和解决方案

近日,有很多客户反馈将标准android demo的VideoActivity视频部分代码移植到自己的工程中遇到本地视频黑屏或者远程视频显示不了的问题,这里对这些问题做汇总说明,并给出解决方案. 1.本地视频黑屏.不显示问题可能原因:没有设置音视频参数,没有使用Java采集模式解决方法:将标准demo里面hallactivity类中的ApplyVideoConfig函数移植到工程中,在初始化SDK之后调用,如下面所示 //初始化SDK anychat.InitSDK(android.os.Bui

再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

Angular 的数据绑定采用什么机制,详述原理? 脏检查机制.阐释脏检查机制,必须先了解如下问题. 单向绑定(ng-bind) 和 双向绑定(ng-model) 的区别? ng-bind 单向数据绑定($scope -> view),用于数据显示,简写形式是 {{}}. 两者的区别在于页面没有加载完毕 {{val}} 会直接显示到页面,直到 Angular 渲染该绑定数据(这种行为有可能将 {{val}} 让用户看到):而 ng-bind 则是在 Angular 渲染完毕后将数据显示. ng-

Hibernate简述及入门实例

一.Hibernate简述 总的概括,Hibernate是一个ORM的轻量级持久层框架,解决了对象和关系数据库中表的不匹配问题(阻抗不匹配)以及拥有开发代码不用去继承hibernate类或接口的优势(无侵入性).hibernate框架实现使得开发人员可以避免反复地编写javajdbc部分代码,应用面向对象的思维操作关系型数据库. 二.使用myeclipse创建hibernate实例两种方法(以hibernate3.5.2及mysql为例) a)手动编写hibernate.cfg.xml及*.hb

PHP常见问题及解答

当作PHP学习时,总是会在baidu上查很多的例如开发环境的选择呀,PHP好不好呀!或者是不是转学JAVA,或是.NET等: 首先本人是从2010年下半年开始报名学的PHP(IN Guangzhou),每周一天学了近6个月左右,从最基础的HTML,CSS,DIV,JAVASCRIPT,AJAX,PHP,然后学二次开发:闲暇之余还开通了一个个人blog( PHP wordpress); 由于个人工作原因,这几年放了一段时间未动PHP了,今年开始又自学了.NET; ---目的就想业余做一份兼职,锻炼

微信JS-SDK说明文档及常见问题处理

概述 微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包. 通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统的能力,同时可以直接使用微信分享.扫一扫.卡券.支付等微信特有的能力,为微信用户提供更优质的网页体验. 此文档面向网页开发者介绍微信JS-SDK如何使用及相关注意事项. 使用说明 在使用微信JS-SDK对应的JS接口前,需确保公众号已获得使用对应JS接口的权限,可登录微信公众平台进入“开发者中心”查看对应的接口权限. 注意:

基于android平台的模拟血压计实现(surfaceView的熟练使用)

这个是我根据上一篇文章的温度计改的血压计,因为客户对温度计还有血压计的需求是一样的,所以,我就选择了偷懒,直接用温度计的代码改了一概,就成了血压计的了 1 package com.example.test; 2 3 4 5 import android.content.Context; 6 import android.graphics.Bitmap; 7 import android.graphics.Canvas; 8 import android.graphics.Color; 9 imp