3. SurfaceView的绘制过程
SurfaceView虽然具有独立的绘图表面,不过它仍然是宿主窗口的视图结构中的一个结点,因此,它仍然是可以参与到宿主窗口的绘制流程中去的。从前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文可以知道,窗口在绘制的过程中,每一个子视图的成员函数draw或者dispatchDraw都会被调用到,以便它们可以绘制自己的UI。
SurfaceView类的成员函数draw和dispatchDraw的实现如下所示:
public class SurfaceView extends View {
......
@Override
public void draw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
super.draw(canvas);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// if SKIP_DRAW is cleared, draw() has already punched a hole
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
// reposition ourselves where the surface is
mHaveFrame = true;
updateWindow(false, false);
super.dispatchDraw(canvas);
}
......
}
这两个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
SurfaceView类的成员函数draw和dispatchDraw的参数canvas所描述的都是建立在宿主窗口的绘图表面上的画布,因此,在这块画布上绘制的任何UI都是出现在宿主窗口的绘图表面上的。
本来SurfaceView类的成员函数draw是用来将自己的UI绘制在宿主窗口的绘图表面上的,但是这里我们可以看到,如果当前正在处理的SurfaceView不是用作宿主窗口面板的时候,即其成员变量mWindowType的值不等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的时候,SurfaceView类的成员函数draw只是简单地将它所占据的区域绘制为黑色。
本来SurfaceView类的成员函数dispatchDraw是用来绘制SurfaceView的子视图的,但是这里我们同样看到,如果当前正在处理的SurfaceView不是用作宿主窗口面板的时候,那么SurfaceView类的成员函数dispatchDraw只是简单地将它所占据的区域绘制为黑色,同时,它还会通过调用另外一个成员函数updateWindow更新自己的UI,实际上就是请求WindowManagerService服务对自己的UI进行布局,以及创建绘图表面,具体可以参考前面第1部分的内容。
从SurfaceView类的成员函数draw和dispatchDraw的实现就可以看出,SurfaceView在其宿主窗口的绘图表面上面所做的操作就是将自己所占据的区域绘为黑色,除此之外,就没有其它更多的操作了,这是因为SurfaceView的UI是要展现在它自己的绘图表面上面的。接下来我们就分析如何在SurfaceView的绘图表面上面进行UI绘制。
从前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文可以知道,如果要在一个绘图表面进行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的绘制过程,如下所示:
图4 SurfaceView的绘制过程
这个过程可以分为5个步骤,接下来我们就详细分析每一个步骤。
Step 1. SurfaceView.getHolder
public class SurfaceView extends View {
......
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
......
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
......
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
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;
}
......
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
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的具体实现可以参考前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文,它大致就是通过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();
}
......
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
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的具体实现同样是可以参考前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文,它大致就是将在前面的Step 3中所获得的一个图形缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以在合适的时候将该图形缓冲区合成到屏幕上去显示,这样就可以将对应的SurfaceView的UI展现出来了。
至此,我们就分析完成SurfaceView的绘制过程了,整个SurfaceView的实现原理也就分析完了。总结来说,就是SurfaceView有以下三个特点:
A. 具有独立的绘图表面;
B. 需要在宿主窗口上挖一个洞来显示自己;
C. 它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入。