1.View Flag
Because there are many states in the View, Flag is handled in binary management mode
static final int PFLAG_WANTS_FOCUS = 0x00000001;
static final int PFLAG_FOCUSED = 0x00000002;
static final int PFLAG_SELECTED = 0x00000004;
static final int PFLAG_IS_ROOT_NAMESPACE = 0x00000008;
static final int PFLAG_HAS_BOUNDS = 0x00000010;
static final int PFLAG_DRAWN = 0x00000020;
/**
* When this flag is set, this view is running an animation on behalf of its
* children and should therefore not cancel invalidate requests, even if they
* lie outside of this view's bounds.
*/
static final int PFLAG_DRAW_ANIMATION = 0x00000040;
static final int PFLAG_SKIP_DRAW = 0x00000080;
static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200;
static final int PFLAG_DRAWABLE_STATE_DIRTY = 0x00000400;
static final int PFLAG_MEASURED_DIMENSION_SET = 0x00000800;
static final int PFLAG_FORCE_LAYOUT = 0x00001000;
static final int PFLAG_LAYOUT_REQUIRED = 0x00002000;
private static final int PFLAG_PRESSED = 0x00004000;
static final int PFLAG_DRAWING_CACHE_VALID = 0x00008000;
/**
* Flag used to indicate that this view should be drawn once more (and only once
* more) after its animation has completed.
*/
static final int PFLAG_ANIMATION_STARTED = 0x00010000;
private static final int PFLAG_SAVE_STATE_CALLED = 0x00020000;
/**
* Indicates that the View returned true when onSetAlpha() was called and that
* the alpha must be restored.
*/
static final int PFLAG_ALPHA_SET = 0x00040000;
/**
* Set by {@link #setScrollContainer(boolean)}.
*/
static final int PFLAG_SCROLL_CONTAINER = 0x00080000;
/**
* Set by {@link #setScrollContainer(boolean)}.
*/
static final int PFLAG_SCROLL_CONTAINER_ADDED = 0x00100000;
/**
* View flag indicating whether this view was invalidated (fully or partially.)
*
* @hide* /
static final int PFLAG_DIRTY = 0x00200000;
/**
* Mask for {@link #PFLAG_DIRTY}.
*
*/
static final int PFLAG_DIRTY_MASK = 0x00200000;
/**
* Indicates whether the background is opaque.
*
*/
static final int PFLAG_OPAQUE_BACKGROUND = 0x00800000;
/**
* Indicates whether the scrollbars are opaque.
*
*/
static final int PFLAG_OPAQUE_SCROLLBARS = 0x01000000;
/**
* Indicates whether the view is opaque.
*
*/
static final int PFLAG_OPAQUE_MASK = 0x01800000;
/** * Indicates a prepressed state; * the short time between ACTION_DOWN and recognizing * a 'real' press. Prepressed is used to recognize quick taps * even when they are shorter than ViewConfiguration.getTapTimeout(). * */
private static final int PFLAG_PREPRESSED = 0x02000000;
/**
* Indicates whether the view is temporarily detached.
*/
static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000;
/**
* Indicates that we should awaken scroll bars once attached
*
* PLEASE NOTE: This flag is now unused as we now send onVisibilityChanged
* during window attachment and it is no longer needed. Feel free to repurpose it.
*
*/
private static final int PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/** * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT. */
private static final int PFLAG_HOVERED = 0x10000000;
/**
* Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
*/
private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
static final int PFLAG_ACTIVATED = 0x40000000;
/**
* Indicates that this view was specifically invalidated, not just dirtied because some
* child view was invalidated. The flag is used to determine when we need to recreate
* a view's display list (as opposed to just returning a reference to its existing
* display list).
*/
static final int PFLAG_INVALIDATED = 0x80000000;
Copy the code
Whether the specified Flag is included
PFLAG_FORCE_LAYOUT indicates whether to include PFLAG_FORCE_LAYOUT. If PFLAG_FORCE_LAYOUT is included, the flag is 1view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ! =0
Copy the code
Clearing a specified Flag
// Clear the specified Flag
view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT
Copy the code
Setting the specified Flag
// Set the specified Flag
view.mPrivateFlags |= View.PFLAG_FORCE_LAYOUT
Copy the code
Check whether the Flag is changed
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
// Check whether Flag is changed. Changed ==1 is changed
int changed = mViewFlags ^ old;
Copy the code
2. Hardware acceleration
-
Use the CPU to draw to the Bitmap, and then paste the Bitmap to the screen, is the software drawing
-
Using THE CPU to draw the content into GPU operation, to the GPU, the GPU is responsible for the real drawing, called hardware drawing
-
GPU drawing simple graphics (such as square, circle, straight line) has inherent advantages in hardware design, it is faster, and the process is optimized (redrawing process involves less content)
Compatibility. Because some drawings cannot be done using the GPU (for now), for certain apis, you need to turn off hardware acceleration to go back to drawing using the CPU.
The basic principle of
- Software drawing is to write a series of Canvas operations into a Bitmap
- For hardware-accelerated drawing, each View has a RenderNode, and when it needs to draw, a RecordingCanvas is fetched from RenderNode
- Just like software drawing, it also calls a series of Canvas apis, but these apis are recorded as a series of operation behaviors stored in the DisplayList
- When a View is finished recording, the DisplayList is handed over to RenderNode
- At this point, the drawing process is documented in RenderNode, and at this point, the hardware drawing of the individual View is completed, also known as the building process of the DisplayList.
Judgment method
canvas.isHardwareAccelerated()
Copy the code
The Canvas object obtained according to the judgment method is also different
- RecordingCanvas (Hardware accelerated Canvas)
- Cavas (Normal Canvas)
Hierarchical control of hardware acceleration
The hierarchy | Open means | Impact after the function is enabled | Impact after shutdown | Enable by default |
---|---|---|---|---|
Application | android:hardwareAccelerated=”true|false” | At the same time, control the Activity/Window/View start The Activity/View can optionally turn off hardware acceleration |
It also controls the Activity/Window closing The Activity/Window can enable hardware acceleration separately |
is |
Activity | android:hardwareAccelerated=”true|false” | At the same time control the Window/View open View can optionally turn off hardware acceleration |
It also controls the View closing | is |
Window | WindowManager.LayoutParams. FLAG_HARDWARE_ACCELERATED |
Windows hardware acceleration cannot be turned off directly | Windows hardware acceleration cannot be turned off directly | |
View | Hardware acceleration for a View cannot be directly enabled | through View.LAYER_TYPE_SOFTWARE Or android: layerType = “software” Shut down |
-
Application
- Through the android: hardwareAccelerated = “true | false” Settings
- Hardware acceleration is enabled at the Application level and controls the start of the Activity/Window/View. The Activity/View can choose to disable hardware acceleration
- The application-level hardware acceleration shutdown also controls the Activity/Window shutdown. The Activity/Window can be independently enabled with hardware acceleration
- The default open
-
Activity
- Through the android: hardwareAccelerated = “true | false” Settings
- Hardware acceleration is enabled on the Activity level, and it also controls the opening of the Window/View. The View can choose to disable hardware acceleration
- The hardware-accelerated shutdown of the Activity level also controls the closing of the View
- The default open
-
Window
-
// Enable hardware acceleration getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); Copy the code
If this method is used, the hardware acceleration of Windows cannot be directly disabled
-
-
View
-
// Disable hardware acceleration View.setLayerType(View.LAYER_TYPE_SOFTWARE, null); Copy the code
Hardware acceleration for a View cannot be directly enabled
-
3.Canvas creation management
The three traversals processes are enabled in the ViewRootImpl’s performTraversals(), and the drawing part obtains different Canvas depending on whether hardware acceleration is supported or not
Cavas (Normal Canvas)
- The acquisition of ordinary Canvas is realized by surface.LockCanvas (Rect inOutDirty). The size of Canvas is determined according to the Rect passed in, and the specific creation is implemented in the Native layer
RecordingCanvas (Hardware accelerated Canvas)
- Each View has a RenderNode, and when it needs to draw, it gets a RecordingCanvas from RenderNode
- RecordingCanvas management uses the concept of pool, through a pool to achieve reuse.
- Finally, in the finally, the Canvas is released
- The pool doesn’t have an initial size, or the initial size is 0, the maximum size is on the DisplayListCanvas and I’ll say POOL_LIMIT = 25, DisplayListCanvas also specifies the maximum size of the bitmap in the drawBitmap() method
4. Off-screen cache
LAYER_TYPE_NONE (without drawing cache)
- The Canvas type is CompatibleCanvas
- From ViewRootImpl. DrawSoftware (), through mSurface. LookCanvas (dirty) to generate the Canvas object
- The entire ViewTree shares the same Canvas object
LAYER_TYPE_SOFTWARE (using software to draw cache)
- The type of Canvas is Canvas
- From the buildDrawingCacheImpl () to start, through the new Canvas () generates a Canvas object, and deposited in the AttachInfo
- Each View that uses the software cache generates a new Canvas, which is reused if available from AttachInfo
If this type of off-screen buffering is used and hardware acceleration is turned off
LAYER_TYPE_HARDWARE (using hardware draw cache)
- The Canvas type is RecordingCanvas
- Begins from the UpdateDIsplayListIfDirty (), by renderNode. BeginRecording () generates a Canvas object
- Each View that supports hardware acceleration generates a new Canvas object
5. Animation differences
- Animation: does not change the actual value of the View, it will be restored after completion
- Animator: changes the real value of the View property and does not restore after completion
6. Drawing hierarchy and implementation methods
- The drawing hierarchy of View and ViewGroup is different and the implementation method is different as follows
type | Draw the hierarchy | Implementation method |
---|---|---|
View | Background –> Content –> Foreground | Implements the draw () Ondraw () and dispathDraw() are empty implementations |
ViewGroup | Background -> content -> hierarchy of sublayouts -> Foreground | Implements the dispathDraw () |
7.View.draw()
- Just like measuring and layout, drawing has a scheduling method called draw().
Source process
The complete process of Draw is as follows:
-
DrawBackground () drawBackground()
-
Canvas.getsavecount () (Records the Canvas state to prepare for drawing the gradient (save the canvas coordinates first, because it will change))
-
OnDraw ()
-
DispathDraw () (distribution Draw, Draw child layout call drawChild() method)
-
MOverlay (draw on top of content, below foreground)
// The code calls the implementation View viewGroup = findViewById(R.id.myviewgroup); // Specify a Drawable for overLay Drawable drawable = ContextCompat.getDrawable(this, R.drawable.shapeme); // Set the size of Drawable drawable.setBounds(0.0.400.58); // Add Drawable for overLay viewGroup.getOverlay().add(drawable); Copy the code
-
-
Draws an edge gradient effect
-
OnDrawForeground ()
The foreground and mOverlay are different, and the foreground will be resized to match the View size
-
DrawDefaultFocusHighlight () (draw the default highlighting effect)
But draw has two different processing branches:
- The default processing is 1,3,4,6,7
- The one with the edge is 1,2,3,4,5,6,7
8.ViewGroup.dispatchDraw()
- DispatchDraw () is actually the ViewGroup’s method of dispatching View drawing. It also implements animation support, Padding cutting and other functions, which we will analyze one by one
Animation support
- When the animation is completed, the dispatchDraw() method calls the completion listener according to the FLAG
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0&&mLayoutAnimationController.isDone() && ! more) {// Erase the draw cache and notify the listener when the next frame is drawn, because drawChild() causes an additional invalidate() when the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run(a) {
// Call the animation listenernotifyAnimationListener(); }}; post(end); }private void notifyAnimationListener(a) {
mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
mGroupFlags |= FLAG_ANIMATION_DONE;
if(mAnimationListener ! =null) {
final Runnable end = new Runnable() {
@Override
public void run(a) { mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation()); }}; post(end); }// Invalidate () will be called once
invalidate(true);
}
Copy the code
Padding cutting
-
Judging from the FLAG, after the padding is set, the canvas of the sub-layout needs to be trimmed according to the padding.
/ / the FLAG final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { // So you need to transform the canvas coordinates, save its state first clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); / / cutting canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } Copy the code
-
To disable clipping, use the following method
setClipToPadding(false) android:clipToPadding="false" Copy the code
Scheduling View drawing
-
**dispatchDraw()** will internally iterate over all the children
-
DrawChild (Canvas Canvas, View Child, long drawingTime
-
DrawChild (Canvas Canvas, View Child, long drawingTime) Long drawingTime) * * method
Draw(Canvas, ViewGroup parent, long drawingTime) is not draw() and can only be called by viewGroup.dispatchDraw ()
9.View.draw(Canvas canvas, ViewGroup parent, long drawingTime)
- In this approach, the View specializes in distinguishing between different rendering behaviors based on layer type and hardware acceleration
Hardware accelerated rendering
- The conditions for hardware acceleration are: Canvas supports hardware acceleration + Window supports hardware acceleration
booleandrawingWithRenderNode = mAttachInfo ! =null&& mAttachInfo.mHardwareAccelerated&& hardwareAcceleratedCanvas;
Copy the code
- The updateDisplayListIfDirty() method is eventually called if hardware acceleration is supported
if (drawingWithRenderNode) {
// This View supports hardware acceleration
// Then try to build DisplayList and return renderNoderenderNode = updateDisplayListIfDirty(); . }Copy the code
View.updateDisplayListIfDirty()
- Get the View’s RenderNode and update its DisplayList if needed
1. Obtain RenderNode
-
Each View is constructed to create a RenderNode:mRenderNode, called RenderNode
final RenderNode renderNode = mRenderNode; Copy the code
-
After retrieving RenderNode, first check whether hardware acceleration is supported. If not, return
if(! canHaveDisplayList()) {returnrenderNode; }Copy the code
2. Determine whether the drawing is complete
- Determine whether the drawing (or relationship) is complete based on the following conditions
- Rendering cache invalid
- The render node does not have a DisplayList yet
- The render node has a DisplayList, but it needs to be updated
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0| |! renderNode.hasDisplayList() || (mRecreateDisplayList)) {// Execute the draw logic
}
Copy the code
3. Execute different drawing policies based on the status
-
Determine whether a DisplayList needs to be built based on the following criteria
// If there is a DisplayList and the DisplayList does not need to be updated, then the View does not need to Draw againrenderNode.hasDisplayList() && ! mRecreateDisplayListCopy the code
3.1. Case 1: No need to redraw
-
Mark that the View has been drawn and the cache is valid
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; Copy the code
-
To continue to see if the child layout needs to build the DisplayList, call the dispatchGetDisplayList() method
dispatchGetDisplayList()
- This method is used to make the children of a ViewGroup restore or recreate their display list by walking through the child layouts and calling their rebuild methods
-
Try recreateChildDisplayList() by traversing the child
final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { // Iterate over the child layout final View child = children[i]; if(((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null)) { // Rebuild the sublayout DisplayListrecreateChildDisplayList(child); }}Copy the code
-
RecreateChildDisplayList () internally also recreateChildDisplayList() determines whether reconstruction is required by the FLAG, and finally recreeDisplayListifDirty () method is called
private void recreateChildDisplayList(View child) { // Determine whether reconstruction is neededchild.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) ! =0; child.mPrivateFlags &= ~PFLAG_INVALIDATED; // Call the sublayout reconstruction method child.updateDisplayListIfDirty(); child.mRecreateDisplayList = false; } Copy the code
3.2. Case 2: DisplayList needs to be built and Draw finally called
-
Create a RecordingCanvas object based on the View coordinates determined in the Layout phase and the layerType (off screen cache type). And open the recording (renderNode. BeginRecording (width, height))
// The View coordinates determined in the Layout phase are used int width = mRight - mLeft; int height = mBottom - mTop; // Get the layerType currently set int layerType = getLayerType(); // Get the Canvas object from renderNode. Initialize the Canvas size to the View size // This Canvas is of the RecordingCanvas type final RecordingCanvas canvas = renderNode.beginRecording(width, height); Copy the code
-
Depending on the layerType (off-screen cache type), different drawing logic is performed
-
If the software draws the cache, it builds the cache and writes it to the Bitmap, and finally draws the Bitmap to the Canvas
// If the software is drawing cache if (layerType == LAYER_TYPE_SOFTWARE) { // Build the cache buildDrawingCache(true); // Write the draw operation to the Bitmap Bitmap cache = getDrawingCache(true); if(cache ! =null) { // Draw the Bitmap to the Canvas canvas.drawBitmap(cache, 0.0, mLayerPaint); }}Copy the code
The buildDrawingCache() method is parsed later
-
If the software drawing cache is not set, FLAG will be used to confirm whether to skip drawing its own content (including content, foreground, background, etc.), if skip, directly draw child, if not skip, then call draw() to initiate drawing itself
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // The View does not need to draw its own content (including content, foreground, background, etc.) // Make the request to draw the sublayout directly dispatchDraw(canvas); drawAutofilledHighlight(canvas); if(mOverlay ! =null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if(debugDraw()) { debugDrawFocus(canvas); }}else { // Need to draw itself draw(canvas); } Copy the code
-
-
Finally finish the canvas recording and hand over the result of the recording: DisplayList to renderNode
finally { // Finally finish canvas recording and give renderNode the result of recording: DisplayList renderNode.endRecording(); setDisplayListProperties(renderNode); } Copy the code
Software cache drawing
- The condition for software cache rendering is that the off-screen software draw cache is set or that the View does not support hardware accelerated rendering
if(layerType == LAYER_TYPE_SOFTWARE || ! drawingWithRenderNode) {// If one of the two is true, it is a software cache drawing
// 2. The View does not support hardware accelerated rendering
if(layerType ! = LAYER_TYPE_NONE) {// There may be a software cache or a hardware cache set. In this case, the hardware cache is used as the software cache
layerType = LAYER_TYPE_SOFTWARE;
// Draw to the software cache
buildDrawingCache(true);
}
// Fetch the software cache
cache = getDrawingCache(true);
}
Copy the code
- Finally, buildDrawingCache() is called to draw to the software cache, and getDrawingCache() is used to fetch the software cache
View.buildDrawingCache()
- This approach was used to build the cache, but with the addition of hardware acceleration after API11, view drawing caching was largely phased out. With hardware acceleration, the intermediate cache layer is largely unnecessary and can easily lead to performance losses due to the high cost of creating and updating hardware
-
If the cache is marked invalid or empty, the cache is built using the buildDrawingCacheImpl() method
public void buildDrawingCache(boolean autoScale) { // If the cache is marked invalid or the cache is empty if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| (autoScale ? mDrawingCache ==null : mUnscaledDrawingCache == null)) { try { // Build the cache buildDrawingCacheImpl(autoScale); } finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}}Copy the code
View.buildDrawingCacheImpl()
- This method is an internally dedicated buildDrawingCache implementation for enabling tracing
-
If the Bitmap does not exist or the size of the Bitmap is different from that of the View, the Bitmap is created
int width = mRight - mLeft; int height = mBottom - mTop; boolean clear = true; Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; .// If the bitmap does not exist or the size of the bitmap is different from that of the View, create the bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),width, height, quality); Copy the code
-
Attempts to obtain a Canvas from attachInfo, creates it if it is empty, and associates a Bitmap with the Canvas instance after obtaining it
Canvas canvas; if(attachInfo ! =null) { canvas = attachInfo.mCanvas; if (canvas == null) { // The first time, AttachInfo has no Canvas canvas = new Canvas(); } / / associated bitmap canvas.setBitmap(bitmap); attachInfo.mCanvas = null; } Copy the code
-
Set FLAG to indicate that the software drawing cache takes effect
if (mAttachInfo == null| |! mAttachInfo.mHardwareAccelerated || mLayerType ! = LAYER_TYPE_NONE) {// Set the flag, indicating that the software drawing cache has taken effect mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; } Copy the code
-
Through the FLAG to confirm whether to skip drawing its own content (including content, foreground, background, etc.), if skipped directly draw child, if not skipped then call draw() to initiate drawing itself, general implementation
// Call public methods as well if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); drawAutofilledHighlight(canvas); if(mOverlay ! =null&&! mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); }}else { draw(canvas); } Copy the code
-
Record the completed Canvas to attachInfo for next use
// Attach the Canvas to attachInfo if(attachInfo ! =null) {attachInfo.mCanvas = canvas; }Copy the code
Animation processing
- The draw(Canvas, ViewGroup Parent, Long drawingTime) method calls the applyLegacyAnimation() method
// Get the animation
final Animation a = getAnimation();
if(a ! =null) {
// Call animation
// More indicates whether the animation is over
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
Copy the code
applyLegacyAnimation(ViewGroup parent, long drawingTime,Animation a, boolean scalingRequired)
-
To start an animation, add the PFLAG_ANIMATION_STARTED FLAG
protected void onAnimationStart(a) { mPrivateFlags |= PFLAG_ANIMATION_STARTED; } Copy the code
-
If the animation does not end, it is treated differently depending on whether the Bounds are changed or not
Case 1: If the bounds are not changed, the coordinates are processed according to the passed layoutAnimation, the FLAG is added, and the final call parent-.invalidate (mLeft, mTop, mRight, mBottom) is made to refresh the specific area
if(! a.willChangeBounds()) {if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==ViewGroup.FLAG_OPTIMIZE_INVALIDATE) { parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED; } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) { // The child needs to draw an animation (which may not be on the screen), so call invalidate().parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; parent.invalidate(mLeft, mTop, mRight, mBottom); }}Copy the code
Case 2: If the bounds are changed, the bounds are computed, and the result of the calculation is passed into the parent-.invalidate () method to complete the specific area refresh
else { if (parent.mInvalidateRegion == null) { parent.mInvalidateRegion = new RectF(); } final RectF region = parent.mInvalidateRegion; a.getInvalidateRegion(0.0, mRight - mLeft, mBottom - mTop, region,invalidationTransform); parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; final int left = mLeft + (int) region.left; final int top = mTop + (int) region.top; // The child needs to draw an animation (which may not be on the screen), so call invalidate(). parent.invalidate(left, top, left + (int) (region.width() + .5f),top + (int) (region.height() + .5f)); } Copy the code
10.View.requestLayout()
- Indicates a rerequest layout
-
Clearing the measurement cache
// Clear the measurement cache if(mMeasureCache ! =null) mMeasureCache.clear(); Copy the code
-
Add mandatory layout tags and redraw tags
// Add a mandatory Layout tag that fires the layout mPrivateFlags |= PFLAG_FORCE_LAYOUT; // Add emphasis mPrivateFlags |= PFLAG_INVALIDATED; Copy the code
-
Call the scheduleTraversals() method in the ViewRootImpl requestLayout() to open three processes (marking layout requests).
// The parent layout continues to call requestLayout mParent.requestLayout(); public void requestLayout(a) { // Whether the layout process is in progress if(! mHandlingLayoutInLayoutRequest) {// Check whether the threads are consistent checkThread(); // Tag has a request for a layout mLayoutRequested = true; // Open the three processes of ViewscheduleTraversals(); }}Copy the code
Summary:
- RequestLayout will eventually trigger the Measure and Layout processes
- The Draw procedure will not fire because the redraw region is not set
- Since PFLAG_FORCE_LAYOUT is set, the invalidate() method is called in the layout phase in the setFram() method, depending on whether the size has changed
11.View.invalidate()
- Indicates that the current drawing is invalid and needs to be redrawn, Int L, int T, int R, int B, Boolean invalidateCache, Boolean fullInvalidate
View.invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate)
- When invalidateCache is False, the size or content of the View has not changed
-
Return directly if skip drawing is set
if (skipInvalidate()) {return; }Copy the code
-
Use FLAG to determine whether the current view has been measured and laid out
- If experienced, the draw flag is cleared
- The setting requires a draw flag, plus a draw invalidation flag and a clear draw cache flag
PFLAG_HAS_BOUNDS indicates that the View has been mapped. PFLAG_HAS_BOUNDS indicates that the View has been mapped if((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) ! = PFLAG_INVALIDATED || (fullInvalidate && isOpaque() ! = mLastIsOpaque)) {if (fullInvalidate) { / / true by default mLastIsOpaque = isOpaque(); // Clear the draw flag mPrivateFlags &= ~PFLAG_DRAWN; } // Need to draw mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { //1 //2, clear the draw cache valid tags // These two tags are used in the hardware accelerated drawing branch mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } Copy the code
- If experienced, the draw flag is cleared
-
Redraw regions to damge, call ViewGroup. InvalidateChild () method to redraw
final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if(p ! =null&& ai ! =null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; // Record the area to be redrawn, damge, which is the size of the View damage.set(l, t, r, b); //p is the parent layout of the View // Call the invalidateChild of the parent layout p.invalidateChild(this, damage); } Copy the code
ViewGourp.invalidateChild(View child, final Rect dirty)
- This method is final and cannot be overridden in order to handle all child drawing refreshes
1. Perform different processing according to whether hardware acceleration is supported
if(attachInfo ! =null && attachInfo.mHardwareAccelerated) {
// If hardware acceleration is supported, go to this branch
onDescendantInvalidated(child, child);
return;
}
Copy the code
Case 1: hardware acceleration branch ViewGourp. OnDescendantInvalidated (@ NonNull View child, @ NonNull View target)
- The purpose of this method is to keep looking up for the parent layout and clear the PFLAG_DRAWING_CACHE_VALID FLAG. That is, when the drawing cache is cleared, then add the PFLAG_INVALIDATED FLAG if it needs to be redrawn, Eventually find the ViewRootImpl upward. OnDescendantInvalidated ()
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
if((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) ! =0) {
// We can go here
mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
// Clear valid flags from the draw cache
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
if (mLayerType == LAYER_TYPE_SOFTWARE) {
// If software drawing is enabled, the drawing failure mark is added
mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
// Change the target point
target = this;
}
if(mParent ! =null) {
// Call the onDescendantInvalidated parent layout
mParent.onDescendantInvalidated(this, target); }}Copy the code
ViewRootImpl.onDescendantInvalidated()
- Finally, it calls its own invalidate() in the method, internally calculates the dirty area (which requires overwriting the drawn data), and finally calls scheduleTraversals() to start the process
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
if((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) ! =0) {
mIsAnimating = true;
}
invalidate();
}
void invalidate(a) {
//mDirty indicates the dirty area, that is, the area that needs to be redrawn
//mWidth, mHeight = Window size
mDirty.set(0.0, mWidth, mHeight);
if(! mWillDrawSoon) {// Open the three processes of ViewscheduleTraversals(); }}Copy the code
Situation 2: software rendering branch ViewGroup. InvalidateChildInParent (final int [] the location, final the Rect dirty)
- This method can refresh the drawing of software drawing
-
According to the incoming failure area to judge, the failure area to make corrections
- If beyond the parent layout area, expand the failure area
- If it does not exceed the parent layout area, take the intersection area
if((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))! = FLAG_OPTIMIZE_INVALIDATE) {// Fix the redrawn area dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { // If the child layout is allowed to be displayed beyond the parent layout area, the dirty area needs to be enlarged dirty.union(0.0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { // The default is to go here // If the child layout is not allowed to be displayed beyond the parent layout area, then the intersection area is taken if(! dirty.intersect(0.0, mRight - left, mBottom - top)) {dirty.setEmpty();} } // Record the offset, which is used to continuously correct the redrawn area to calculate its coordinates relative to the screen location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; } Copy the code
-
If the parent layout mParent is not empty, the invalidateChildInParent() method is also called until the implementation of ViewRootImpl
- If the invalid area is empty, the entire window is refreshed
- If it is not empty, the invalid area is fetched and the scheduleTraversals() method is called to open the three flows
public ViewParent invalidateChildInParent(int[] location, Rect dirty) { // Thread check checkThread(); if (dirty == null) { If the dirty region is empty, the entire window is refreshed by default invalidate(); return null; } else if(dirty.isEmpty() && ! mIsAnimating) {return null; } invalidateRectOnScreen(dirty); return null; } private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; // Merge dirty regions and fetch the union localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); if(! mWillDrawSoon && (intersected || mIsAnimating)) {// Start the three main drawing processes of the ViewscheduleTraversals(); }}Copy the code
12.ViewRootImpl.scheduleTraversals()
- Both requestLayout() and invalidate() will eventually execute this method, opening the entry for the three steps
- Mark drawing requests to mask repeated requests within a short period of time
- The synchronization barrier message is sent to the Looper queue on the main thread to improve the priority of asynchronous messages
- Choreographer task processing scheduling
void scheduleTraversals(a) {
if(! mTraversalScheduled) {// Mark a drawing request to mask repeated requests within a short period of time
mTraversalScheduled = true;
// Put a synchronization barrier message in the main thread Looper queue to control the execution of asynchronous messages
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Put mChoreographer in the queue and mTraversalRunnable in the queue
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code
13.Choreographer
- When the Vsync pulse arrives, Choreographer controls the timing of each frame drawing operation by adjusting the Vsync pulse cycle to provide a stable Message processing time
60Hz refresh rate processing logic
Choreographer initialization
1.Choreographer instance acquisition
- Choreographer is a thread singleton, kept in the ThreadLocal zone, and also bound with Looper objects for use by internal handlers
Choreographer is saved in the ThreadLocal zone
private static final ThreadLocal<Choreographer> sThreadInstance =new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue(a) {
// Get Looper for the current thread
Looper looper = Looper.myLooper();
// Construct the Choreographer object
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
returnchoreographer; }};private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// Initialize FrameHandler
mHandler = new FrameHandler(looper);
// Initialize DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long) (1000000000 / getRefreshRate());
// Initialize CallbacksQueues
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = newCallbackQueue(); }}Copy the code
2.FrameDisplayEventReceiver
- Vsync registration, apply for and receive is done through the FrameDisplayEventReceiver, in the Choreographer will create the object structure
- FrameDisplayEventReceiver initialization process, through BitTube (is essentially a socket pair), to convey and request Vsync events, when SurfaceFlinger received Vsync events, The appEventThread sends the event via BitTube to DisplayEventDispatcher. After The DisplayEventDispatcher listens to the Vsync event on the receiving end of BitTube, The callback Choreographer. FrameDisplayEventReceiver. OnVsync, triggering began to draw a frame
private Choreographer(Looper looper, int vsyncSource) {...// Initialize DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null; . }public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
public DisplayEventReceiver(Looper looper, int vsyncSource) {... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
}
Copy the code
- FrameDisplayEventReceiver inherited from DisplayEventReceiver, inside there are three key ways
onVsync(long timestampNanos, long physicalDisplayId, int frame)
- Vsync signal callback
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {... mTimestampNanos = timestampNanos; mFrame = frame; Message msg = Message.obtain(mHandler,this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
Copy the code
run()
- Perform doFrame
@Override
public void run(a) {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
Copy the code
scheduleVsync()
- The request for the Vsync signal ended up calling the Native implementation
public void scheduleVsync(a) {... nativeScheduleVsync(mReceiverPtr); . }Copy the code
3.Choreographer FrameHandler
- FrameHandler is a processing Handler within Choreographer that receives the corresponding MSG and distributes it to different implementations
private final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
// Start the next frame rendering
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
/ / request Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
/ / CallBack processing
doScheduleCallback(msg.arg1);
break; }}}Copy the code
Choreographer processing logic
- When FrameDisplayEventReceiver enter onVsync () method, after the post, the final call to doFrame () method, the overall steps are as follows
1. Calculate the frame deletion logic
- The arrival of the Vsync pulse marks StartTime, and in the actual execution of doFrame(), it marks EndTime. The difference between the two marks is the Vsync processing time delay, or frame drop
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
......
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// Time mark subtraction is to drop frame
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread."); }}... }... }Copy the code
2. Record the drawing information
- Choreographer FrameInfo is responsible for recording frame drawing information, while doFrame executes, it records the drawing time of each key node
3. Implement the CallBack
- In the end, different callbacks are executed depending on the type, which is as follows:
- Handling of Input events
- Animation Processing of an Animation
- Traversal processing (measure, layout, draw)
void doFrame(long frameTimeNanos, int frame) {...// Handle CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// Handle CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// Handle CALLBACK_INSETS_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// Handle CALLBACK_TRAVERSAL Callbacks
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// Handle CALLBACK_COMMIT CallbacksdoCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); . }Copy the code
CALLBACK_TRAVERSAL
- When combined with the above ViewRootImpl. ScheduleTraversals () in the final call the callback, but the final distribution to performTraversals () method to realize the three steps
void scheduleTraversals(a) {
if(! mTraversalScheduled) {// Mark a drawing request to mask repeated requests within a short period of time
mTraversalScheduled = true;
// Put a synchronization barrier message in the main thread Looper queue to control the execution of asynchronous messages
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Put mChoreographer in the queue and mTraversalRunnable in the queue
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}final class TraversalRunnable implements Runnable {
@Override
public void run(a) {
// Start to implement measure, layout and drawdoTraversal(); }}void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// Insert SyncBarrier
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// StartperformTraversals(); }}private void performTraversals(a) {
/ / measure operation
if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); }/ / layout operations
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
/ / the draw operation
if(! cancelDraw && ! newSurface) { performDraw(); }}Copy the code