SurfaceView is a special kind of view in Android. The biggest difference between it and TextView and Button is that it is not on the same view layer as its view container, and its UI display can be done in a separate thread, so the drawing of SurfaceView does not affect the main thread. surfaceView does not affect the main thread. Combining these features, SurfaceView is generally used to implement dynamic or complex images and animations.

MVC framework for SurfaceView

The relationship between Surface, SurfaceHolder, and SurfaceView is essentially what is known as MVC, or Model-View-Controller. Model means model, or data model, or more simply, data, which is Surface in this case; View means view, which represents the user interaction interface, which is SurfaceView in this case; SurfaceHolder can obviously be understood as the Controller in MVC. (controller).

Application Scenarios

Unlike normal controls, SurfaceView can run outside the main thread and does not need to respond to user input in a timely manner, nor does it cause ANR problems. surfaceView is generally used for complex UI and efficient image display for games, video, photography, etc., which require a separate thread for image processing.

Analyze the source code

Analyze the Surface, SurfaceHolder, and SurfaceView classes

Surface

android.view.SurfaceView

1
2
3
4
5
6
/**
 * Handle onto a raw buffer that is being managed by the screen compositor.
 */
public class Surface implements Parcelable {  
    // code......
}

First, look at the Surface class, which implements the Parcelable interface for serialization (here it is mainly used to pass surface objects between processes) and is used to handle the data in the screen display buffer, which is commented in the source code as: Handle onto a raw buffer that is being managed by the screen compositor. Surface is a handle to a raw buffer that is being managed by the screen compositor.

A handle to the raw buffer managed by the screen compositor (similar to a handle) - Explanation of the term: Handle, English: HANDLE, the memory address of a data object is obtained after it enters memory, but the memory address is not fixed and a handle is needed to store the memory address where the content is located. In terms of data type it is just a 32-bit (or 64-bit) unsigned integer. - Surface acts as a handle to get the source buffer and its contents - the raw buffer is used to store the pixel data of the current window - so it is clear that Surface is the place for drawing in Android, specifically it is the Canvas in Surface Surface defines the Canvas object related to the canvas

1
private final Canvas mCanvas = new CompatibleCanvas();  

Java, drawing is usually done on a Canvas object, Surface also contains a Canvas object, here CompatibleCanvas is an internal class in Surface.java, which contains a matrix object Matrix (variable name mOrigMatrix). The Matrix Matrix is a memory area where all the drawing operations for the View are stored. Surface has an internal class CompatibleCanvas, which is designed to be compatible with Android screens of various resolutions and to handle different image data according to the resolution of different screens.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
        }
    }

Two important methods

It is important to note that the lockCanvas is not the lockCanvas and unlockCanvasAndPost methods that are called by the SurfaceHolder object when the SurfaceView is actually used for drawing. The methods used in the actual example are wrapped inside the SurfaceView after wrapping these two methods. - lockCanvas(…) + Gets a Canvas object for drawing into this surface. + After drawing into the provided Canvas, the caller must invoke 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 needs to be called to unlock the canvas and then post the drawn image to the current screen. When a Canvas is being drawn, it is locked, meaning it must wait for the frame being drawn to finish and unlock the canvas before doing anything else + The actual locking of the Canvas is done at the jni level - unlockCanvasAndPost(…) + Posts the new contents of the Canvas to the surface and releases the Canvas. The actual release process is done at the jni layer)

Surface’s lockCanvas and unlockCanvasAndPost methods end up calling the jni layer methods to handle the process. /frameworks/native/libs/gui/Surface.cpp /frameworks/base/core/jni/android_view_Surface.cpp

SurfaceHolder

SurfaceHolder SurfaceHolder is actually an interface that acts as a controller.

1
2
public interface SurfaceHolder {  
}

Let’s see what the commentary says

  • 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. The bare bones Controller role that controls the size and format of the Surface, monitors changes to the Surface (and handles changes to the Surface in a callback function)
  • When using this interface from a thread other than the one running its SurfaceView, you will want to carefully read the methods - When using this interface from a thread other than the one running its SurfaceView, you will want to carefully read the methods of Callback.surfaceCreated() If you are using a subthread to handle the drawing of the SurfaceView, you will need to use the surfaceCreated method of the key interface Callback, which is described next. As you can see in the example given earlier, the thread that draws the animation is opened in the surfaceCreated method

Critical Interfaces 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); + Surface is called the first time it is created, for example when the SurfaceView goes from the invisible state to the visible state + Between the time this method is called and the time the Surface objects can be manipulated between the time this method is called and the time the surfaceDestroyed method is called, which in the case of SurfaceView means that if the SurfaceView is visible on the interface, it can be drawn and animated + Another thing to note here is that Surface If you have already rendered the data in another thread, you don’t need to open a thread here to draw the Surface - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height); + The surface will be called when the size and format change, for example, if you need to handle Sufface images and animations when switching between horizontal and vertical screens, you need to implement it here + This method will be called at least once after the surfaceCreated - public void surfaceDestroyed(SurfaceHolder holder); + Surface is called when it is destroyed, e.g. when the SurfaceView is invisible from the visible state + After this method is called, no more operations can be performed on the Surface object. So you need to make sure that the drawing thread does not operate on the Surface after this method is called, otherwise it will report an error

SurfaceView

android.view.SurfaceView

SurfaceView, which is the View used to display the Surface data, is used to see the Surface data through the SurfaceView.

1
2
3
public class SurfaceView extends View {  
    // code.....
}

Analyze the comments on the SurfaceView in the source code

  • Provides a dedicated drawing surface embedded inside of a view hierarchy.
  • the SurfaceView punches a hole in its window to allow its surface to be displayed. SurfaceView
  • The Surface will be created for you while the SurfaceView’s window is visible.
  • you should implement SurfaceHolder.Callback#surfaceCreated and SurfaceHolder.Callback#surfaceDestroyed to discover when the Surface is created and destroyed as the window is shown and hidden.

Using SurfaceView

The example shows how to use the SurfaceView

  1. you need to implement the SurfaceHolder.Callback interface
  2. Callbackin thesurfaceCreatedmethod ofSurfaceHolder.Callback to open a thread to draw the animation frame by frame.
  3. you need to end the drawing thread in the surfaceDestroyed method of SurfaceHolder.Callback and call the removeCallbck method of SurfaceHolder.
  4. the lockCanvas method needs to be called before each frame of the drawing thread starts to lock the canvas for drawing
  5. After drawing a frame of data, you need to call the unlockCanvasAndPost method to submit the data to display the image

Example

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.Canvas;  
import android.graphics.Color;  
import android.graphics.Paint;  
import android.graphics.Paint.Style;  
import android.graphics.drawable.BitmapDrawable;  
import android.view.SurfaceHolder;  
import android.view.SurfaceView;  
import android.view.KeyEvent;  
import android.view.MotionEvent;  
import android.view.SurfaceHolder.Callback;

public class MySurfaceView extends SurfaceView implements Runnable, SurfaceHolder.Callback {  
    private SurfaceHolder mHolder; // Used to control SurfaceView
    private Thread t; // Declare a thread
    private volatile boolean flag; // Identifier of the thread running, used to control the thread
    private Canvas mCanvas; // Declare a canvas
    private Paint p; // Declare a paintbrush
    float m_circle_r = 10;

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

        mHolder = getHolder(); // Get SurfaceHolder object
        mHolder.addCallback(this); // Adding a state listener to SurfaceView
        p = new Paint(); // Create a brush object
        p.setColor(Color.WHITE); // Set the color of the brush to white
        setFocusable(true); // Set 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 run flag to true
        t.start(); // Start threads
    }

    /**
     * Call this function when the SurfaceView's 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 run 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() {
        while (flag) {
            try {
                synchronized (mHolder) {
                    Thread.sleep(100); // Let threads rest for 100 milliseconds
                    Draw(); // Calling custom drawing methods
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (mCanvas != null) {
                    // mHolder.unlockCanvasAndPost(mCanvas);//End the lock drawing and commit the changes.

                }
            }
        }
    }

    /**
     * Customize a method to draw a circle on the canvas
     */
    protected void Draw() {
        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); // Complete the drawing and display the canvas on the screen
        }
    }
}

Some problems that may be encountered

Why does a SurfaceView display a black area when placed in a layout file without a task image drawn?

The reason for this phenomenon can be seen in the draw and dispatchDraw methods of the SurfaceView, where the windownType variable is initialized to WindowManager. MEDIA, so the entire Canvas will be painted black during the process of creating and drawing this View

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Default value of windowType
int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;

@Override
    public void draw(Canvas canvas) {
        if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
            // 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);
    }

Reference https://tech.youzan.com/surfaceview-sourcecode/