zoukankan      html  css  js  c++  java
  • SurfaceView

      在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。在本文中,我们就详细分析SurfaceView的实现原理。

    android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            在前面Android控件TextView的实现原理分析一文中提到,普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这意味着它们的UI是在应用程序的主线程中进行绘制的。由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则的话,系统就会认为应用程序没有响应了,因此就会弹出一个ANR对话框出来。对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制,因此,它们的UI就不适合在应用程序的主线程中进行绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI。

            在前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系统的文章中,我们主要分析了Android应用程序窗口是如何通过SurfaceFlinger服务来绘制自己的UI的。一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。

            无论是LayerBuffer,还是Layer,它们都是以LayerBase为基类的,也就是说,SurfaceFlinger服务把所有的LayerBuffer和Layer都抽象为LayerBase,因此就可以用统一的流程来绘制和合成它们的UI。由于LayerBuffer的绘制和合成与Layer的绘制和合成是类似的,因此本文不打算对LayerBuffer的绘制和合成操作进行分析。需要深入理解LayerBuffer的绘制和合成操作的,可以参考Android应用程序与SurfaceFlinger服务的关系概述和学习计划Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系统的文章。

            为了接下来可以方便地描述SurfaceView的实现原理分析,我们假设在一个Activity窗口的视图结构中,除了有一个DecorView顶层视图之外,还有两个TextView控件,以及一个SurfaceView视图,这样该Activity窗口在SurfaceFlinger服务中就对应有两个Layer或者一个Layer的一个LayerBuffer,如图1所示:

    图1 SurfaceView及其宿主Activity窗口的绘图表面示意图

             在图1中,Activity窗口的顶层视图DecorView及其两个TextView控件的UI都是绘制在SurfaceFlinger服务中的同一个Layer上面的,而SurfaceView的UI是绘制在SurfaceFlinger服务中的另外一个Layer或者LayerBuffer上的。

             注意,用来描述SurfaceView的Layer或者LayerBuffer的Z轴位置是小于用来其宿主Activity窗口的Layer的Z轴位置的,但是前者会在后者的上面挖一个“洞”出来,以便它的UI可以对用户可见。实际上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不过是在其宿主Activity窗口上设置了一块透明区域。

            从总体上描述了SurfaceView的大致实现原理之后,接下来我们就详细分析它的具体实现过程,包括它的绘图表面的创建过程、在宿主窗口上面进行挖洞的过程,以及绘制过程。

            1. SurfaceView的绘图表面的创建过程

            由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们首先要将它的绘图表面创建出来。尽管SurfaceView不与它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图结构的一个结点的,也就是说,SurfaceView仍然是会参与到宿主窗口的某些执行流程中去。

            从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文可以知道,每当一个窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals。ViewRoot类的成员函数performTraversals在执行的过程中,如果发现当前窗口的绘图表面还没有创建,或者发现当前窗口的绘图表面已经失效了,那么就会请求WindowManagerService服务创建一个新的绘图表面,同时,它还会通过一系列的回调函数来让嵌入在窗口里面的SurfaceView有机会创建自己的绘图表面。

    一、Surface
    
       Surface在SDK的文档中的描述是这样的:Handle onto a raw buffer that is being managed by the screen compositor,Android中的Surface就是一个用来画图形(graphics)或图像(image)的地方,对于View及其子类,都是画在Surface上,各Surface对象通过Surfaceflinger合成到frameBuffer,每个Surface都是双缓冲,它有一个backBuffer和一个frontBuffer,Surface中创建了Canvas对象,用来管理Surface绘图操作,Canvas对应Bitmap,存储Surface中的内容。流程为:
    
       1:创建一个Bitmap对象。
    
         2:创建一个Canvas对象关联创建的Bitmap对象。
    
         3:在Canvas上进行绘制。
    
         4:锁定Canvas画布。
    
         5:将Bitmap内容绘制到backBuffer中去。
    
         6:解锁Canvas画布。
    二、SurfaceView
    
       SurfaceView是视图类View的子类,且实现了Parcelable接口且实现了Parcelable接口,其中内嵌了一个专门用于绘制的Surface,SurfaceView可以控制这个Surface的格式和尺寸,以及Surface的绘制位置。可以理解为Surface就是管理数据的地方,SurfaceView就是展示数据的地方。
    三、SurfaceHolder
    
       SurfaceHolder是一个接口,类似于一个surace的监听器。通过下面三个回调方法监听Surface的创建、销毁或者改变。
    
        SurfaceView中调用getHolder方法,可以获得当前SurfaceView中的surface对应的SurfaceHolder,SurfaceHolder中重要的方法有:
    
        1: abstract  void addCallback(SurfaceHolder.Callback callback );为SurfaceHolder添加一个SurfaceHolder.Callback回调接口。
    
       2:  abstract  Canvas lockCanvas() ;获取Surface中的Canvas对象,并锁定之。所得到的Canvas对象。
    
        3:abstract  void unlockCanvasAndPost(Canvas canvas);当修改Surface中的数据完成后,释放同步锁,并提交改变,然后将新的数据进行展示。
    四、SurfaceHolder.Callback
    
       SurfaceHolder.Callback是SurfaceHolder接口内部的静态子接口,SurfaceHolder.Callback中定义了三个接口方法:
       1:public void sufaceChanged(SurfaceHolder holder,int format,int width,int height){}//Surface的大小发生改变时调用。
       2: public void surfaceCreated(SurfaceHolder holder){}//Surface创建时激发,一般在这里调用画面的线程。
       3: public void surfaceDestroyed(SurfaceHolder holder){}//销毁时激发,一般在这里将画面的线程停止、释放。
    
        SurfaceView和View最本质的区别在于:SurfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。下面是SurfaceView的例子:
    
    [java] view plain copy
    
        import android.content.Context;  
        import android.graphics.Bitmap;  
        import android.graphics.Canvas;  
        import android.graphics.Color;  
        import android.graphics.Paint;  
        import android.graphics.Paint.Style;  
        import android.graphics.drawable.BitmapDrawable;  
        import android.view.SurfaceHolder;  
        import android.view.SurfaceView;  
        import android.view.KeyEvent;  
        import android.view.MotionEvent;  
        import android.view.SurfaceHolder.Callback;  
          
        public class MySurfaceView extends SurfaceView implements Runnable, Callback {  
            private SurfaceHolder mHolder; // 用于控制SurfaceView  
            private Thread t; // 声明一条线程  
            private volatile boolean flag; // 线程运行的标识,用于控制线程  
            private Canvas mCanvas; // 声明一张画布  
            private Paint p; // 声明一支画笔  
            float m_circle_r = 10;  
          
            public MySurfaceView(Context context) {  
                super(context);  
          
                mHolder = getHolder(); // 获得SurfaceHolder对象  
                mHolder.addCallback(this); // 为SurfaceView添加状态监听  
                p = new Paint(); // 创建一个画笔对象  
                p.setColor(Color.WHITE); // 设置画笔的颜色为白色  
                setFocusable(true); // 设置焦点  
            }  
          
            /** 
             * 当SurfaceView创建的时候,调用此函数 
             */  
            @Override  
            public void surfaceCreated(SurfaceHolder holder) {  
                t = new Thread(this); // 创建一个线程对象  
                flag = true; // 把线程运行的标识设置成true  
                t.start(); // 启动线程  
            }  
          
            /** 
             * 当SurfaceView的视图发生改变的时候,调用此函数 
             */  
            @Override  
            public void surfaceChanged(SurfaceHolder holder, int format, int width,  
                    int height) {  
            }  
          
            /** 
             * 当SurfaceView销毁的时候,调用此函数 
             */  
            @Override  
            public void surfaceDestroyed(SurfaceHolder holder) {  
                flag = false; // 把线程运行的标识设置成false  
                mHolder.removeCallback(this);  
            }  
          
            /** 
             * 当屏幕被触摸时调用 
             */  
            @Override  
            public boolean onTouchEvent(MotionEvent event) {  
          
                return true;  
            }  
          
            /** 
             * 当用户按键时调用 
             */  
            @Override  
            public boolean onKeyDown(int keyCode, KeyEvent event) {  
                if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {  
                }  
                return super.onKeyDown(keyCode, event);  
            }  
          
            @Override  
            public boolean onKeyUp(int keyCode, KeyEvent event) {  
                surfaceDestroyed(mHolder);  
                return super.onKeyDown(keyCode, event);  
            }  
          
            @Override  
            public void run() {  
                while (flag) {  
                    try {  
                        synchronized (mHolder) {  
                            Thread.sleep(100); // 让线程休息100毫秒  
                            Draw(); // 调用自定义画画方法  
                        }  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    } finally {  
                        if (mCanvas != null) {  
                            // mHolder.unlockCanvasAndPost(mCanvas);//结束锁定画图,并提交改变。  
          
                        }  
                    }  
                }  
            }  
          
            /** 
             * 自定义一个方法,在画布上画一个圆 
             */  
            protected void Draw() {  
                mCanvas = mHolder.lockCanvas(); // 获得画布对象,开始对画布画画  
                if (mCanvas != null) {  
                    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);  
                    paint.setColor(Color.BLUE);  
                    paint.setStrokeWidth(10);  
                    paint.setStyle(Style.FILL);  
                    if (m_circle_r >= (getWidth() / 10)) {  
                        m_circle_r = 0;  
                    } else {  
                        m_circle_r++;  
                    }  
                    Bitmap pic = ((BitmapDrawable) getResources().getDrawable(  
                            R.drawable.qq)).getBitmap();  
                    mCanvas.drawBitmap(pic, 0, 0, paint);  
                    for (int i = 0; i < 5; i++)  
                        for (int j = 0; j < 8; j++)  
                            mCanvas.drawCircle(  
                                    (getWidth() / 5) * i + (getWidth() / 10),  
                                    (getHeight() / 8) * j + (getHeight() / 16),  
                                    m_circle_r, paint);  
                    mHolder.unlockCanvasAndPost(mCanvas); // 完成画画,把画布显示在屏幕上  
                }  
            }  
        }  
  • 相关阅读:
    透明的LISTVIEW
    循序渐进实现仿QQ界面(三):界面调色与控件自绘
    循序渐进实现仿QQ界面(一):园角矩形与双缓冲贴图窗口
    循序渐进实现仿QQ界面(二):贴图按钮的三态模拟
    C#中如何通过点击按钮切换窗口
    jQuery -- 光阴似箭(二):jQuery效果的使用
    jQuery -- 光阴似箭(一):初见 jQuery -- 基本用法,语法,选择器
    JavaScript -- 时光流逝(十三):DOM -- Console 对象
    JavaScript -- 时光流逝(十二):DOM -- Element 对象
    JavaScript -- 时光流逝(十一):DOM -- Document 对象
  • 原文地址:https://www.cnblogs.com/yaowen/p/7283834.html
Copyright © 2011-2022 走看看