Android進階5:SurfaceView實現原理分析

YingKe發表於2019-10-17

SurfaceView的概念

第一次接觸SurfaceView,找了很多資料才理解SurfaceView概念,總結查資料的結果。Android中有一種特殊的檢視,稱為SurefaceView,與平時時候的 TextView、Button的區別:

  1. 它擁有獨立的特殊的繪製表面,即 它不與其宿主視窗共享一個繪製表面
  2. SurefaceView的UI可以在一個獨立的執行緒中進行繪製
  3. 因為不會佔用主執行緒資源,一方面可以實現複雜而高效的UI,二是不會導致使用者輸入得不到及時響應。

綜合這些特點,SurfaceView 一般用來實現動態的或者比較複雜的影像還有動畫的顯示。

為什麼需要SurfaceView

普通空間,TextView,Button等,都是講自己的UI繪製在宿主視窗的繪製表面Surface之上,意味著他們的UI在應用程式的主執行緒中繪製的。但是主執行緒除了繪製UI之外,還要及時響應使用者輸入,手勢等,否則,系統會認為應用程式沒響應,ANR。

因此,對一些遊戲畫面,或者攝像頭,視訊播放等,UI都比較複雜,要求能夠進行高效的繪製,因此,他們的UI不適合在主執行緒中繪製。這時候就必須要給那些需要複雜而高效的UI檢視生成一個獨立的繪製表面Surface,並且使用獨立的執行緒來繪製這些檢視UI。

理解SurfaceView的形成

每個視窗在SurfaceFlinger服務中都對應有一個layer,用來描述它的繪製表面surface。對於那些具有SurfaceView的視窗來說,每個SurfaceFlinger服務中還對應一個獨立的Layer或者LayerBuffer,用來單獨描述它的繪製表面,以區別它的宿主視窗的繪製表面。

無論是LayerBuffer,還是Layer,它們都是以LayerBase為基類的,也就是說,SurfaceFlinger服務把所有的LayerBuffer和Layer都抽象為LayerBase,因此就可以用統一的流程來繪製和合成它們的UI。由於LayerBuffer的繪製和合成與Layer的繪製和合成是類似的

為了接下來可以方便地描述SurfaceView的實現原理分析,我們假設在一個Activity視窗的檢視結構中,除了有一個DecorView頂層檢視之外,還有兩個TextView控制元件,以及一個SurfaceView檢視,這樣該Activity視窗在SurfaceFlinger服務中就對應有兩個Layer或者一個Layer的一個LayerBuffer,如圖1所示

圖1 SurfaceView及其宿主Activity視窗的繪圖表面示意圖
圖1 SurfaceView及其宿主Activity視窗的繪圖表面示意圖

在圖1中,Activity視窗的頂層檢視DecorView及其兩個TextView控制元件的UI都是繪製在SurfaceFlinger服務中的同一個Layer上面的,而SurfaceView的UI是繪製在SurfaceFlinger服務中的另外一個Layer或者LayerBuffer上的。

注意,用來描述SurfaceView的Layer或者LayerBuffer的Z軸位置是小於用來其宿主Activity視窗的Layer的Z軸位置的,但是前者會在後者的上面挖一個“洞”出來,以便它的UI可以對使用者可見。實際上,SurfaceView在其宿主Activity視窗上所挖的“洞”只不過是在其宿主Activity視窗上設定了一塊透明區域。

從總體上描述了SurfaceView的大致實現原理之後,接下來我們就詳細分析它的具體實現過程,包括它的繪圖表面的建立過程、在宿主視窗上面進行挖洞的過程,以及繪製過程。參考連結:https://blog.csdn.net/luoshengyang/article/details/8661317/

SurfaceView的繪製過程

SurfaceView雖然具有獨立的繪圖表面,不過它仍然是宿主視窗的檢視結構中的一個結點,因此,它仍然是可以參與到宿主視窗的繪製流程中去的。從前面Android應用程式視窗(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文可以知道,視窗在繪製的過程中,每一個子檢視的成員函式draw或者dispatchDraw都會被呼叫到,以便它們可以繪製自己的UI。
SurfaceView類的成員函式draw和dispatchDraw的實現如下所示:

    @Override
    public void draw(Canvas canvas) {
        if (mDrawFinished && !isAboveParent()) {
            // draw() is not called when SKIP_DRAW is set
            if ((mPrivateFlags & PFLAG_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 (mDrawFinished && !isAboveParent()) {
            // draw() is not called when SKIP_DRAW is set
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                // punch a whole in the view-hierarchy below us
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
        }
        super.dispatchDraw(canvas);
    }
複製程式碼

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進行佈局,以及建立繪圖表面

從SurfaceView類的成員函式draw和dispatchDraw的實現就可以看出,SurfaceView在其宿主視窗的繪圖表面上面所做的操作就是將自己所佔據的區域繪為黑色,除此之外,就沒有其它更多的操作了,這是因為SurfaceView的UI是要展現在它自己的繪圖表面上面的。接下來我們就分析如何在SurfaceView的繪圖表面上面進行UI繪製。

從前面Android應用程式視窗(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文可以知道,如果要在一個繪圖表面進行UI繪製,那麼就順序執行以下的操作:

    (1). 在繪圖表面的基礎上建立一塊畫布,即獲得一個Canvas物件。
    (2). 利用Canvas類提供的繪圖介面在前面獲得的畫布上繪製任意的UI。
    (3). 將已經填充好了UI資料的畫布緩衝區提交給SurfaceFlinger服務,以便SurfaceFlinger服務可以將它合成到螢幕上去。
複製程式碼

分析原始碼

分析 Surface,SurfaceHolder,SurfaceView 三個類

Surface:

處理被螢幕排序的原生的buffer,Android中的Surface就是一個用來畫圖形(graphics)或影像(image)的地方,對於View及其子類,都是畫在Surface上,各Surface物件通過Surfaceflinger合成到frameBuffer,每個Surface都是雙緩衝(實際上就是兩個執行緒,一個渲染執行緒,一個UI更新執行緒),它有一個backBuffer和一個frontBuffer,Surface中建立了Canvas物件,用來管理Surface繪圖操作,Canvas對應Bitmap,儲存Surface中的內容。

SurfaceView:

SurfaceView是View的子類,且實現了Parcelable介面且實現了Parcelable介面,其中內嵌了一個專門用於繪製的Surface,SurfaceView可以控制這個Surface的格式和尺寸,以及Surface的繪製位置。可以理解為Surface就是管理資料的地方,SurfaceView就是展示資料的地方。

SurfaceHolder:

一個管理SurfaceHolder的容器。SurfaceHolder是一個介面,可理解為一個Surface的監聽器。 通過回撥方法addCallback(SurfaceHolder.Callback callback )監聽Surface的建立 通過獲取Surface中的Canvas物件,並鎖定之。所得到的Canvas物件 通過當修改Surface中的資料完成後,釋放同步鎖,並提交改變Surface的狀態及影像,將新的影像資料進行展示。-

而最後綜合:SurfaceView中呼叫getHolder方法,可以獲得當前SurfaceView中的Surface對應的SurfaceHolder,SurfaceHolder開始對Surface進行管理操作。這裡其實按MVC模式理解的話,可以更好理解。M:Surface(影像資料),V:SurfaceView(影像展示),C:SurfaceHolder(影像資料管理)。

Surface原始碼

/**
 * Handle onto a raw buffer that is being managed by the screen compositor.
 */

public class Surface implements Parcelable {  
    // code......
}
複製程式碼

首先來看 Surface 這個類,它實現了 Parcelable 介面進行序列化(這裡主要用來在程式間傳遞 surface 物件),用來處理螢幕顯示緩衝區的資料,原始碼中對它的註釋為: Handle onto a raw buffer that is being managed by the screen compositor. Surface是原始影像緩衝區(raw buffer)的一個控制程式碼,而原始影像緩衝區是由螢幕影像合成器(screen compositor)管理的。

  • 由螢幕顯示內容合成器(screen compositor)所管理的原生緩衝器的控制程式碼(類似控制程式碼) - 名詞解釋:控制程式碼,英文:HANDLE,資料物件進入記憶體之後獲取到記憶體地址,但是所在的記憶體地址並不是固定的,需要用控制程式碼來儲存內容所在的記憶體地址。從資料型別上來看它只是一個32位(或64位)的無符號整數。 - Surface 充當控制程式碼的角色,用來獲取源生緩衝區以及其中的內容 - 源生緩衝區(raw buffer)用來儲存當前視窗的畫素資料 - 於是可知 Surface 就是 Android 中用來繪圖的的地方,具體來說應該是 Surface 中的 Canvas Surface 中定義了畫布相關的 Canvas 物件
private final Canvas mCanvas = new CompatibleCanvas();  
複製程式碼

Java中,繪圖通常在一個 Canvas 物件上進行的,Surface 中也包含了一個 Canvas 物件,這裡的 CompatibleCanvas 是Surface.java 中的一個內部類,其中包含一個矩陣物件Matrix(變數名mOrigMatrix)。矩陣Matrix就是一塊記憶體區域,針對View的各種繪畫操作都儲存在此記憶體中。
Surface 內部有一個 CompatibleCanvas 的內部類,這個內部類的作用是為了能夠相容 Android 各個解析度的螢幕,根據不同螢幕的解析度處理不同的影像資料。

private final class CompatibleCanvas extends Canvas {  
        // A temp matrix to remember what an application obtained via {@link getMatrix}
        private Matrix mOrigMatrix = null;

        @Override
        public void setMatrix(Matrix matrix) {
            if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
                // don't scale the matrix if it's not compatibility mode, or
                // the matrix was obtained from getMatrix.
                super.setMatrix(matrix);
            } else {
                Matrix m = new Matrix(mCompatibleMatrix);
                m.preConcat(matrix);
                super.setMatrix(m);
            }
        }

        @SuppressWarnings("deprecation")
        @Override
        public void getMatrix(Matrix m) {
            super.getMatrix(m);
            if (mOrigMatrix == null) {
                mOrigMatrix = new Matrix();
            }
            mOrigMatrix.set(m);
        }
    }
複製程式碼

1.Surface的兩個重要方法:
Surface中的很多方法都是原生方法,lockCanvas和unlockCanvasAndPost也是原生的,這裡不是指SurfaceHolder中的lockCanvas和unlockCanvasAndPost,SurfaceHolder只是做了封裝。

  • lockCanvas(…) + Gets a Canvas for drawing into this surface. 獲取進行繪畫的 Canvas 物件 + After drawing into the provided Canvas, the caller must invoke unlockCanvasAndPost to post the new contents to the surface. 繪製完一幀的資料之後需要呼叫 unlockCanvasAndPost 方法把畫布解鎖,然後把畫好的影像 Post 到當前螢幕上去顯示 + 當一個 Canvas 在被繪製的時候,它是出於被鎖定的狀態,就是說必須等待正在繪製的這一幀繪製完成之後並解鎖畫布之後才能進行別的操作 + 實際鎖住 Canvas 的過程是在 jni 層完成的
  • unlockCanvasAndPost(…) + Posts the new contents of the Canvas to the surface and releases the Canvas.將新繪製的影像內容傳給 surface 之後這個 Canvas 物件會被釋放掉(實際釋放的過程是在 jni 層完成的)

urface 的 lockCanvas 和 unlockCanvasAndPost 兩個方法最終都是呼叫 jni 層的方法來處理,有興趣可以看下相關的原始碼:

/frameworks/native/libs/gui/Surface.cpp /frameworks/base/core/jni/android_view_Surface.cpp
複製程式碼

SurfaceHolder原始碼

SurfaceHolder 實際上是一個介面,它充當的是 Controller 的角色。

package android.view;

import android.graphics.Canvas;
import android.graphics.Rect;

/**
 * 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 {@link SurfaceView} class.
 *
 * <p>When using this interface from a thread other than the one running
 * its {@link SurfaceView}, you will want to carefully read the
 * methods
 * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}.
 */

public interface SurfaceHolder {

    ...otherCodes

    public interface Callback {

        public void surfaceCreated(SurfaceHolder holder);


        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height)
;


        public void surfaceDestroyed(SurfaceHolder holder);
    }

    /**
     * Additional callbacks that can be received for {@link Callback}.
     */

    public interface Callback2 extends Callback {

        void surfaceRedrawNeeded(SurfaceHolder holder);


        default void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) {
            surfaceRedrawNeeded(holder);
            drawingFinished.run();
        }
    }


    public void addCallback(Callback callback);


    public void removeCallback(Callback callback);


    public boolean isCreating();

    @Deprecated
    public void setType(int type);


    public void setFixedSize(int width, int height);


    public void setSizeFromLayout();


    public void setFormat(int format);


    public void setKeepScreenOn(boolean screenOn);


    public Canvas lockCanvas();


    public Canvas lockCanvas(Rect dirty);


    default Canvas lockHardwareCanvas() {
        throw new IllegalStateException("This SurfaceHolder doesn't support lockHardwareCanvas");
    }


    public void unlockCanvasAndPost(Canvas canvas);


    public Rect getSurfaceFrame();


    public Surface getSurface();
}
複製程式碼
  1. 關鍵介面 Callback

callback 是 SurfaceHolder 內部的一個介面,例子中就實現了這個介面來控制繪製動畫的執行緒。
介面中有以下三個方法

  • public void surfaceCreated(SurfaceHolder holder); + Surface 第一次被建立時被呼叫,例如 SurfaceView 從不可見狀態到可見狀態時
    • 在這個方法被呼叫到 surfaceDestroyed 方法被呼叫之前的這段時間,Surface 物件是可以被操作的,拿 SurfaceView 來說就是如果 SurfaceView 只要是在介面上可見的情況下,就可以對它進行繪圖和繪製動畫
    • 這裡還有一點需要注意,Surface 在一個執行緒中處理需要渲染的影像資料,如果你已經在另一個執行緒裡面處理了資料渲染,就不需要在這裡開啟執行緒對 Surface 進行繪製了
  • public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
    • Surface 大小和格式改變時會被呼叫,例如橫豎屏切換時如果需要對 Sufface 的影像和動畫進行處理,就需要在這裡實現
    • 這個方法在 surfaceCreated 之後至少會被呼叫一次
  • public void surfaceDestroyed(SurfaceHolder holder);
    • Surface 被銷燬時被呼叫,例如 SurfaceView 從可見到不可見狀態時
    • 在這個方法被呼叫過之後,就不能夠再對 Surface 物件進行任何操作,所以需要保證繪圖的執行緒在這個方法呼叫之後不再對 Surface 進行操作,否則會報錯

SurfaceView原始碼

SurfaceView,就是用來顯示 Surface 資料的 View,通過 SurfaceView 來看到 Surface 的資料。

public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {

    //OtherCodes

   final ArrayList<SurfaceHolder.Callback> mCallbacks
            = new ArrayList<SurfaceHolder.Callback>();//重要的回撥集合

    final int[] mLocation = new int[2];

    final ReentrantLock mSurfaceLock = new ReentrantLock();
    // Current surface in use
    final Surface mSurface = new Surface();       

    //OtherCodes

     /**
     * Return the SurfaceHolder providing access and control over this
     * SurfaceView's underlying surface.
     *
     * @return SurfaceHolder The holder of the surface.
     */

     //獲取當前持有的holder
    public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }


    //surfaceView持有mSurfaceHolder的final類。對上面的surface進行管理

    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        private static final String LOG_TAG = "SurfaceHolder";

        @Override
        public boolean isCreating() {
            return mIsCreating;
        }

         //把holder.callback新增到上面回撥集合
        @Override
        public void addCallback(Callback callback) {
            synchronized (mCallbacks) {
                // This is a linear search, but in practice we'll
                // have only a couple callbacks, so it doesn't matter.
                if (mCallbacks.contains(callback) == false) {
                    mCallbacks.add(callback);
                }
            }
        }

        @Override
        public void removeCallback(Callback callback) {
            synchronized (mCallbacks) {
                mCallbacks.remove(callback);
            }
        }

        @Override
        public void setFixedSize(int width, int height) {
            if (mRequestedWidth != width || mRequestedHeight != height) {
                mRequestedWidth = width;
                mRequestedHeight = height;
                requestLayout();
            }
        }

        @Override
        public void setSizeFromLayout() {
            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
                mRequestedWidth = mRequestedHeight = -1;
                requestLayout();
            }
        }

        @Override
        public void setFormat(int format) {
            // for backward compatibility reason, OPAQUE always
            // means 565 for SurfaceView
            if (format == PixelFormat.OPAQUE)
                format = PixelFormat.RGB_565;

            mRequestedFormat = format;
            if (mSurfaceControl != null) {
                updateSurface();
            }
        }

        /**
         * @deprecated setType is now ignored.
         */

        @Override
        @Deprecated
        public void setType(int type) { }

        @Override
        public void setKeepScreenOn(boolean screenOn) {
            runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
        }

        /**
         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
         *
         * After drawing into the provided {@link Canvas}, the caller must
         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
         *
         * The caller must redraw the entire surface.
         * @return A canvas for drawing into the surface.
         */


         //封裝的鎖Canvas
        @Override
        public Canvas lockCanvas() {
            return internalLockCanvas(nullfalse);
        }

        /**
         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
         *
         * After drawing into the provided {@link Canvas}, the caller must
         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
         *
         * @param inOutDirty A rectangle that represents the dirty region that the caller wants
         * to redraw.  This function may choose to expand the dirty rectangle if for example
         * the surface has been resized or if the previous contents of the surface were
         * not available.  The caller must redraw the entire dirty region as represented
         * by the contents of the inOutDirty rectangle upon return from this function.
         * The caller may also pass <code>null</code> instead, in the case where the
         * entire surface should be redrawn.
         * @return A canvas for drawing into the surface.
         */

        @Override
        public Canvas lockCanvas(Rect inOutDirty) {
            return internalLockCanvas(inOutDirty, false);
        }

        @Override
        public Canvas lockHardwareCanvas() {
            return internalLockCanvas(nulltrue);
        }

         //內部鎖畫布
        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
            mSurfaceLock.lock();

            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);

            Canvas c = null;
            if (!mDrawingStopped && mSurfaceControl != null) {
                try {
                    //最終是鎖住持有的surface的canvas,上面說的surface的lockcanvas是jni方法
                    if (hardware) {

                        c = mSurface.lockHardwareCanvas();
                    } else {
                        c = mSurface.lockCanvas(dirty);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }

            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }

            // If the Surface is not ready to be drawn, then return null,
            // but throttle calls to this function so it isn't called more
            // than every 100ms.
            long now = SystemClock.uptimeMillis();
            long nextTime = mLastLockTime + 100;
            if (nextTime > now) {
                try {
                    Thread.sleep(nextTime-now);
                } catch (InterruptedException e) {
                }
                now = SystemClock.uptimeMillis();
            }
            mLastLockTime = now;
            mSurfaceLock.unlock();

            return null;
        }

        /**
         * Posts the new contents of the {@link Canvas} to the surface and
         * releases the {@link Canvas}.
         *
         * @param canvas The canvas previously obtained from {@link #lockCanvas}.
         */


         //封裝的解鎖canvas,實際也是呼叫Surafce的解鎖canvas
        @Override
        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            mSurfaceLock.unlock();
        }
        //獲取當前持有的 surface
        @Override
        public Surface getSurface() {
            return mSurface;
        }

        @Override
        public Rect getSurfaceFrame() {
            return mSurfaceFrame;
        }
    };



}
    ...otherCodes
複製程式碼

SurfaceView的使用

surfaceview提供了兩個執行緒:UI執行緒和渲染執行緒。

  1. 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI執行緒裡呼叫,一般來說就是應用程式的主執行緒。渲染執行緒訪問的各種變數應該做同步處理。
  2. 由於surface可能被銷燬,它只在SurfaceHolder.Callback.surfaceCreated()和SurfaceHolder.Callback.surfaceDestroyed()之間有效,所以要確保渲染執行緒訪問的是合法有效的surface。

  3. SurfaceView是個重要的繪圖容器,它可以在主執行緒外的執行緒中向螢幕繪圖,這樣可以避免畫圖任務繁重的時候造成主執行緒阻塞,從而提高了程式的反應速度。在遊戲開發中多用到SurfaceView,遊戲中的背景、人物、動畫等等儘量在畫布canvas中畫出。
    可以把Surface理解為視訊記憶體的一個對映,寫入到Surface的內容可以直接複製到視訊記憶體從而顯示出來,這會使得顯示速度非常快),Surface被銷燬之前必須結束。
    應用過程:
1class MyView extends SurfaceView implements SurfaceHolder.Callback 
2. SurfaceView.getHolder()獲得SurfaceHolder物件 
3. SurfaceHolder.addCallback(callback)新增回撥函式 
4. SurfaceHolder.lockCanvas()獲得Canvas物件並鎖定畫布 
5. Canvas繪畫 
6. SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結束鎖定畫圖,並提交改變,將圖形顯示。
複製程式碼

其中4,5,6都應該在繪圖執行緒中執行,1,2,3同步變數並且在主執行緒中執行。
SurfaceHolder可以看成是一個surface控制器,用來操縱surface。處理它的Canvas上畫的效果和動畫,控制表面,大小,畫素等。

SurfaceView的例子

public class MySurfaceView extends SurfaceView implements RunnableSurfaceHolder.Callback {  
    private SurfaceHolder mHolder; // 用於控制SurfaceView
    private Thread t; // 宣告一條執行緒
    private volatile boolean flag; // 執行緒執行的標識,用於控制執行緒
    private Canvas mCanvas; // 宣告一張畫布
    private Paint p; // 宣告一支畫筆
    float m_circle_r = 10;

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

        mHolder = getHolder(); // 獲得SurfaceHolder物件
        mHolder.addCallback(this); // 為SurfaceView新增狀態監聽
        p = new Paint(); // 建立一個畫筆物件
        p.setColor(Color.WHITE); // 設定畫筆的顏色為白色
        setFocusable(true); // 設定焦點
    }

    /**
     * 當SurfaceView建立的時候,呼叫此函式
     */

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        t = new Thread(this); // 建立一個執行緒物件
        flag = true// 把執行緒執行的標識設定成true
        t.start(); // 啟動執行緒
    }

    /**
     * 當SurfaceView的檢視發生改變的時候,呼叫此函式
     */

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

    /**
     * 當SurfaceView銷燬的時候,呼叫此函式
     */

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        flag = false// 把執行緒執行的標識設定成false
        mHolder.removeCallback(this);
    }

    /**
     * 當螢幕被觸控時呼叫
     */

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        return true;
    }

    /**
     * 當使用者按鍵時呼叫
     */

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        surfaceDestroyed(mHolder);
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void run() {
        while (flag) {
            try {
                synchronized (mHolder) {
                    Thread.sleep(100); // 讓執行緒休息100毫秒
                    Draw(); // 呼叫自定義畫畫方法
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (mCanvas != null) {
                    // mHolder.unlockCanvasAndPost(mCanvas);//結束鎖定畫圖,並提交改變。

                }
            }
        }
    }

    /**
     * 自定義一個方法,在畫布上畫一個圓
     */

    protected void Draw() {
        mCanvas = mHolder.lockCanvas(); // 獲得畫布物件,開始對畫布畫畫
        if (mCanvas != null) {
            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            paint.setColor(Color.BLUE);
            paint.setStrokeWidth(10);
            paint.setStyle(Style.FILL);
            if (m_circle_r >= (getWidth() / 10)) {
                m_circle_r = 0;
            } else {
                m_circle_r++;
            }
            Bitmap pic = ((BitmapDrawable) getResources().getDrawable(
                    R.drawable.qq)).getBitmap();
            mCanvas.drawBitmap(pic, 00, paint);
            for (int i = 0; i < 5; i++)
                for (int j = 0; j < 8; j++)
                    mCanvas.drawCircle(
                            (getWidth() / 5) * i + (getWidth() / 10),
                            (getHeight() / 8) * j + (getHeight() / 16),
                            m_circle_r, paint);
            mHolder.unlockCanvasAndPost(mCanvas); // 完成畫畫,把畫布顯示在螢幕上
        }
    }
}
複製程式碼

參考連結

  1. https://tech.youzan.com/surfaceview-sourcecode/
  2. https://blog.csdn.net/luoshengyang/article/details/8303098
  3. https://www.zhihu.com/question/30922650
  4. https://blog.csdn.net/jam96/article/details/53180093
  5. https://www.jianshu.com/p/15060fc9ef18

相關文章