淺談Surface
Surface是安卓中一個比較重要但是又比較偏門的東西,其實我們每個應用基本基本上都會在不知不覺中用到它.這段時間剛好查了相關的一些資料,這裡把它記錄下來.
#什麼是Surface
讓我們看看Surface的官方介紹:
Handle onto a raw buffer that is being managed by the screen compositor.
Surface是一個raw buffer的控制代碼,我們可以通過它在raw buffer上進行繪製.
對應到程式碼其實就是可以通過Surface獲得一個Canvas:
Canvas canvas = mSurface.lockCanvas(null); //使用Canvas進行繪製 mSurface.unlockCanvasAndPost(canvas);
Surface其實是安卓中專門用來畫圖的地方,所有我們看到的影象最終都通過Surface去繪製.例如我們的Activity它其實也是繪製在一塊Surface上的.
如果深入去挖,會講到SurfaceControl,講到ISurface,講到SurfaceFlinger.但是這些東西都太過底層了.這裡我想給大家講講比較偏應用層的東西.
SurfaceView
Surface可能大家比較陌生,但是SurfaceView和GLSurfaceView相信大家或多或少都會聽說過.
SurfaceView其實就是對Surface進行了一次封裝,它內部幫我們管理了一個Surface.我們使用SurfaceView其實最終都是獲取到這個Surface去繪製.
這裡開門見山,直接丟擲一個簡單的SurfaceView的用法,下面的Demo用SurfaceView畫了一個100*100的紅色矩形
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private DrawThread mDrawThread; public MySurfaceView(Context context) { this(context, null); } public MySurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getHolder().addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { mDrawThread = new DrawThread(holder.getSurface()); mDrawThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mDrawThread.stopDraw(); try { mDrawThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } private static class DrawThread extends Thread { private Surface mSurface; private boolean mRunning = true; private Paint mPaint = new Paint(); DrawThread(Surface surface) { mSurface = surface; mPaint.setColor(Color.RED); } void stopDraw() { mRunning = false; } @Override public void run() { super.run(); while (mRunning) { Canvas canvas = mSurface.lockCanvas(null); canvas.drawColor(Color.WHITE); canvas.drawRect(0, 0, 100, 100, mPaint); mSurface.unlockCanvasAndPost(canvas); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
執行效果如下:

這個Demo有幾個關鍵程式碼,第一個是在建構函式裡面使用getHolder()獲取到SurfaceHolder,然後使用addCallback註冊了個監聽.這樣就能監聽SurfaceView內部Surface的生命週期.
接著我們在surfaceCreated回撥裡面開啟了一個DrawThread執行緒.它的主要工作就是在一個while迴圈裡面不停的繪製.
通過程式碼我們可以看到這個繪製的過程:
- 通過SurfaceHolder.getSurface可以獲取到Surface
- 通過Surface.lockCanvas可以獲取到Surface的Canvas
- 使用Canvas去繪製圖像
- 使用Surface.unlockCanvasAndPost可以釋放Canvas
相信這個Demo程式碼不用再多說,大家都可以很快理解.從中我們可以看到,SurfaceView最大的特點就是可以在子執行緒中繪製圖像.
在子執行緒中繪製圖像有什麼好處呢?
我們都知道一般情況下View都是在主執行緒中繪製的,而且需要通過measure、layout、draw三個步驟.當佈局越複雜,繪製的效率就越低,而且主執行緒中的一些耗時操作也會進一步降低效率.
如果使用SurfaceView的話我們就能越過measure、layout操作,而且不會被主執行緒的運算減低繪製效能.這樣的特性十分適合於一些頻繁更新且對重新整理率有一定要求的程式,如相機的預覽、畫筆書寫等.
GLSurfaceView
而GLSurfaceView繼承自SurfaceView,其實是對SurfaceView再做了一次封裝,方便我們在安卓中使用OpenGL.
我們都知道OpenGL是一個跨平臺的圖形庫.它提供了一些全平臺統一的圖形介面.但是各個平臺其實都有一些很難統一的差異,所以為了跨平臺的相容性,OpenGL不負責視窗管理及上下文管理.這部分由各個平臺自己實現.EGL就是安卓平臺上的實現它是 OpenGL ES 和底層 Native 平臺視窗系統之間的介面.
所以在安卓上使用OpenGL,都需要先用EGL進行一些初始化操作,結束的時候再用EGL做一些清理工作.
GLSurfaceView已經幫我們用SurfaceHolder做了EGL的初始化和清理操作,所以我們不需要再去關心EGL.
和我們上面寫的SurfaceView的Demo一樣,GLSurface的繪製也是在子執行緒中進行的,它為我們開啟了一個GLThread,對一些處理事件進行了處理.我們只需要實現Renderer介面進行繪製即可,GLSurfaceView就會在GLThread中呼叫我們的Renderer進行繪製:
public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { ... private Renderer mRenderer ... static class GLThread extends Thread { ... @Override public void run() { ... guardedRun(); ... } ... private void guardedRun() throws InterruptedException { ... while(true){ ... view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); ... view.mRenderer.onSurfaceChanged(gl, w, h); ... view.mRenderer.onDrawFrame(gl); ... } ... } ... } ... }
一個簡單的Demo如下:
public class MyGLSurfaceView extends GLSurfaceView { public MyGLSurfaceView(Context context) { this(context, null); } public MyGLSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); setRenderer(new MyRender()); } private static class MyRender implements Renderer { private FloatBuffer mVB; MyRender() { float coords[] = { -0.5f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.5f, 0.5f, 0.0f, -0.5f, 0.5f, 0.0f }; ByteBuffer vbb = ByteBuffer.allocateDirect(coords.length * 4); vbb.order(ByteOrder.nativeOrder()); mVB = vbb.asFloatBuffer(); mVB.put(coords); mVB.position(0); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); } @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVB); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 6); } } }
可以看到,我們實現了Renderer去畫一個紅色矩形,然後使用setRenderer設定給GLSurfaceView就可以了,執行效果如下:

ViewRootImpl與Surface
ViewRootImpl相信大家都知道它是啥,但是可能很多人都不知道,其實它內部也是用Surface進行實際的繪製工作的:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ... private void performTraversals() { ... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... performDraw(); } ... private void performDraw() { ... draw(fullRedrawNeeded); ... } ... private void draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { return; } ... } ... private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, boolean scalingRequired, Rect dirty) { ... canvas = mSurface.lockCanvas(dirty); ... mView.draw(canvas); ... surface.unlockCanvasAndPost(canvas); ... } ... }
可以看到,它也是呼叫的mSurface.lockCanvas獲取的Canvas.這個Canvas就是我們在View.onDraw裡面傳進來的Canvas.
所以我們Activity上的View也是畫在Surface上的.