mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-11 22:40:37 +08:00
Summary: This fixes a crash for the case when we try to drop a view that has already been dropped. **The Problem** We got reports of a crash (t12912526) that occurs when the resolveViewManager method can't resolve a ViewManager for a View being dropped. Investigating this, one thing in common between all the stack traces for this is that dropView is called from line 210 of FlatNativeViewHierarchyManager. This part of the code is specifically the part we added to remove strong references to any clipped children (from views that have subview clipping enabled). So this is a problem specifically with Nodes and clipSubviews, which brings up some questions: **when can this happen?** The only situation this can possibly happen is when we drop a child (which is clipped) followed by dropping its parent in the same cycle. Consider a tree where each view only has one child, such as: A - B - C - D. This crash would happen if D is clipped, and we removed it, followed by removing any of its parents in the same frame. **if the removes happen in different frames, does this bug occur?** No - the reason is that before we execute the DropView operations, we run through StateBuilder, which traverses the shadow tree and marks updates, thus removing the view going away (such that the delete in the next frame doesn't try to re-delete it). So why doesn't this happen when we're dropping in the same frame? The reason is that manageChildren (where this all starts) asks to remove some views. We handle this by removing said Nodes and their children from the shadow tree. Consequently, when StateBuilder iterates over the shadow tree, it can't do the right thing because said nodes no longer exist. As a more concrete example, consider A - B - C - D again, and consider that both D and B are removed. StateBuilder only sees A, and realizes that it now has 0 children (whereas before it has 1), so it removes B from its children. However, this process isn't recursive, so C never gets cleaned up. **why doesn't this happen with Nodes without clipping containers?** The answer to this is that NativeViewHierarchyManager's dropView method checks the existance of each child before deeply dropping that child and its subtree. So in this case, we drop D and all its children, and when we come to drop B, we try to drop C (which exists) and then its children (D, which doesn't exist because we already dropped it, so we ignore it). **why doesn't this happen with non-Nodes?** The reason is that non-Nodes handles removes differently - every remove is enqueued in a call to NativeViewHierarchy's manageChildren, which explicitly asks the parent to remove said child. Consequently, we never try to remove a child that is already removed. **Fix** The initial fix was to check whether or not the view exists, but this updated patch just does the right thing at drop time - i.e. whenever a view is dropped, we notify the parent of this fact so that it can clear the reference from clipped views. **One last Note** There are two reasons for switching `super.dropView` to `dropView` - first, the comment is only partially correct - calling `super.dropView` will avoid looking at clipped children (as an aside, that could cause a leak in the case of nested clipping subviews), but will look at clipped grandchildren, because of the super class's iteration across the set of children. Reviewed By: astreet Differential Revision: D3815485
159 lines
5.5 KiB
Java
159 lines
5.5 KiB
Java
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
package com.facebook.react.flat;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Rect;
|
|
import android.util.SparseArray;
|
|
import android.util.SparseIntArray;
|
|
import android.view.View;
|
|
import android.view.ViewParent;
|
|
|
|
/**
|
|
* Underlying logic which handles draw commands, views and node regions when clipping in a
|
|
* {@link FlatViewGroup}.
|
|
*/
|
|
/* package */ abstract class DrawCommandManager {
|
|
|
|
/**
|
|
* Mount a set of draw commands to this manager. The order the commands are given is the order in
|
|
* which they should be drawn. If any of the commands are new DrawViews, then mountViews will be
|
|
* called after by the UIManager.
|
|
*
|
|
* @param drawCommands The draw commands to mount.
|
|
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
|
|
* @param maxBottom At each index i, the maximum bottom value (or right value in the case of
|
|
* horizontal clipping) value of all draw commands at or below i.
|
|
* @param minTop At each index i, the minimum top value (or left value in the case of horizontal
|
|
* clipping) value of all draw commands at or below i.
|
|
* @param willMountViews Whether we are going to also receive a mountViews command in this state
|
|
* cycle.
|
|
*/
|
|
abstract void mountDrawCommands(
|
|
DrawCommand[] drawCommands,
|
|
SparseIntArray drawViewIndexMap,
|
|
float[] maxBottom,
|
|
float[] minTop,
|
|
boolean willMountViews);
|
|
|
|
/**
|
|
* Add and detach a set of views. The views added here will already have a DrawView passed in
|
|
* mountDrawCommands.
|
|
*
|
|
* @param viewResolver
|
|
* @param viewsToAdd The views to add, by tag. If this is a new view, this will be reactTag,
|
|
* otherwise it will be -reactTag. This allows to optimize when we have already attached
|
|
* views.
|
|
* @param viewsToDetach The views to detach, by tag. These will all be positive.
|
|
*/
|
|
abstract void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach);
|
|
|
|
/**
|
|
* Get the current clipping rect and adjust clipping so that when draw is dispatched we do as
|
|
* little work as possible.
|
|
*
|
|
* @return true if the FlatViewGroup should invalidate.
|
|
*/
|
|
abstract boolean updateClippingRect();
|
|
|
|
/**
|
|
* Sets an input rect to match the bounds of our current clipping rect.
|
|
*
|
|
* @param outClippingRect Set the out
|
|
*/
|
|
abstract void getClippingRect(Rect outClippingRect);
|
|
|
|
/**
|
|
* Return the views that are currently detached, so they can be cleaned up when we are.
|
|
*
|
|
* @return A collection of the currently detached views.
|
|
*/
|
|
abstract SparseArray<View> getDetachedViews();
|
|
|
|
/**
|
|
* Draw the relevant items. This should do as little work as possible.
|
|
*
|
|
* @param canvas The canvas to draw on.
|
|
*/
|
|
abstract void draw(Canvas canvas);
|
|
|
|
/**
|
|
* Draws layout bounds for debug.
|
|
*
|
|
* @param canvas The canvas to draw on.
|
|
*/
|
|
abstract void debugDraw(Canvas canvas);
|
|
|
|
/**
|
|
* Mount node regions, which are the hit boxes of the shadow node children of this FlatViewGroup,
|
|
* though some may not have a corresponding draw command.
|
|
*
|
|
* @param nodeRegions Array of node regions to mount.
|
|
* @param maxBottom At each index i, the maximum bottom value (or right value in the case of
|
|
* horizontal clipping) value of all node regions at or below i.
|
|
* @param minTop At each index i, the minimum top value (or left value in the case of horizontal
|
|
* clipping) value of all draw commands at or below i.
|
|
*/
|
|
abstract void mountNodeRegions(NodeRegion[] nodeRegions, float[] maxBottom, float[] minTop);
|
|
|
|
/**
|
|
* Find a matching node region for a touch.
|
|
*
|
|
* @param touchX X coordinate of touch.
|
|
* @param touchY Y coordinate of touch.
|
|
* @return Matching node region, or null if none are found.
|
|
*/
|
|
abstract @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY);
|
|
|
|
/**
|
|
* Find a matching virtual node region for a touch.
|
|
*
|
|
* @param touchX X coordinate of touch.
|
|
* @param touchY Y coordinate of touch.
|
|
* @return Matching node region, or null if none are found.
|
|
*/
|
|
abstract @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY);
|
|
|
|
/**
|
|
* Event that is fired when a clipped view is dropped.
|
|
*
|
|
* @param view the view that is dropped
|
|
*/
|
|
abstract void onClippedViewDropped(View view);
|
|
|
|
/**
|
|
* Throw a runtime exception if a view we are trying to attach is already parented.
|
|
*
|
|
* @param view The view to check.
|
|
*/
|
|
protected static void ensureViewHasNoParent(View view) {
|
|
ViewParent oldParent = view.getParent();
|
|
if (oldParent != null) {
|
|
throw new RuntimeException(
|
|
"Cannot add view " + view + " to DrawCommandManager while it has a parent " + oldParent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a draw command manager that will clip vertically (The view scrolls up and down).
|
|
*
|
|
* @param flatViewGroup FlatViewGroup to use for drawing.
|
|
* @param drawCommands List of commands to mount.
|
|
* @return Vertically clipping draw command manager.
|
|
*/
|
|
static DrawCommandManager getVerticalClippingInstance(
|
|
FlatViewGroup flatViewGroup,
|
|
DrawCommand[] drawCommands) {
|
|
return new VerticalDrawCommandManager(flatViewGroup, drawCommands);
|
|
}
|
|
}
|