SurfaceView + SurfaceHolder 入門

最近準備研究cocos2d,所以先記錄下SurfaceView學習筆記。


先看看官方API文檔對SurfaceView的描述:
https://developer.android.com/reference/android/view/SurfaceView.html

Provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen

The surface is Z ordered so that it is behind the window holding its SurfaceView; the SurfaceView punches a hole in its window to allow its surface to be displayed. The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. This can be used to place overlays such as buttons on top of the Surface, though note however that it can have an impact on performance since a full alpha-blended composite will be performed each time the Surface changes.

The transparent region that makes the surface visible is based on the layout positions in the view hierarchy. If the post-layout transform properties are used to draw a sibling view on top of the SurfaceView, the view may not be properly composited with the surface.

Access to the underlying surface is provided via the SurfaceHolder interface, which can be retrieved by calling getHolder().

The Surface will be created for you while the SurfaceView’s window is visible; you should implement surfaceCreated(SurfaceHolder) and surfaceDestroyed(SurfaceHolder) to discover when the Surface is created and destroyed as the window is shown and hidden.

One of the purposes of this class is to provide a surface in which a secondary thread can render into the screen. If you are going to use it this way, you need to be aware of some threading semantics:

All SurfaceView and SurfaceHolder.Callback methods will be called from the thread running the SurfaceView’s window (typically the main thread of the application). They thus need to correctly synchronize with any state that is also touched by the drawing thread.

You must ensure that the drawing thread only touches the underlying Surface while it is valid – between
SurfaceHolder.Callback.surfaceCreated() and
SurfaceHolder.Callback.surfaceDestroyed().

由於全段過長,我只加粗了重點部分。
簡單來說,要使用SurfaceView,需要兩個線程:

  • 主線程(即UI線程):用於接受用戶操作輸入
  • 第二線程(secondary thread):用於渲染界面

那麼我們首先創建一個SurfaceUI類(extends SurfaceView)作爲界面。

public class SurfaceUI extends SurfaceView{

    public SurfaceUI(Context context) {
        super(context);
    }

}

修改MainActivity的setContentView:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        SurfaceUI surfaceUI = new SurfaceUI(this);
        setContentView(surfaceUI);
    }

}

接下來我們需要使用SurfaceHolder,一樣先看看官方API文檔:

https://developer.android.com/reference/android/view/SurfaceHolder.html

Abstract interface to someone holding a display surface. Allows you to control the surface size and format, edit the pixels in the surface, and monitor changes to the surface. This interface is typically available through the SurfaceView class.

When using this interface from a thread other than the one running its SurfaceView, you will want to carefully read the methods lockCanvas() and Callback.surfaceCreated().

發現SurfaceHolder是個抽象接口,在它的內部類中找到SurfaceHolder.Callback

A client may implement this interface to receive information about changes to the surface. When used with a SurfaceView, the Surface being held is only available between calls to surfaceCreated(SurfaceHolder) and surfaceDestroyed(SurfaceHolder). The Callback is set with SurfaceHolder.addCallback method.

看了說明,大體明白,主要是要實現SurfaceHolder.Callback接口,修改SurfaceUI 實現SurfaceHolder.Callback接口並且實現未實現方法:

public class SurfaceUI extends SurfaceView implements SurfaceHolder.Callback{

    public SurfaceUI(Context context) {
        super(context);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

}

接下來根據說明,可以獲取SurfaceHolder和添加回調:

    public SurfaceUI(Context context) {
        super(context);

        SurfaceHolder surfaceHolder = getHolder();//獲取SurfaceHolder
        surfaceHolder.addCallback(this);//爲SurfaceHolder添加回調
    }

有了SurfaceView和SurfaceHolder,現在只缺第二線程了,也就是渲染線程,在SurfaceUI類中創建內部線程類:

    /**
     * 用於渲染的第二線程
     */
    public class RenderThread extends Thread {

        @Override
        public void run() {
            super.run();
        }

    }

接下來就是注意,調用渲染線程必須在surfaceCreated(SurfaceHolder) and surfaceDestroyed(SurfaceHolder)之間,這個在API文檔中多次出現。
那麼的話首先定義一個flag:

    /**
     * 用於控制是否渲染界面
     */
    private boolean isRender = true;

在surfaceCreated中開啓渲染線程,surfaceDestroyed中停止渲染:

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        new RenderThread().start();//開始渲染線程
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isRender = false;//停止渲染
    }

上述都完成後,就剩下如何操作渲染線程了。
其實SurfaceView和SurfaceHolder都是操作Surface(可以在源碼中找到),可以在Surface的源碼中看到一段:(還記得官方API上面寫的you will want to carefully read the methods lockCanvas()嗎)

    /** draw into a surface */
    public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException {
        /*
         * the dirty rectangle may be expanded to the surface's size, if for
         * instance it has been resized or if the bits were lost, since the last
         * call.
         */
        return lockCanvasNative(dirty);
    }

可以看到上述的代碼中的註釋就是說,這是用來繪製界面的,既然有lock,一般就有對應的unlock方法,在此方法的源碼下面可以看到兩個方法:

  /** unlock the surface and asks a page flip */
    public native   void unlockCanvasAndPost(Canvas canvas);

    /** 
     * unlock the surface. the screen won't be updated until
     * post() or postAll() is called
     */
    public native   void unlockCanvas(Canvas canvas);

可以看到unlockCanvas不會更新界面,直到調用post或者postAll方法,所以提供了另一個方法unlockCanvasAndPost。
但是上述方法都是Surface本身的,我們實際使用的SurfaceView,所以SurfaceHolder中也有對應的方法:

    public Canvas lockCanvas();

    public Canvas lockCanvas(Rect dirty);

    public void unlockCanvasAndPost(Canvas canvas);

不過要注意的是,SurfaceHolder的lockCanvas上的鎖和Surface中上的鎖不是同一把鎖,使用SurfaceHolder的lockCanvas相當於上了2次鎖
所以可以在渲染線程中分三步完成渲染:

//1.lockCanvas
Canvas lockCanvas = surfaceHolder.lockCanvas();
//2.繪製界面(可以做其他複雜操作)
lockCanvas.drawRGB(255, 0, 0);
//3.unlock and post
surfaceHolder.unlockCanvasAndPost(lockCanvas);

運行效果如下:

運行效果

完整代碼

SurfaceUI


public class SurfaceUI extends SurfaceView implements SurfaceHolder.Callback{

    /**
     * 用於控制是否渲染界面
     */
    private boolean isRender = true;

    private SurfaceHolder surfaceHolder;

    public SurfaceUI(Context context) {
        super(context);

        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);//爲SurfaceHolder添加回調
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        new RenderThread().start();//開始渲染線程
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isRender = false;//停止渲染
    }

    /**
     * 用於渲染的第二線程
     */
    public class RenderThread extends Thread {

        @Override
        public void run() {
            while (isRender) {
                //1.lockCanvas
                Canvas lockCanvas = surfaceHolder.lockCanvas();
                //2.繪製界面(可以做其他複雜操作)
                lockCanvas.drawRGB(255, 0, 0);
                //3.unlock and post
                surfaceHolder.unlockCanvasAndPost(lockCanvas);
            }
        }

    }

}

MainActivity

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        SurfaceUI surfaceUI = new SurfaceUI(this);
        setContentView(surfaceUI);
    }

}

聲明

原創文章,歡迎轉載,請保留出處。
有任何錯誤、疑問或者建議,歡迎指出。
我的郵箱:[email protected]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章