Android 系统提供一种特殊的视图,称为SurfaceView,它是View的之类。与普通View不同的是SurfaceView拥有独立的绘图层,可以在主线程之外的线程中向屏幕绘图,由于不占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,在游戏画面、视频播放中都有应用。
linux 平台上的应用绘图一般是操作系统的framebuffer来实现OSD显示,如果C/C++层直接通过SurfaceFlinger来申请surface,该应用绘图就脱离了Android 窗口管理,将无法管理Z order。由于SurfaceView具有独立绘图层,它为移植一下linux平台上需要操作图形成的应用提供了可能性。本文将介绍SurfaceView的基本原理及如何通过native 语言(C/C++)来实现绘图。
一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来 说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer,用来单独描述它的绘图 表面,以区别于它的宿主窗口的绘图表面,同时layer的Z序由android 窗口系统管理。下图是dump出SurfaceFlinger信息摘录,应用程序窗口layer
Layer 0x41873008 (com.hybroad.launcher/com.hybroad.launcher.activity.MainActivity),同时存在SurfaceView 的Layer,该layer是具有内存空间的,可通过操作这块空间来绘制图形。
+
Layer 0x413f7008 (SurfaceView)
Region transparentRegion (this=0x413f716c, count=1)
[ 0, 0, 0, 0]
Region visibleRegion (this=0x413f7010, count=1)
[ 0, 0, 0, 0]
layerStack= 0, z= 21010, pos=(0,0), size=(1920,1080), crop=( 0, 0,1920,1080), isOpaque=0, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
layerStack= 0, z= 21010, pos=(0,0), size=(1920,1080), crop=( 0, 0,1920,1080), isOpaque=0, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
client=0x41879050
format= 4, activeBuffer=[ 0x 0: 0, 0], queued-frames=0, mRefreshPending=0
mTexName=10 mCurrentTexture=-1
CurrentCrop=[-1,0,0,0] mCurrentTransform=0
mAbandoned=0
-BufferQueue maxBufferCount=3, mSynchronousMode=1, default-size=[1920x1080], default-format=4, transform-hint=00, FIFO(0)={}
-BufferQueue maxBufferCount=3, mSynchronousMode=1, default-size=[1920x1080], default-format=4, transform-hint=00, FIFO(0)={}
[00] state=FREE , crop=[0,0,-1,-1], xform=0x00, time=0, scale=FREEZE
[01] state=FREE , crop=[0,0,-1,-1], xform=0x00, time=0, scale=FREEZE
[02] state=FREE , crop=[0,0,-1,-1], xform=0x00, time=0, scale=FREEZE
+ Layer 0x41873008 (com.hybroad.launcher/com.hybroad.launcher.activity.MainActivity)
Region transparentRegion (this=0x4187316c, count=1)
[ 0, 0, 0, 0]
Region visibleRegion (this=0x41873010, count=1)
[ 0, 0, 1920, 1080]
layerStack= 0, z= 21015, pos=(0,0), size=(1920,1080), crop=( 0, 0,1920,1080), isOpaque=0, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
layerStack= 0, z= 21015, pos=(0,0), size=(1920,1080), crop=( 0, 0,1920,1080), isOpaque=0, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
client=0x41879050
format= 1, activeBuffer=[1920x1080:1920, 1], queued-frames=0, mRefreshPending=0 mTexName=9 mCurrentTexture=2
mCurrentCrop=[0,0,0,0] mCurrentTransform=0
mAbandoned=0
-BufferQueue maxBufferCount=3, mSynchronousMode=1, default-size=[1920x1080], default-format=1, transform-hint=00, FIFO(0)={}
-BufferQueue maxBufferCount=3, mSynchronousMode=1, default-size=[1920x1080], default-format=1, transform-hint=00, FIFO(0)={}
[00] state=FREE , crop=[0,0,0,0], xform=0x00, time=0xyix26ea6e1d862, scale=FREEZE, 0x41417d60 [1920x1080:1920, 1]
[01] state=FREE , crop=[0,0,0,0], xform=0x00, time=0x26eb122a85d, scale=FREEZE, 0x400d4300 [1920x1080:1920, 1]
>[02] state=ACQUIRED, crop=[0,0,0,0], xform=0x00, time=0x26f007b9884, scale=FREEZE, 0x413f9f88 [1920x1080:1920, 1]
上述APP窗口的布局中存在一个SurfaceView 及Android 其他控件如:textView 、imageView。这样该Activity 窗口在surfaceFlinger中对应两个Layer,surfaceView具有一层layer,其余控件均与DecorView顶层视图共用另一Layer。surfaceview 对应的Layer在java层的标识就是SurfaceView中的成员surface。
从总体上描述了SurfaceView的大致实现原理之后,接下来我们就详细分析它的使用方法,包括surface层的创建过程,绘图过程及通过本地语言绘图的实现方法。
1. SurfaceView 中surface的创建。
当一个Android窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals。ViewRoot类的成员函数 performTraversals在执行的过程中,如果发现当前窗口的绘图表面还没有创建,或者发现当前窗口的绘图表面已经失效了,那么就会请求 WindowManagerService服务创建一个新的绘图表面,同时,它还会通过一系列的回调函数来让嵌入在窗口里面的SurfaceView有机会创建自己的绘图层。过程如图2所示,共有8个步骤。这里假定读者对Android 窗口管理有一定了解,本地不对SurfaceView如何关联到窗口系统中去做描述,只关注surfaceView中surface的创建。
图1 SurfaceView 的创建
如图1中描述,SurfaceView最终会通过成员函数updateWindow来更新当前正在处理的SurfaceView。在更新的过程中,如果发现当前正在处理的SurfaceView还没有创建绘图表面,那么就地请求WindowManagerService服务为它创建一个。
SurfaceHolder 代码段如下,从中可以看到通过SurfaceHolder 来获取canvas其实就是从上surface上获取一块画图区域。
图2: surface绘制UI流程
图1 SurfaceView 的创建
如图1中描述,SurfaceView最终会通过成员函数updateWindow来更新当前正在处理的SurfaceView。在更新的过程中,如果发现当前正在处理的SurfaceView还没有创建绘图表面,那么就地请求WindowManagerService服务为它创建一个。
private void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
... ...
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
... ...
mSurfaceLock.lock();
try {
... ...
relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
mWinFrame, mContentInsets,
mVisibleInsets, mConfiguration, mNewSurface);
... ...
} finally {
mSurfaceLock.unlock();
}
try {
redrawNeeded |= creating | reportDrawNeeded
... ...
if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
mSurfaceCreated = false;
if (mSurface.isValid()) {
... ...
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceDestroyed(mSurfaceHolder);
}
}
}
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
... ...
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
if (creating || formatChanged || sizeChanged
|| visibleChanged || realSizeChanged) {
... ...
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
... ...
} finally {
... ...
}
} catch (RemoteException ex) {
}
... ...
}
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
从updateWindow方法中可以看到,在SurfaceView第一次增加到WindowManagerService服务中去后会通过成员变量mSession所描述的一个Binder代理对象的成员函数relayout来请求WindowManagerService服务对SurfaceView的UI进行布局。布局过程中如果发现窗口的绘制surface还未创建,或者需要需要重新创建,那么就会为请求SurfaceFlinger服务为该窗口创建一个新的绘图surface,并且将该绘图surface返回来给调用者。代码段中的mSurface就是SurfaceView类的成员变量,为该SurfaceView的绘图对象,也是本文描述的C/C++绘图将要操作的图层在java层的标示。
2. SurfaceView 中surface的使用。
2.1 java 层的绘图过程
UI 绘图包括下面三个步骤
(1). 在绘图surface上建立一块画布,即获得一个Canvas对象。
(2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
(3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以它合成到显示屏幕上去。SurfaceView提供了一个SurfaceHolder接口,通过SurfaceHolder 可以完成上述绘图流程。
代码示例:
SurfaceView sv = (SurfaceView )findViewById(R.id.sv);
sfh =sv.getHolder();
.... ...
private void draw() {
try {
// 步骤1
canvas = sfh.lockCanvas(); // 得到一个canvas实例
// 步骤2
canvas.drawColor(Color.WHITE);// 刷屏
canvas.drawText("test", 100, 100, paint);// 画文字文本
} catch (Exception ex) {
} finally {
// 步骤3
if (canvas != null)
sfh.unlockCanvasAndPost(canvas); // 将画好的画布提交
}
}
SurfaceHolder 代码段如下,从中可以看到通过SurfaceHolder 来获取canvas其实就是从上surface上获取一块画图区域。
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
... ...
public void addCallback(Callback callback) {
... ...
}
public void removeCallback(Callback callback) {
... ...
}
... ...
public Canvas lockCanvas(Rect dirty) {
return internalLockCanvas(dirty);
}
private final Canvas internalLockCanvas(Rect dirty) {
mSurfaceLock.lock();
... ...
if (!mDrawingStopped && mWindow != null) {
... ...
try {
c = mSurface.lockCanvas(dirty);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
mSurfaceLock.unlock();
return null;
}
public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
public Surface getSurface() {
return mSurface;
}
public Rect getSurfaceFrame() {
return mSurfaceFrame;
}
};
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
2.2 C/C++中使用surface绘图
C/C++中要在绘制图形是需要拿到一个绘图层surface,由它来提供填充数据的buffer,如果直接通过请求SurfaceFlinger服务来创建surface,我们将无法通过窗口系统来管理Z order。而通过SurfaceView 可以很好的解决这个问题,获取到SurfaceView中的surface,通过JNI(JNI 方法参考
Android JNI 介绍)的方式传递到Native层,C/C++便可以很方便地再该图层上绘制图形。
从updateWindow方法中可以看到,当surface状态变化的时候都会通过callback来通知,当surface创建完成会调用 c.surfaceCreated(mSurfaceHolder),当surface大小变化时会通过 c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight)来通知。因此可以当surface创建时可以通过callback获取到SurfaceHolder,SurfaceHolder提供方法getSurface可以拿到。
SurfaceView的图层,将改surface 设置到native层即可。获取Surface的示例代码如下: SurfaceHolder mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
// surfaceView的大小发生改变的时候
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
// 创建surface后可以通过SurfaceHolder 来获取该surface,并根据需要设置位图格式
// 将这个localSurface通过JNI可以提供给Native代码绘图使用
Surface localSurface = arg0.getSurface();
arg0.setFormat(PixelFormat.RGBA_8888);
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
// surfaceView销毁的时候
}
再回过头来看java层UI绘制的三个步骤。从图2中可以看出,java层的操作与native层是一一对应。通过通过native层的surface 的lock方法获取到绘图buffer,通过surface的unlockAndPost方法将填充好的数据提交给SurfaceFlinger来合成显示。
图2: surface绘制UI流程