The concept of SurfaceView

Contact SurfaceView for the first time, find a lot of information to understand the concept of SurfaceView, summarize the results of the data. Android has a special view called SurefaceView, which is different from TextView and Button.

  1. It has its own special drawing surface, that is, it does not share a drawing surface with its host window
  2. SurefaceView’s UI can be drawn in a separate thread
  3. Because it does not occupy the main thread resources, on the one hand, it can achieve a complex and efficient UI, and on the other hand, it does not cause user input to be unresponsive.

Combined with these features, SurfaceView is generally used to display dynamic or complex images and animations.

Why SurfaceView

Normal Spaces, such as TextView and Button, draw their UI on top of the drawing Surface of the host window, meaning that their UI is drawn in the main thread of the application. But in addition to drawing the UI, the main thread must respond to user input, gestures, etc. Otherwise, the system will assume that the application is not responding, ANR.

Therefore, for some game graphics, or camera, video playback, etc., the UI is complex and requires efficient drawing, therefore, their UI is not suitable for drawing in the main thread. You must create a separate drawing Surface for UI views that need to be complex and efficient, and use separate threads to draw those view UIs.

Understand SurfaceView formation

Each window has a layer in the SurfaceFlinger service that describes its drawing surface. For Windows with SurfaceView, each SurfaceFlinger service has a separate Layer or LayerBuffer that describes its drawing surface to distinguish it from the drawing surface of its host window.

Both the LayerBuffer and Layer are based on the LayerBase class, that is, the SurfaceFlinger service abstracts all the LayerBuffers and layers as layerBases. As a result, their UIs can be drawn and synthesized using a unified process. Since the drawing and composition of LayerBuffer is similar to that of Layer

For the sake of describing the implementation of the SurfaceView, let’s assume that in addition to a DecorView top-level view, there are two TextView controls and a SurfaceView view in the view structure of an Activity window. The Activity window then has two layers or one Layer of a LayerBuffer in the SurfaceFlinger service, as shown in Figure 1

Figure 1. Drawing surface diagram of SurfaceView and its host Activity window

In Figure 1, the Top view of the Activity window, DecorView, and the UI of its two TextView controls are drawn on top of the same Layer in the SurfaceFlinger service. The SurfaceView UI is drawn on top of another Layer or LayerBuffer in the SurfaceFlinger service.

Note that the Z-axis position of the Layer used to describe the SurfaceView or the LayerBuffer is smaller than the z-axis position of the Layer used to host the Activity window, but the former makes a “hole” in the latter so that its UI is visible to the user. In fact, the “hole” the SurfaceView makes in its host Activity window is nothing more than a transparent area in its host Activity window.

After describing the general implementation of SurfaceView, we will analyze its implementation process in detail, including the creation of its drawing surface, the process of digging holes in the host window, and the drawing process. Refer to the link: https://blog.csdn.net/luoshengyang/article/details/8661317/

SurfaceView drawing process

Although the SurfaceView has a separate drawing surface, it is still a node in the view structure of the host window, so it can still participate in the drawing process of the host window. As can be seen from the previous article on Measure, Layout and Draw process analysis of Android application window, the member functions Draw or dispatchDraw of each sub-view will be called during the process of window drawing. So that they can draw their own UI. The member functions draw and dispatchDraw of the SurfaceView class are implemented as follows:

    @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);

    }

Copy the code

The canvas parameters of the Member functions draw and dispatchDraw of the SurfaceView class describe canvases built on the drawing surface of the host window, so any UI drawn on this canvas appears on the drawing surface of the host window.

The SurfaceView member function draw is used to draw its UI on the drawing surface of the host window, but if the SurfaceView is not used as the host window panel, The value of its member variables mWindowType is not equal to WindowManager. LayoutParams. TYPE_APPLICATION_PANEL, SurfaceView class member functions the draw will simply it occupied area mapped to black.

The SurfaceView member dispatchDraw draws a subview of the SurfaceView, but we also see that if the SurfaceView is not being used as the host window panel, So the SurfaceView member function dispatchDraw simply paints the area it’s occupying as black, and it updates its UI by calling another member function, updateWindow, Essentially asking the WindowManagerService service to lay out its UI and create a drawing surface

As you can see from the implementation of the Member functions Draw and dispatchDraw of the SurfaceView class, all the SurfaceView does on the drawing surface of its host window is to paint the area it takes up black, but not much else. This is because the SurfaceView’s UI is intended to be displayed on its own drawing surface. Next we’ll look at how to do UI painting on the SurfaceView’s drawing surface.

As can be seen from the previous article on Measure, Layout and Draw processes in the Android app window, if you want to Draw UI on a drawing surface, then perform the following operations in sequence:

(1). Create a Canvas based on the drawing surface, that is, obtain a Canvas object.

(2). Use the drawing interface provided by the Canvas class to draw arbitrary UI on the previously obtained Canvas.

(3). Submit the canvas buffer that has been filled with UI data to the SurfaceFlinger service so that the SurfaceFlinger service can compose it to the screen.

Copy the code

Analysis of the source code

Analysis of Surface, SurfaceHolder, SurfaceView three classes

Surface:

The Surface of Android is a place to draw graphics or images. For views and their subclasses, they are drawn on the Surface. Each Surface object is composed into the frameBuffer by Surfaceflinger. Each Surface is double buffered (essentially two threads, one rendering thread and one UI update thread). It has a backBuffer and a frontBuffer. Canvas object is created in Surface to manage Surface drawing operations. Canvas corresponds to Bitmap and stores Surface contents.

SurfaceView:

SurfaceView is a subclass of View, and implements the Parcelable interface and implements the Parcelable interface, which is embedded with a Surface for drawing, SurfaceView can control the format and size of the Surface, And where to draw the Surface. The Surface is where the data is managed and the SurfaceView is where the data is displayed.

SurfaceHolder:

A container that manages the SurfaceHolder. The SurfaceHolder is an interface that can be understood as a Surface listener. Listen to the creation of the Surface through the Callback method addCallback (SurfaceHolder.callback Callback) by fetching the Canvas object in the Surface and locking it. After modifying the data in the Surface, the obtained Canvas object releases the synchronization lock and submits the state and image of the Surface to display the new image data. –

Finally, call getHolder in the SurfaceView to obtain the SurfaceHolder corresponding to the Surface in the current SurfaceView, and the SurfaceHolder begins to manage the Surface. This is actually a little bit easier to understand if you follow the MVC model. M:Surface(image data), V:SurfaceView(image display), C:SurfaceHolder(image data management).

Surface source

/ * *

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

* /


public class Surface implements Parcelable {  

    // code......

}

Copy the code

The Surface class implements the Parcelable interface for serialization (which is mainly used to pass Surface objects between processes). It is used to process data from the screen buffer. Handle onto a raw buffer that is being managed by the screen compositor. The raw image buffer is managed by the screen Compositor.

  • A HANDLE to a native buffer managed by a screen compositor is used to store the memory address of a data object in memory, which is not fixed. It is simply a 32-bit (or 64-bit) unsigned integer in terms of data type. – The Surface acts as a handle to the raw buffer and its contents — the raw buffer is used to hold the pixel data of the current window — so the Surface is used for drawing in Android. Specifically, the Canvas in the Surface defines Canvas objects related to the Canvas
private final Canvas mCanvas = new CompatibleCanvas();  

Copy the code

In Java, drawing is usually done on a Canvas object, and Surface contains a Canvas object, where CompatibleCanvas is an inner class in Surface. Java. It contains a Matrix object Matrix (variable name mOrigMatrix). Matrix Matrix is an area of memory where the various drawing operations on the View are stored. Surface has an internal class CompatibleCanvas, which is designed to be compatible with Android screens of various resolutions and process different image data according to different screen resolutions.

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);

        }

    }

Copy the code

1. Two important methods of Surface: A lot of the methods in Surface are native, lockCanvas and unlockCanvasAndPost are native, not lockCanvas and unlockCanvasAndPost in SurfaceHolder, The SurfaceHolder is just a wrapper.

  • LockCanvas (…). + Gets a Canvas for drawing into this surface. Get the Canvas to draw on + After drawing into the provided Canvas, the caller must invoke unlockCanvasAndPost to post the new contents to the surface. After drawing a frame of data, the unlockCanvasAndPost method is called to unlock the Canvas, and then the drawn image is posted to the current screen for display. This means that you must wait for the frame being drawn to complete and unlock the Canvas before you can do anything else + the actual locking of the Canvas is done in the JNI layer
  • UnlockCanvasAndPost (…). + Posts the new contents of the Canvas to the surface and releases the Canvas. The Canvas object is released when the newly drawn image content is passed to the Surface (the actual release is done in the JNI layer)

Urface’s lockCanvas and unlockCanvasAndPost methods are finally called jNI layer methods to handle, you can look at the relevant source code:

/frameworks/native/libs/gui/Surface.cpp /frameworks/base/core/jni/android_view_Surface.cpp

Copy the code

SurfaceHolder source

The SurfaceHolder is actually an interface that acts as a 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(a);



    @Deprecated

    public void setType(int type);





    public void setFixedSize(int width, int height);





    public void setSizeFromLayout(a);





    public void setFormat(int format);





    public void setKeepScreenOn(boolean screenOn);





    public Canvas lockCanvas(a);





    public Canvas lockCanvas(Rect dirty);





    default Canvas lockHardwareCanvas(a) {

        throw new IllegalStateException("This SurfaceHolder doesn't support lockHardwareCanvas");

    }





    public void unlockCanvasAndPost(Canvas canvas);





    public Rect getSurfaceFrame(a);





    public Surface getSurface(a);

}

Copy the code
  1. Key interface Callback

Callback is an interface inside the SurfaceHolder, which is implemented in the example to control the thread that draws the animation. There are three methods in the interface

  • public void surfaceCreated(SurfaceHolder holder); + is called when the Surface is first created, such as when the SurfaceView goes from invisible to visible
    • The Surface object can be manipulated until the surfaceDestroyed method is called. In the case of the SurfaceView, it can be drawn and animated as long as the SurfaceView is visible on the screen
    • One more thing to note here is that the Surface handles rendering data in one thread. If you’re already handling rendering data in another thread, you don’t need to open the thread to render the Surface
  • public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
    • This is called when the Surface size and format changes, for example if Sufface images and animations need to be processed when switching between vertical and horizontal screens
    • This method will be called at least once after surfaceCreated
  • public void surfaceDestroyed(SurfaceHolder holder);
    • Called when the Surface is destroyed, such as when the SurfaceView changes from visible to invisible
    • After this method is called, you can’t do anything with the Surface object, so you need to make sure that the drawing thread doesn’t do anything with the Surface after this method is called, otherwise an error will be reported

SurfaceView source

The SurfaceView is the View used to display Surface data. The SurfaceView is used to View Surface data.

public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {



    //OtherCodes



   final ArrayList<SurfaceHolder.Callback> mCallbacks

            = new ArrayList<SurfaceHolder.Callback>();// Important callback collection



    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.

* /


     // Get the currently held holder

    public SurfaceHolder getHolder(a) {

        return mSurfaceHolder;

    }





    //surfaceView holds the mSurfaceHolder final class. Manage the surface above



    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {

        private static final String LOG_TAG = "SurfaceHolder";



        @Override

        public boolean isCreating(a) {

            return mIsCreating;

        }



         // Add holder.callback to the above callback collection

        @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(a) {

            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.

* /




         // Encapsulate the lock Canvas

        @Override

        public Canvas lockCanvas(a) {

            return internalLockCanvas(null.false);

        }



        / * *

         * 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(a) {

            return internalLockCanvas(null.true);

        }



         // Inside lock canvas

        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 {

                    // Lock the canvas of any surface. The surface lockCanvas is 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}.

* /




         // Encapsulate the unlocked canvas, which is actually the unlocked canvas that calls Surafce

        @Override

        public void unlockCanvasAndPost(Canvas canvas) {

            mSurface.unlockCanvasAndPost(canvas);

            mSurfaceLock.unlock();

        }

        // Get the currently held Surface

        @Override

        public Surface getSurface(a) {

            return mSurface;

        }



        @Override

        public Rect getSurfaceFrame(a) {

            return mSurfaceFrame;

        }

    };







}

. otherCodes

Copy the code

The use of SurfaceView

The SurfaceView provides two threads: the UI thread and the render thread.

  1. All SurfaceView and SurfaceHolder.callback methods should be called in the UI thread, which is generally the main thread of the application. Variables accessed by the render thread should be synchronized.
  2. Due to the surface may be destroyed, it only in the SurfaceHolder. Callback. SurfaceCreated () and SurfaceHolder. Callback. SurfaceDestroyed () between effective, So make sure the render thread is accessing a valid surface.
  3. The SurfaceView is an important drawing container. It can draw to the screen in a thread outside the main thread. This avoids the main thread blocking when drawing heavy tasks, thus improving the response time of the program. In game development, SurfaceView is often used. The background, characters, animations and so on in the game are drawn on the canvas as far as possible. The Surface can be thought of as a mapping of video memory. The content written to the Surface can be copied directly to video memory to display, which makes the display very fast. Application process:
1class MyView extends SurfaceView implements SurfaceHolder.Callback 

2. SurfaceView.getHolder() to obtainSurfaceHolderobject

3. SurfaceHolder.addCallback(callbackAdd a callback function

4. SurfaceHolder.lockCanvas() to obtainCanvasObject and lock the canvas

5. Canvaspainting

6. SurfaceHolder.unlockCanvasAndPost(Canvas canvas) end the lock drawing, and submit changes to the graphic display.

Copy the code

4,5, and 6 should all be executed in the drawing thread, and 1,2, and 3 synchronize variables and execute in the main thread. The SurfaceHolder can be thought of as a Surface controller that manipulates the surface. Handles its Canvas drawing effects and animations, controlling surfaces, sizes, pixels, etc.

An example of SurfaceView

public class MySurfaceView extends SurfaceView implements Runnable.SurfaceHolder.Callback {  

    private SurfaceHolder mHolder; // To control the SurfaceView

    private Thread t; // Declare a thread

    private volatile boolean flag; // The thread running identifier, used to control the thread

    private Canvas mCanvas; // Declare a canvas

    private Paint p; // Declare a brush

    float m_circle_r = 10;



    public MySurfaceView(Context context) {

        super(context);



        mHolder = getHolder(); // Get the SurfaceHolder object

        mHolder.addCallback(this); // Add a status listener to the SurfaceView

        p = new Paint(); // Create a brush object

        p.setColor(Color.WHITE); // Set the brush color to white

        setFocusable(true); // Set the focus

    }



    / * *

* This function is called when the SurfaceView is created

* /


    @Override

    public void surfaceCreated(SurfaceHolder holder) {

        t = new Thread(this); // Create a thread object

        flag = true// Set the thread running flag to true

        t.start(); // Start the thread

    }



    / * *

* This function is called when the SurfaceView view changes

* /


    @Override

    public void surfaceChanged(SurfaceHolder holder, int format, int width,

            int height)
 
{

    }



    / * *

* This function is called when the SurfaceView is destroyed

* /


    @Override

    public void surfaceDestroyed(SurfaceHolder holder) {

        flag = false// Set the thread running flag to false

        mHolder.removeCallback(this);

    }



    / * *

* Called when the screen is touched

* /


    @Override

    public boolean onTouchEvent(MotionEvent event) {



        return true;

    }



    / * *

* Called when the user presses a key

* /


    @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(a) {

        while (flag) {

            try {

                synchronized (mHolder) {

                    Thread.sleep(100); // Let the thread rest for 100 milliseconds

                    Draw(); // Call the custom draw method

                }

            } catch (InterruptedException e) {

                e.printStackTrace();

            } finally {

                if(mCanvas ! =null) {

                    // mHolder.unlockCanvasAndPost(mCanvas); // End the lock drawing and commit the changes.



                }

            }

        }

    }



    / * *

* Create a custom method to draw a circle on the canvas

* /


    protected void Draw(a) {

        mCanvas = mHolder.lockCanvas(); // Get the canvas object and start drawing on the canvas

        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, 0.0, 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); // Finish drawing and display the canvas on the screen

        }

    }

}

Copy the code

Refer to the link

  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