preface

This is the third part of the Android drawing process. In Windows Connection and Surface Creation, we introduce how the application is associated with SurfaceFlinger and creates a layer that can be drawn.

And then we’re going to go down and see how the view is drawn on the Surface that we’ve created.

As a summary of the review record, the same is only a rough, process of exploration.

The goal here is to get a general idea by ignoring some of the esoteric details.

If there is any mistake, please point it out and make progress together

The source code parsing

Now that you have layers to draw, it’s natural to go to the familiar view to draw the three scutters — Measure, Layout and Draw

The following code is based on Android 11 (Android R).

1 measure

The first is to measure the size of all views in the View tree, which is the performMeasure method called by performTraversals.

PS: Of course, this isn’t actually the only place to call the performMeasure method.

// ==============android.view.ViewRootImpl================
private void performTraversals(a) {
    // The outer measurement mode and size (default is Window screen size)
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . }private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return; }... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); . }Copy the code

Remember what an mView is inside ViewRootImpl? In the first article, it was a DecorView that was assigned by the setView method after we created the ViewRootImpl.

And the DecorView inherits from FrameLayout which is a ViewGroup.

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec

MeasureSpec is a 32-bit integer value of type INT.

In base 2, the higher 2 bits represent SpecMode and the lower 30 bits represent SpecSize. The two values are combined by a bit operation.

Bit operations represent state management

SpecMode has three types of values:

  • UNSPECIFIEDThe parent container does not pairViewLimit the size.
  • EXACTLYThe exact size, isSpecSizeValue, generally in the setmatch_parentAnd specific values.
  • AT_MOSTThe parent container specifies an available size i.eSpecSize.ViewThe value cannot be larger than this valuewrap_content “Is set.

For the outermost DecorView’s MeasureSpec, the default is getRootMeasureSpec, via the size of the Window.

// ==============android.view.ViewRootImpl================
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}
Copy the code

DecorView then undecorates the ViewGroup, traversing all child views until the innermost View finishes measuring.

Since ViewGroup is an abstract class, onMeasure operations are implemented in subclasses. Here, take FrameLayout as an example for analysis.

// ========== android.view.View ==============
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // Check if it is layout
    boolean optical = isLayoutModeOptical(this); . onMeasure(widthMeasureSpec, heightMeasureSpec); . }// ========== android.widget.FrameLayout ================

public class FrameLayout extends ViewGroup {
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        intcount = getChildCount(); .for (int i = 0; i < count; i++) {
            // Traverses all subviews in the View tree
            final View child = getChildAt(i);
            // Filter the GONE View
            if(mMeasureAllChildren || child.getVisibility() ! = GONE) {// Perform the measurement of the subview
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); . }}// Add the Padding size
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Compare the minimum size with the padding size
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Compare the Drawable size set inside the ViewGroup
        final Drawable drawable = getForeground();
        if(drawable ! =null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
        // Set its own size
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));    
    }
    
    int getPaddingLeftWithForeground(a) {
        returnisForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : mPaddingLeft + mForegroundPaddingLeft; }}// ============== android.view.ViewGroup ==============

protected void measureChildWithMargins(View child,
                                       int parentWidthMeasureSpec, int widthUsed,
                                       int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    // The parent view's MeasureSpec and its own LayoutParams measure the child view's MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                                                          mPaddingLeft + mPaddingRight + lp.leftMargin + 
                                                          lp.rightMargin + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                                                           mPaddingTop + mPaddingBottom + lp.topMargin + 
                                                           lp.bottomMargin + heightUsed, lp.height);
	// Execute the subview measurement process
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

Until the innermost View executes its own onMeasure.

Subclasses of View have different implementations. The default implementation is getDefaultSize to get the default width and height.

GetDefaultSize calculates the final width from the parent view’s MeasureSpec.

// ========== android.view.View ==============
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // The default implementation, different subclasses have different implementations
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// Get the recommended minimum height
protected int getSuggestedMinimumHeight(a) {
    // It is determined by layout:minHeight and background
    return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }//
protected int getSuggestedMinimumWidth(a) {
    return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;

        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
	// Indicates that the View (ViewGroup) measurement has been performed
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

// Get the default size, as determined by measureSpec passed by the parent View and the recommended minimum size
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}
Copy the code

2 layout

The performLayout method is then used to layout all views in the View tree.

// ==============android.view.ViewRootImpl================

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    finalView host = mView; .// The lowest level of DecorView
    host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }Copy the code

Inside, you execute the DecorView layout method to verify the position of all the child views inside the ViewGroup.

Explain layout parameters in advance (horrible one-letter abbreviations)

  • L: The distance between the left edge of its View and the left edge of its parent container (left)

  • T: The distance between the top edge of its View and the top edge of its parent container (top)

  • R: The distance between the right edge of its View and the left edge of its parent container (right)

  • B: The distance between the bottom edge of its View and the top edge of its parent container

// ========== android.view.ViewGroup ==============
@Override
public final void layout(int l, int t, int r, int b) {
    if(! mSuppressLayout && (mTransition ==null| |! mTransition.isChangingLayout())) {// Call the parent View method directly when no animation is executed
        if(mTransition ! =null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true; }}// ========== android.view.View ================
public void layout(int l, int t, int r, int b) {
    if((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) ! =0) {
        // If no Measure is executed, execute the Measure process again
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    
    boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
    if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); . mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; . }... }private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?
        ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    return setFrame(
        left   + parentInsets.left - childInsets.left,
        top    + parentInsets.top  - childInsets.top,
        right  + parentInsets.left + childInsets.right,
        bottom + parentInsets.top  + childInsets.bottom);
}

// Determine the position of the four vertices of the View
// initialize mLeft, mTop, mRight, mBottom
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false; f (mLeft ! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed =true;
        
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        booleansizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight);// Invalidate our old position
        invalidate(sizeChanged); // Look familiarmLeft = left; mTop = top; mRight = right; mBottom = bottom; . }return changed
}

Copy the code

The layout method uses the setFrame method to determine the position of the View’s four vertices. Once the vertex position is established, its position in the parent container is also determined.

The onLayout method is called when the vertex position changes or the PFLAG_LAYOUT_REQUIRED flag bit is added.

View and ViewGroup are both empty implementations, and the implementation varies from subclass to subclass.

Here, the FrameLayout of the DecorView is used as an example

// ========== android.widget.FrameLayout ================

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final intcount = getChildCount(); .for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if(child.getVisibility() ! = GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;
            // Use the Gravity property to determine the vertex position of the child View.// Perform the layout operation of the child View to determine the four vertex positions.child.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code

3 draw

After determining the position and size of the view, we finally get to performDraw, where the view is drawn.

// ============== android.view.ViewRootImpl ================

boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;

private void performDraw(a) {...final booleanfullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw; .booleancanUseAsync = draw(fullRedrawNeeded); . }private boolean draw(boolean fullRedrawNeeded) {
    // Get the correct Surface reference to the native object
    Surface surface = mSurface;
    Draw is not executed when there is no available surface buffer area (Layer)
    if(! surface.isValid()) {return false; }...finalRect dirty = mDirty; .boolean useAsyncReport = false;
    // The drawing area is not empty, or animation is being performed
    if(! dirty.isEmpty() || mIsAnimating ...) {if(mAttachInfo.mThreadedRenderer ! =null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            // Hardware acceleration enabled.// Render with RenderThread's GPU
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);    
            useAsyncReport = true;
        }else{
            // Use software acceleration.if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {return false; }}}...return useAsyncReport;
}
Copy the code

And draw method is divided into software drawing and hardware drawing two cases.

Personal ability is limited, here only focus on the situation of software drawing, the basic principle is similar.

For hardware rendering RenderThread see also:

Android-Surface creation process and software and hardware drawing

Android Systrace Basics (5) – SurfaceFlinger interpretation

Android Systrace Basics (9)-MainThread and RenderThread

// ============== android.view.ViewRootImpl ================ 
/ * * *@return true if drawing was successful, false if an error occurred
*/
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                             boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    // Draw with software renderer.
    finalCanvas canvas; .//1. Apply for buffer buffer and create Canvascanvas = mSurface.lockCanvas(dirty); .try{...//2. Execute View draw operation, internal call onDraw, write contents to buffer buffermView.draw(canvas); . }finally{
        // 3. Release the Canvas locked buffer to SurfaceFlinger for consumption and display the buffer contentssurface.unlockCanvasAndPost(canvas); . }...return true
}
Copy the code

3.1 lockCanvas requests GraphicBuffer

Since the Canvas of the view. draw method is created by Surface, let’s look at the lockCanvas method first.

// =============== android.view.Surface =================

private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
            throws OutOfResourcesException;

public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
    synchronized (mLock) {
        // Check whether the native reference address mNativeObject! = 0checkNotReleasedLocked(); .// Call the native method to apply for the buffer area
        mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
        returnmCanvas; }}Copy the code

The following native methods are viewed through Android Search Code, which requires climbing the wall.

Code based on Android11-RC3, limited space, only retain the key core code.

// ============== frameworks/base/core/jni/android_view_Surface.cpp ==================
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    //1. Get the native layer Surface object reference saved by Java layer Surface.
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject)); . ANativeWindow_Buffer buffer;//1. Apply for the image buffer and assign the value to buffer
    status_t err = surface->lock(&buffer, dirtyRectPtr); .//2. Create native Canvas
    graphics::Canvas canvas(env, canvasObj);
    canvas.setBuffer(&buffer, static_cast<int32_t>(surface->getBuffersDataSpace())); .// Create another reference to the surface and return it. This reference
    // should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
    // because the latter could be replaced while the surface is locked.
    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get(a); }Copy the code

The nativeLockCanvas method is roughly divided into three steps

  1. To obtain2.4Is created in the native layerSurfaceObject that is calledlockMethod to obtain a buffer image buffer object reference, assign a value tobufferThe variable.
  2. Create the native Canvas and set the buffer layer buffer, which is assigned to the Java Canvas object reference
  3. Returns the newly created Surface reference that is associated with the Native layerCanvasandBuffer.

Let’s just focus on the Surface.lock method and see how the Buffer object is retrieved.

// ============= frameworks/base/core/jni/android_view_Surface.cpp =================

status_t Surface::lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds){
   
    if(! mConnectedToCpu) {// If the Cpu is not connected, call connect to connect
        int err = Surface::connect(NATIVE_WINDOW_API_CPU);
        if (err) {
            return err;
        }
        // we're intending to do software rendering from this point
        setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
    }
	ANativeWindowBuffer* out;
    int fenceFd = - 1;
    //1. Request a GraphicBuffer and assign it to out
    status_t err = dequeueBuffer(&out, &fenceFd); .if (err == NO_ERROR) {
        //2. Assign the obtained GraphicBuffer object to backBuff and mark it as a backup buffer
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out)); .void* vaddr;
        // Lock the cached buffer to avoid access collisions
        // Map the buffer to the virtual address space of the current process. Vaddr is the starting buffer address.
        status_t res = backBuffer->lockAsync(
           GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
           newDirtyRegion.bounds(), &vaddr, fenceFd ); .if(res ! =0) {
            err = INVALID_OPERATION;
        } else {
            // Assign to a locked Buff object that points to the current applied graphics buffer
            mLockedBuffer = backBuffer;
            //3. Assign the obtained graph buffer to the external object reference.outBuffer->width = backBuffer->width; outBuffer->height = backBuffer->height; outBuffer->stride = backBuffer->stride; outBuffer->format = backBuffer->format; outBuffer->bits = vaddr; }}return err;
}
Copy the code

Again, we’ll focus on the Surface.dequeueBuffer method.

// ============= frameworks/base/core/jni/android_view_Surface.cpp ==============

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {...int buf = - 1; .// Binder cross-process calls, producers apply to the Buffer queue for available GraphicBuffer graphics buffers and assign buf to the queue index of the Buffer object
    // Producer is in the surfaceFlinger process
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                            reqFormat, reqUsage, &mBufferAge,
                                                            enableFrameTimestamps ? &frameTimestamps: nullptr); .// The object reference pointer to the specified index in the GraphicBuffer array of the Surface local map
    // The Surface and Producer are not in the same process and require a memory-mapped Buffer
    sp<GraphicBuffer>& gbuf(mSlots[buf].buffer); .if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) {
        // If no buffer is retrieved from the buffer queue,
        // Or the producer object's flag bit indicates that buffer needs to be reallocated
        if(mReportRemovedBuffers && (gbuf ! =nullptr)) {
            mRemovedBuffers.push_back(gbuf);
        }
        // Producer retrieves the buffer under the specified index and assigns the reference pointer to the buffer object to GBUF
        // Called with Binder mechanism, buffer objects of Producer process are mapped and filled into the specified index of the Surface buffer array.
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
        if(result ! = NO_ERROR) { ...// If an error occurs, cancel and release the buffer
            mGraphicBufferProducer->cancelBuffer(buf, fence);
            returnresult; }}...// Assign the buffer object reference address mapped locally to the external variable
    *buffer = gbuf.get(a); .return OK;    
}

// ============ frameworks/native/libs/gui/include/gui/Surface.h ===================
class Surface
    : public ANativeObjectBase<ANativeWindow, Surface, RefBase>
{

protected:
   	//BufferSlot corresponds to a GraphicBuffer
	struct BufferSlot {
        sp<GraphicBuffer> buffer;
        Region dirtyRegion;
    };
    // mSurfaceTexture is the interface to the surface texture server. All
    // operations on the surface texture client ultimately translate into
    // interactions with the server using this interface.
    // TODO: rename to mBufferProducer
    // This is actually the producer object passed in when constructing the native Surface
    sp<IGraphicBufferProducer> mGraphicBufferProducer;
        
	// mSlots stores the buffers that have been allocated for each buffer slot.
    // It is initialized to null pointers, and gets filled in with the result of
    // IGraphicBufferProducer::requestBuffer when the client dequeues a buffer from a
    // slot that has not yet been used. The buffer allocated to a slot will also
    // be replaced if the requested buffer usage or geometry differs from that
    // of the buffer allocated to a slot.	
    // GraphicBuffer queue array, up to 64 length array,
    // A shared memory map for SurfaceFlinger's Buffer queueBufferSlot mSlots[NUM_BUFFER_SLOTS]; . }Copy the code

This is the IGraphicBufferProducer object passed in when we created the native layer Surface in 2.4, The BufferQueueProducer analyzed in 2.2 is the Binder agent implementation class of IGraphicBufferProducer.

Regarding the dequeueBuffers and requestBuffers requested by BufferQueueProducer, we will not analyze them for the time being.

See also:

Surface GraphicBuffer application process

Graphic buffer application and consumption process and core classes

The flow chart can clearly describe the process

More on BufferQueue:

Android BufferQueue

Producers, consumers, and BufferQueue

Basic Knowledge of Android Systrace – Triple Buffer interpretation

  • Summary:
  1. throughThe Shared memoryThe way willSurfaceFlingerin-processBufferQueueThe available buffer objects obtained from the queue are mapped to the current App process, and the memory area of the buffer is locked to avoid access conflicts.
  2. Assign the buffer object to the newly created native layerCanvasObject, and in turn will native layerCanvasObject to the external Java layerCanvas

The principle of anonymous shared memory

Android anonymous shared memory usage

3.2 the View. The draw

We’ve already taken the mapping of the GraphicBuffer and assigned it to the Canvas object we created. Next is the familiar operation of drawing with Canvas.

The official notes are quite detailed.

// ========== android.view.View ================
public void draw(Canvas canvas) {
    final intprivateFlags = mPrivateFlags; mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; .// Step 1, draw the background, if needed    drawBackground(canvas); .// Step 3, draw the contentonDraw(canvas); .// Step 4, draw the children, recursively distributed to the inner child View, only in the ViewGroup implementationdispatchDraw(canvas); . }Copy the code

The familiar Canvas drawXXX series methods are used to write the displayed content to the GraphicBuffer buffer held internally.

3.3 unlockCanvasAndPost

If the Buffer is written to the device, the Buffer will be read and displayed on the device.

// ============== android.view.ViewRootImpl ================
public void unlockCanvasAndPost(Canvas canvas) {
    synchronized (mLock) {
        // Check whether the native surface reference address mNativeObject! = 0
        checkNotReleasedLocked();

        if(mHwuiContext ! =null) {
            // Release hardware-accelerated drawing lock
            mHwuiContext.unlockAndPost(canvas);
        } else {
            // Whether the buffer is drawn by softwareunlockSwCanvasAndPost(canvas); }}}private void unlockSwCanvasAndPost(Canvas canvas) {
    if(canvas ! = mCanvas) {throw new IllegalArgumentException("canvas object must be the same instance that "
                                           + "was previously returned by lockCanvas");
    }
    if(mNativeObject ! = mLockedObject) { Log.w(TAG,"WARNING: Surface's mNativeObject (0x" +
              Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
              Long.toHexString(mLockedObject) +")");
    }
    if (mLockedObject == 0) {
        throw new IllegalStateException("Surface was not locked");
    }
    try {
        nativeUnlockCanvasAndPost(mLockedObject, canvas);
    } finally {
        
        nativeRelease(mLockedObject);
        mLockedObject = 0; }}// Release the buffer held by Canvas
private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
// Release the current locked Surface reference
private static native void nativeRelease(long nativeObject);

Copy the code

The native method is called, and finally the buffer object is unlocked by SurfaceFlinger. UnlockAndPost method. The buffer object is put back into the BufferQueue queue, which is synthesized and displayed to the device by SurfaceFlinger consumption.

// ======== frameworks/base/core/jni/android_view_Surface.cpp ==============

static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject)); .// detach the canvas from the surface
    graphics::Canvas canvas(env, canvasObj);	// Convert to native Canvas
    canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN); // Disassociate Canvas from Buffer

    // unlock surface
    status_t err = surface->unlockAndPost(a); . }static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
    sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
    sur->decStrong(&sRefBaseOwner);
}

// ============ frameworks/native/libs/gui/Surface.cpp ==================

status_t Surface::unlockAndPost(a)
{
    if (mLockedBuffer == nullptr) {...return INVALID_OPERATION;
    }
    int fd = - 1;
    // Unlock the buffer buffer of the surface mapping
    status_t err = mLockedBuffer->unlockAsync(&fd); .// Put the buffer back into the BufferQueue and tell the SurfaceFlinger to consume the buffer
    err = queueBuffer(mLockedBuffer.get(), fd); .// The secondary buffer mechanism records the currently used buffer
    mPostedBuffer = mLockedBuffer;
    // Empty the buffer object that represents the current lock
    mLockedBuffer = nullptr;
    return err;
}
Copy the code

3.4 summary

At this point, the view drawing of a frame is complete.

In fact, on a 60-frame device, the Vsync sync signal is sent every 16.6ms, so especially in the onDraw method, if too much data needs to be written to the buffer at one time (1 frame), the SurfaceFlinger will not be able to get the buffer at that time. The previous buffer was used to draw, resulting in frame loss and stutter.

Double buffering and triple buffering

APP stuck problem analysis and solution

conclusion

Finally, a brief overview of the View drawing process

  1. Measure and layout operations are executed successively to determine the size and position of all views in the window.

  2. Internal hold Producer Binder Surface through the native layer proxy objects, across processes call BufferQueueProducer. DequeueBuffer method applied to BufferQueue queue for Buffer available.

    The Buffer object mapped to the App process is obtained through shared memory mapping, and the Buffer is locked to avoid access conflicts.

  3. Create Canvas object, hold Buffer object mapped to Surface virtual memory address, execute draw operation, write fill display content to Buffer object.

  4. After Buffer is written, the Canvas association is released and the Buffer object is unlocked. Through the queueBuffer method, the Buffer object contact is put back into the queue, notifying the SurfaceFlinger that the consumption is displayed to the device.

Afterword.

Through three articles, I have roughly combed a drawing process. The actual drawing process is actually quite complicated and involves a lot of things, which is far from being clear in these three superficial articles.

At present, we mainly have a preliminary and general understanding of the drawing process on the application layer, paving the way for the subsequent in-depth analysis of the details.

The resources

Android Java layer UI rendering to achieve the creation of four Surface and View rendering

Talk about Surface cross-process delivery

How does Draw () work

Android View event distribution and drawing process

Surface GraphicBuffer application process