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 |
GPU Shared 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 reconstructionTextView 的 DisplayList |
Overlapping sibling nodes are not requiredCPU Re-paint, GPU Will take care of itself |
TextView Frame by frame to playAlpha / Translation / Scale animation |
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 updatedTextView The correspondingRenderNode The properties of the |
Refresh a frame performance greatly improved, animation fluency improved |
Modify theTextView transparency |
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,CPU The 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 a
UI
There is no change, then there is no need to execute its relatedCanvas API
That is, its member functions are not executedonDraw()
Instead, you just reuse what you built last timeDisplay List
Can. - Second: when drawing the next frame of the window, if a certain view
UI
Changes have taken place, but only some simple properties have changed, such as position and transparency, directly modifiedDisplay List
Without 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: Build
Display List
inMain Thread
In the. - Step 2: Render
Display List
Occur in theRender Thread
In 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:
-
- call
updateRootDisplayList()
Method construction parametersView
的Display List
. theView
For the root viewDecor View
.
- call
-
- call
registerAnimatingRenderNode()
Method registration animationRender Node
.
- call
-
- call
nSyncAndDrawFrame()
Methods to informRender Thread
Render the next frame.
- call
-
- if
nSyncAndDrawFrame()
Contains the return value ofSYNC_LOST_SURFACE_REWARD_IF_FOUND
或SYNC_REDRAW_REQUESTED
Indicates thatRender Thread
May need to work withMain Thread
Synchronize information.
- if
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