Android之SurfaceView

来源:互联网 发布:天猫宝贝淘宝搜不到 编辑:程序博客网 时间:2024/06/06 03:25

一、SurfaceView介绍和使用

SurfaceView是View的子类,使用的方式与任何View所派生的类都是完全相同的,可以像其他View那样应用动画,并把它们放到布局中。SurfaceView封装的Surface支持使用本章前面所描述的所有标准Canvas方法进行绘图,同时也支持完全的OpenGL ES库。
使用OpenGL,你可以在Surface上绘制任何支持的2D或者3D对象,与在2D画布上模拟相同的效果相比,这种方法可以依靠硬件加速(可用的时候)来极大地提高性能。
SurfaceView默认使用双缓冲技术的,它一个很好用的地方就是允许其他线程(不是UI线程)绘制图形(使用Canvas),这样就不会阻塞主线程了,所以它更适合于游戏的开发。

  • 在xml文件中使用SurfaceView

xml文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"              android:orientation="vertical"              android:layout_width="match_parent"              android:layout_height="match_parent">    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"            android:text="btn1" android:id="@+id/btn1"/>    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"            android:text="btn2" android:id="@+id/btn2"/>    <SurfaceView android:layout_width="match_parent" android:layout_height="match_parent"            android:id="@+id/surface"/></LinearLayout>

文件中定义了两个Button和一个SurfaceView,点击两个按钮分别在canvas上进行不同的绘制。canvas由这个SurfaceView得到。
activity文件

public class SurfaceActivity extends Activity {    public SurfaceHolder holder;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);//        setContentView(new MySurfaceView(this));        setContentView(R.layout.surface);        Button btn1 = (Button) findViewById(R.id.btn1);        Button btn2 = (Button) findViewById(R.id.btn2);        SurfaceView surfaceView = (SurfaceView)findViewById(R.id.surface);        holder = surfaceView.getHolder();        btn1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                simpleDraw();            }        });        btn2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                simpleDraw2();            }        });    }    public void simpleDraw()    {        new Thread(new Runnable() {            @Override            public void run() {                Canvas canvas = holder.lockCanvas();                System.out.println("canvas:"+canvas.hashCode());                clearCanvas(canvas);                Paint paint = new Paint();                paint.setColor(Color.RED);                canvas.drawRect(new RectF(2.3f, 3.5f,100.3f,200.0f),paint);                canvas.drawColor(Color.argb(160,122,122,122));                holder.unlockCanvasAndPost(canvas);            }        }).start();    }    public void simpleDraw2()    {        new Thread(new Runnable() {            @Override            public void run() {                Canvas canvas = holder.lockCanvas();                System.out.println("canvas:"+canvas.hashCode());                clearCanvas(canvas);                Paint paint = new Paint();                paint.setColor(Color.GRAY);                canvas.drawColor(Color.YELLOW);                canvas.drawRect(new RectF(2.3f, 3.5f,200.3f,300.0f),paint);                holder.unlockCanvasAndPost(canvas);            }        }).start();    }    public void clearCanvas(Canvas canvas)    {        Paint paint = new Paint();        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));        canvas.drawPaint(paint);        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));    }}

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

上面程序Activity窗口的视图结构中,除了有一个DecorView顶层视图之外,还有两个Button控件,以及一个SurfaceView视图,这样该Activity窗口在SurfaceFlinger服务中就对应有两个Layer或者一个Layer的一个LayerBuffer。

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

SurfaceFlinger是运行在System进程中,用来统一管理系统的帧缓冲区设备的服务。由于SurfaceFlinger服务运行在System进程中,因此,Android应用程序就需要通过Binder进程间通信机制来请求它来渲染自己的UI。Android应用程序请求SurfaceFlinger服务渲染自己的UI可以分为三步曲:首先是创建一个到SurfaceFlinger服务的连接,接着再通过这个连接来创建一个Surface,最后请求SurfaceFlinger服务渲染该Surface。

  • 自定义的SurfaceView

下面的程序在一个SurfaceView上简单绘制了一个方块、计时器以及一个移动的图片。

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {    private String TAG = "conowen";    private SurfaceHolder sfh;    private Canvas canvas;    private int lastX;    public MySurfaceViewThread mThread = null;    class MySurfaceViewThread extends Thread{        private boolean done;        private int counter;        @Override        public void run() {            // TODO Auto-generated method stub            while (!done) {                // 锁定画布,得到Canvas对象                canvas = sfh.lockCanvas();                // 设定Canvas对象的背景颜色                canvas.drawColor(Color.BLUE);                // 创建画笔                Paint p = new Paint();                // 设置画笔颜色                p.setColor(Color.RED);                // 设置文字大小                p.setTextSize(40);                // 创建一个Rect对象rect                // public Rect (int left, int top, int right, int bottom)                Rect rect = new Rect(100, 50, 400, 350);                // 在canvas上绘制rect                canvas.drawRect(rect, p);                // 在canvas上显示时间                // public void drawText (String text, float x, float y, Paint paint)                canvas.drawText("时间 = " + (counter++) + " 秒", 500, 200, p);                Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);                canvas.drawBitmap(bmp, lastX+10, 360, p);                lastX += 10;                if(lastX > 600)                    lastX = 0;                if (canvas != null) {                    // 解除锁定,并提交修改内容,更新屏幕                    sfh.unlockCanvasAndPost(canvas);                }                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }            }        }        public void requestExitAndWait()        {            done = true;            try {                Thread.currentThread().join();            }catch (Exception ex)            {                ex.printStackTrace();            }        }    };    public MySurfaceView(Context context) {        super(context);        // TODO Auto-generated constructor stub        // 通过SurfaceView获得SurfaceHolder对象        sfh = this.getHolder();        // 为SurfaceHolder添加回调结构SurfaceHolder.Callback        sfh.addCallback(this);    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width,                               int height) {        // TODO Auto-generated method stub        Log.i(TAG, "surfaceChanged");    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        // TODO Auto-generated method stub        Log.i(TAG, "surfaceCreated");        if (mThread == null)        {            mThread = new MySurfaceViewThread();            mThread.start();        }else {            mThread.start();        }    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        // TODO Auto-generated method stub        Log.i(TAG, "surfaceDestroyed");        mThread.requestExitAndWait();        mThread = null;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Toast.makeText(getContext(),"click surfaceview", Toast.LENGTH_SHORT).show();                break;        }        return super.onTouchEvent(event);    }}

效果图:

这里写代码片
机器人图片不动右移,当超过一定范围后又回到初始位置

二、SurfaceView的绘制过程

窗口在绘制的过程中,每一个子视图的成员函数draw或者dispatchDraw都会被调用到,以便它们可以绘制自己的UI。如果要在一个绘图表面进行UI绘制,那么就顺序执行以下的操作:
(1). 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。
(2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
(3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去。
SurfaceView提供了一个SurfaceHolder接口,通过这个SurfaceHolder接口就可以执行上述的第(1)和第(3)个操作,示例代码如下所示:

SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);  SurfaceHolder sh = sv.getHolder();  Cavas canvas = sh.lockCanvas()  //Draw something on canvas  .......  sh.unlockCanvasAndPost(canvas);  

注意,只有在一个SurfaceView的绘图表面的类型不SURFACE_TYPE_PUSH_BUFFERS的时候,我们才可以自由地在上面绘制UI。我们使用SurfaceView来显示摄像头预览或者播放视频时,一般就是会将它的绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS。在这种情况下,SurfaceView的绘图表面所使用的图形缓冲区是完全由摄像头服务或者视频播放服务来提供的,因此,我们就不可以随意地去访问该图形缓冲区,而是要由摄像头服务或者视频播放服务来访问,因为该图形缓冲区有可能是在专门的硬件里面分配的。
另外还有一个地方需要注意的是,上述代码既可以在应用程序的主线程中执行,也可以是在一个独立的线程中执行。如果上述代码是在应用程序的主线程中执行,那么就需要保证它们不会占用过多的时间,否则的话,就会导致应用程序的主线程不能及时地响应用户输入,从而导致ANR问题。
我们假设一个SurfaceView的绘图表面的类型不是SURFACE_TYPE_PUSH_BUFFERS,接下来,我们就从SurfaceView的成员函数getHolder开始,分析这个SurfaceView的绘制过程。 这个过程可以分为5个步骤:
Step 1. SurfaceView.getHolder

public class SurfaceView extends View {      ......      public SurfaceHolder getHolder() {          return mSurfaceHolder;     }      ......      private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {          ......      }      ......  }  

SurfaceView类的成员函数getHolder的实现很简单,它只是将成员变量mSurfaceHolder所指向的一个SurfaceHolder对象返回给调用者。
Step 2. SurfaceHolder.lockCanvas

public class SurfaceView extends View {      ......    final ReentrantLock mSurfaceLock = new ReentrantLock();      final Surface mSurface = new Surface();      ......      private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {          ......          public Canvas lockCanvas() {              return internalLockCanvas(null);          }          ......          private final Canvas internalLockCanvas(Rect dirty) {              if (mType == SURFACE_TYPE_PUSH_BUFFERS) {                  throw new BadSurfaceTypeException(                          "Surface type is SURFACE_TYPE_PUSH_BUFFERS");              }              mSurfaceLock.lock();              ......              Canvas c = null;              if (!mDrawingStopped && mWindow != null) {                  Rect frame = dirty != null ? dirty : mSurfaceFrame;                  try {                      c = mSurface.lockCanvas(frame);                  } catch (Exception e) {                     Log.e(LOG_TAG, "Exception locking surface", e);                  }              }             ......            if (c != null) {                  mLastLockTime = SystemClock.uptimeMillis();                  return c;              }              ......              mSurfaceLock.unlock();              return null;            }           ......               }      ......  }

SurfaceHolder类的成员函数lockCanvas通过调用另外一个成员函数internalLockCanvas来在当前正在处理的SurfaceView的绘图表面上建立一块画布返回给调用者。
SurfaceHolder类的成员函数internalLockCanvas首先是判断当前正在处理的SurfaceView的绘图表面的类型是否是SURFACE_TYPE_PUSH_BUFFERS,如果是的话,那么就会抛出一个类型为BadSurfaceTypeException的异常,原因如前面所述。
由于接下来SurfaceHolder类的成员函数internalLockCanvas要在当前正在处理的SurfaceView的绘图表面上建立一块画布,并且返回给调用者访问,而这块画布不是线程安全的,也就是说它不能同时被多个线程访问,因此,就需要对当前正在处理的SurfaceView的绘图表面进行锁保护,这是通过它的锁定它的成员变量mSurfaceLock所指向的一个ReentrantLock对象来实现的。
注意,如果当前正在处理的SurfaceView的成员变量mWindow的值等于null,那么就说明它的绘图表面还没有创建好,这时候就无法创建一块画布返回给调用者。同时,如果当前正在处理的SurfaceView的绘图表面已经创建好,但是该SurfaceView当前是处于停止绘制的状态,即它的成员变量mDrawingStopped的值等于true,那么也是无法创建一块画布返回给调用者的。
假设当前正在处理的SurfaceView的绘制表面已经创建好,并且它不是处于停止绘制的状态,那么SurfaceHolder类的成员函数internalLockCanvas就会通过调用该SurfaceView的成员变量mSurface所指向的一个Surface对象的成员函数lockCanvas来创建一块画布,并且返回给调用者。注意,在这种情况下,当前正在处理的SurfaceView的绘制表面还是处于锁定状态的。
另一方面,如果SurfaceHolder类的成员函数internalLockCanvas不能成功地在当前正在处理的SurfaceView的绘制表面上创建一块画布,即变量c的值等于null,那么SurfaceHolder类的成员函数internalLockCanvas在返回一个null值调用者之前,还会将该SurfaceView的绘制表面就会解锁。
从前面第1部分的内容可以知道,SurfaceView类的成员变量mSurface描述的是就是SurfaceView的专有绘图表面,接下来我们就继续分析它所指向的一个Surface对象的成员函数lockCanvas的实现,以便可以了解SurfaceView的画布的创建过程。

Step 3. Surface.lockCanvas
Surface类的成员函数lockCanvas,它大致就是通过JNI方法来在当前正在处理的绘图表面上获得一个图形缓冲区,并且将这个图形绘冲区封装在一块类型为Canvas的画布中返回给调用者使用。
调用者获得了一块类型为Canvas的画布之后,就可以调用Canvas类所提供的绘图函数来绘制任意的UI了,例如,调用Canvas类的成员函数drawLine、drawRect和drawCircle可以分别用来画直线、矩形和圆。
调用者在画布上绘制完成所需要的UI之后,就可以将这块画布的图形绘冲区的UI数据提交给SurfaceFlinger服务来处理了,这是通过调用SurfaceHolder类的成员函数unlockCanvasAndPost来实现的。

Step 4. SurfaceHolder.unlockCanvasAndPost

public class SurfaceView extends View {      ......      final ReentrantLock mSurfaceLock = new ReentrantLock();      final Surface mSurface = new Surface();      ......      private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {          ......          public void unlockCanvasAndPost(Canvas canvas) {              mSurface.unlockCanvasAndPost(canvas);              mSurfaceLock.unlock();          }           ......               }      ......  }  

SurfaceHolder类的成员函数unlockCanvasAndPost是通过调用当前正在处理的SurfaceView的成员变量mSurface所指向的一个Surface对象的成员函数unlockCanvasAndPost来将参数canvas所描述的一块画布的图形缓冲区提交给SurfaceFlinger服务处理的。
提交完成参数canvas所描述的一块画布的图形缓冲区给SurfaceFlinger服务之后,SurfaceHolder类的成员函数unlockCanvasAndPost再调用当前正在处理的SurfaceView的成员变量mSurfaceLock所指向的一个ReentrantLock对象的成员函数unlock来解锁当前正在处理的SurfaceView的绘图表面,因为在前面的Step 2中,我们曾经将该绘图表面锁住了。
接下来,我们就继续分析Surface类的成员函数unlockCanvasAndPost的实现,以便可以了解SurfaceView的绘制过程。

Step 5. Surface.unlockCanvasAndPost
Surface类的成员函数unlockCanvasAndPost,它大致就是将在前面的Step 3中所获得的一个图形缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以在合适的时候将该图形缓冲区合成到屏幕上去显示,这样就可以将对应的SurfaceView的UI展现出来了。
至此,我们就分析完成SurfaceView的绘制过程了,整个SurfaceView的实现原理也就分析完了。总结来说,就是SurfaceView有以下三个特点:
A. 具有独立的绘图表面;
B. 需要在宿主窗口上挖一个区域来显示自己;
C. 它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入。

0 0
原创粉丝点击