Reprint address

Introduction to the principle and implementation of Android hardware acceleration

Android application UI hardware accelerated rendering Display List construction process analysis

Hardware-accelerated drawing

Compared with software rendering, hardware-accelerated rendering can make full use of GPU performance and greatly improve the rendering efficiency. Android Level 14 or later hardware acceleration is enabled by default. For details about hardware acceleration control, see Hardware Acceleration.

The difference between software drawing and winner accelerated drawing

Introduction to the principle and implementation of Android hardware acceleration

Render the scene Pure software drawing Hardware acceleration Acceleration effect analysis
Page initialization Draw allView Create allDisplayList GPUShared complex computing tasks
Call background transparency on a complex pageTextView.setText()And its size position remains unchanged after the call Redraw all dirty areasView The reconstructionTextViewDisplayList Overlapping sibling nodes are not requiredCPURe-paint, GPUWill take care of itself
TextViewFrame by frame to playAlpha / Translation / Scaleanimation Every frame should be repainted all the dirty areas View Except for the first frame, which is the same as scene 2, each subsequent frame will only be updatedTextViewThe correspondingRenderNodeThe properties of the Refresh a frame performance greatly improved, animation fluency improved
Modify theTextViewtransparency Redraw all dirty areasView Direct callRenderNode.setAlpha()update Full page traversal is required before acceleration, and a lot of redrawing is requiredView; Only trigger when acceleratedView.updateDisplayListIfDirty()Instead of going down,CPUThe execution time is negligible

Display List

The Display List is essentially a buffer that records the sequence of drawing commands to be executed. These draw commands are eventually converted into Open GL commands to be executed by the GPU. There are two advantages to logging a draw command in Display List and then executing it, as opposed to executing it directly.

  • First: when drawing the next frame of a window, the view of aUIThere is no change, then there is no need to execute its relatedCanvas APIThat is, its member functions are not executedonDraw()Instead, you just reuse what you built last timeDisplay ListCan.
  • Second: when drawing the next frame of the window, if a certain viewUIChanges have taken place, but only some simple properties have changed, such as position and transparency, directly modifiedDisplay ListWithout having to rebuild, and thus without having to execute its member functionsonDraw().

RenderNode

A RenderNode corresponds to a View that contains all Display lists of its own and its child views.

Hardware speeds up the rendering process

In a hardware-accelerated rendering environment, UI rendering of an Android application window takes place in two steps.

  • Step 1: BuildDisplay ListinMain ThreadIn the.
  • Step 2: RenderDisplay ListOccur in theRender ThreadIn the.

To build the Display List

Hardware rendering is performed in the threadRenderer.draw () method.

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) { // 1. updateRootDisplayList(view, callbacks); // 2. if (attachInfo.mPendingAnimatingRenderNodes ! = null) { final int count = attachInfo.mPendingAnimatingRenderNodes.size(); for (int i = 0; i < count; i++) { registerAnimatingRenderNode( attachInfo.mPendingAnimatingRenderNodes.get(i)); } attachInfo.mPendingAnimatingRenderNodes.clear(); // We don't need this anymore as subsequent calls to // ViewRootImpl#attachRenderNodeAnimator will go directly to us. attachInfo.mPendingAnimatingRenderNodes = null; } // 3. int syncResult = syncAndDrawFrame(choreographer.mFrameInfo); // 4. if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) ! = 0) { setEnabled(false); attachInfo.mViewRootImpl.mSurface.release(); // Invalidate since we failed to draw. This should fetch a Surface // if it is still needed or do nothing if we are no longer drawing attachInfo.mViewRootImpl.invalidate(); } if ((syncResult & SYNC_REDRAW_REQUESTED) ! = 0) { attachInfo.mViewRootImpl.invalidate(); }}Copy the code

The threadRenderer.draw () method does four things:

    1. callupdateRootDisplayList()Method construction parametersViewDisplay List. theViewFor the root viewDecor View.
    1. callregisterAnimatingRenderNode()Method registration animationRender Node.
    1. callnSyncAndDrawFrame()Methods to informRender ThreadRender the next frame.
    1. ifnSyncAndDrawFrame()Contains the return value ofSYNC_LOST_SURFACE_REWARD_IF_FOUNDSYNC_REDRAW_REQUESTEDIndicates thatRender ThreadMay need to work withMain ThreadSynchronize information.

Let’s look at the implementation of updateRootDisplayList().

private void updateRootDisplayList(View view, DrawCallbacks callbacks) { updateViewTreeDisplayList(view); if (mRootNodeNeedsUpdate || ! mRootNode.hasDisplayList()) { RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight); try { final int saveCount = canvas.save(); canvas.translate(mInsetLeft, mInsetTop); callbacks.onPreDraw(canvas); canvas.enableZ(); canvas.drawRenderNode(view.updateDisplayListIfDirty()); canvas.disableZ(); callbacks.onPostDraw(canvas); canvas.restoreToCount(saveCount); mRootNodeNeedsUpdate = false; } finally { mRootNode.endRecording(); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW); }Copy the code

UpdateRootDisplayList () method by calling updateViewTreeDisplayList view () method to construct the tree all views of the Display List.

private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}
Copy the code

UpdateViewTreeDisplayList () method in addition to the mPrivateFlags, mRecreateDisplayList marks, only call the updateDisplayListIfDirty () method.

public RenderNode updateDisplayListIfDirty() { final RenderNode renderNode = mRenderNode; if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || ! RenderNode. HasDisplayList () | | (mRecreateDisplayList)) {/ / no need to rebuild the current View of the Display List, Only need to update or rebuild the View of the Display List if (renderNode. HasDisplayList () &&! mRecreateDisplayList) { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchGetDisplayList(); return renderNode; DisplayList mRecreateDisplayList = true; DisplayList mRecreateDisplayList = true; int width = mRight - mLeft; int height = mBottom - mTop; int layerType = getLayerType(); final RecordingCanvas canvas = renderNode.beginRecording(width, height); try { if (layerType == LAYER_TYPE_SOFTWARE) { buildDrawingCache(true); Bitmap cache = getDrawingCache(true); if (cache ! = null) { canvas.drawBitmap(cache, 0, 0, mLayerPaint); } } else { computeScroll(); canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { draw(canvas); } } } finally { renderNode.endRecording(); setDisplayListProperties(renderNode); } } else { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } return renderNode; }Copy the code

If the current View itself does not need to rebuild or update the DisplayList, the dispatchGetDisplayList() method is called to distribute the reconstructed child View DisplayList. The dispatchGetDisplayList() method is implemented in the ViewGroup.

protected void dispatchGetDisplayList() { final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null)) { recreateChildDisplayList(child); } } final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View child = mTransientViews.get(i); if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null)) { recreateChildDisplayList(child); } } if (mOverlay ! = null) { View overlayView = mOverlay.getOverlayView(); recreateChildDisplayList(overlayView); } if (mDisappearingChildren ! = null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size(); for (int i = 0; i < disappearingCount; ++i) { final View child = disappearingChildren.get(i); recreateChildDisplayList(child); }}}Copy the code

Call recreateChildDisplayList() for all child Views that are displaying or executing an animation.

private void recreateChildDisplayList(View child) { child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) ! = 0; child.mPrivateFlags &= ~PFLAG_INVALIDATED; child.updateDisplayListIfDirty(); child.mRecreateDisplayList = false; }Copy the code

Here the updateDisplayListIfDirty () method is called again, can be found that this is a recursive call. The recursion process is as follows:

Call the view.draw (Canvas) method if the View of the Display List needs to be rebuilt or updated during recursion. \ endRecording and through RenderNode. BeginRecording () () method of reconstruction or update the Display List.

The Android UI drawing process is summarized as follows (dotted line represents recursion) :

The figure above comes from: Introduction to the principle and implementation of Android hardware acceleration