View recycling in JS.

Summary: public
Native view recycling implementation based on limited pools of views.

In this diff I introduced new UIManager method: dropViews. Instead of removing views from tag->view maps when they are detached we keep them there until we get a call to dropViews with the appropriate tag. JS may keep a pool of object and selectively decide not to enqueue drop for certain views. Then instead of removing those views it may decide to reuse tag that has been previously allocated for a view that is no longer in use.

Special handling is required for layout-only nodes as they only can transition from layout-only to non-layout-only (reverse transition hasn't been implemented). Because of that we'd loose benefits of view flattening if we decide to recycle existing non-layout-only view as a layout-only one.

This diff provides only a simple and manual method for configuring pools by calling `ReactNativeViewPool.configure` with a dict from native view name to the view count. Note that we may not want recycle all the views (e.g. when we render mapview we don't want to keep it in memory after it's detached)

Reviewed By: davidaurelio

Differential Revision: D2677289

fb-gh-sync-id: 29f44ce5b01db3ec353522af051b6a50924614a2
This commit is contained in:
Krzysztof Magiera
2015-11-20 08:02:35 -08:00
committed by facebook-github-bot-0
parent 0c8850f3a7
commit 205a35ad37
11 changed files with 342 additions and 27 deletions

View File

@@ -349,7 +349,7 @@ import com.facebook.react.touch.JSResponderHandler;
viewsToAdd,
tagsToDelete));
}
dropView(viewToDestroy);
detachView(viewToDestroy);
}
}
}
@@ -378,10 +378,15 @@ import com.facebook.react.touch.JSResponderHandler;
view.setId(tag);
}
public void dropView(int tag) {
mTagsToViews.remove(tag);
mTagsToViewManagers.remove(tag);
}
/**
* Releases all references to given native View.
*/
private void dropView(View view) {
private void detachView(View view) {
UiThreadUtil.assertOnUiThread();
if (!mRootTags.get(view.getId())) {
// For non-root views we notify viewmanager with {@link ViewManager#onDropInstance}
@@ -396,13 +401,11 @@ import com.facebook.react.touch.JSResponderHandler;
for (int i = viewGroupManager.getChildCount(viewGroup) - 1; i >= 0; i--) {
View child = viewGroupManager.getChildAt(viewGroup, i);
if (mTagsToViews.get(child.getId()) != null) {
dropView(child);
detachView(child);
}
}
viewGroupManager.removeAllViews(viewGroup);
}
mTagsToViews.remove(view.getId());
mTagsToViewManagers.remove(view.getId());
}
public void removeRootView(int rootViewTag) {
@@ -412,7 +415,7 @@ import com.facebook.react.touch.JSResponderHandler;
"View with tag " + rootViewTag + " is not registered as a root view");
}
View rootView = mTagsToViews.get(rootViewTag);
dropView(rootView);
detachView(rootView);
mRootTags.delete(rootViewTag);
mRootViewsContext.remove(rootViewTag);
}

View File

@@ -90,6 +90,10 @@ public class NativeViewHierarchyOptimizer {
}
}
public void handleDropViews(int[] viewTagsToDrop, int length) {
mUIViewOperationQueue.enqueueDropViews(viewTagsToDrop, length);
}
/**
* Handles native children cleanup when css node is removed from hierarchy
*/

View File

@@ -261,6 +261,23 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
}
}
@ReactMethod
public void dropViews(ReadableArray viewTags) {
int size = viewTags.size(), realViewsCount = 0;
int realViewTags[] = new int[size];
for (int i = 0; i < size; i++) {
int tag = viewTags.getInt(i);
ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag);
if (!cssNode.isVirtual()) {
realViewTags[realViewsCount++] = tag;
}
mShadowNodeRegistry.removeNode(tag);
}
if (realViewsCount > 0) {
mNativeViewHierarchyOptimizer.handleDropViews(realViewTags, realViewsCount);
}
}
@ReactMethod
public void updateView(int tag, String className, ReadableMap props) {
ViewManager viewManager = mViewManagers.get(className);
@@ -405,7 +422,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
private void removeShadowNode(ReactShadowNode nodeToRemove) {
mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove);
mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag());
for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) {
removeShadowNode(nodeToRemove.getChildAt(i));
}

View File

@@ -154,4 +154,12 @@ import com.facebook.react.uimanager.events.TouchEventType;
return constants;
}
public static Map<String, Object> getLayoutOnlyPropsConstants() {
HashMap<String, Object> constants = new HashMap<>();
for (String propName : ViewProps.LAYOUT_ONLY_PROPS) {
constants.put(propName, Boolean.TRUE);
}
return constants;
}
}

View File

@@ -25,6 +25,7 @@ import com.facebook.react.common.MapBuilder;
private static final String CUSTOM_BUBBLING_EVENT_TYPES_KEY = "customBubblingEventTypes";
private static final String CUSTOM_DIRECT_EVENT_TYPES_KEY = "customDirectEventTypes";
private static final String LAYOUT_ONLY_PROPS = "layoutOnlyProps";
/**
* Generates map of constants that is then exposed by {@link UIManagerModule}. The constants map
@@ -75,6 +76,7 @@ import com.facebook.react.common.MapBuilder;
constants.put(CUSTOM_BUBBLING_EVENT_TYPES_KEY, bubblingEventTypesConstants);
constants.put(CUSTOM_DIRECT_EVENT_TYPES_KEY, directEventTypesConstants);
constants.put(LAYOUT_ONLY_PROPS, UIManagerModuleConstants.getLayoutOnlyPropsConstants());
return constants;
}

View File

@@ -142,6 +142,25 @@ public class UIViewOperationQueue {
}
}
private final class DropViewsOperation extends ViewOperation {
private final int[] mViewTagsToDrop;
private final int mArrayLength;
public DropViewsOperation(int[] viewTagsToDrop, int length) {
super(-1);
mViewTagsToDrop = viewTagsToDrop;
mArrayLength = length;
}
@Override
public void execute() {
for (int i = 0; i < mArrayLength; i++) {
mNativeViewHierarchyManager.dropView(mViewTagsToDrop[i]);
}
}
}
private final class ManageChildrenOperation extends ViewOperation {
private final @Nullable int[] mIndicesToRemove;
@@ -502,6 +521,10 @@ public class UIViewOperationQueue {
initialProps));
}
public void enqueueDropViews(int[] viewTagsToDrop, int length) {
mOperations.add(new DropViewsOperation(viewTagsToDrop, length));
}
public void enqueueUpdateProperties(int reactTag, String className, CatalystStylesDiffMap props) {
mOperations.add(new UpdatePropertiesOperation(reactTag, props));
}

View File

@@ -84,7 +84,7 @@ public class ViewProps {
Spacing.BOTTOM
};
private static final HashSet<String> LAYOUT_ONLY_PROPS = new HashSet<>(
/*package*/ static final HashSet<String> LAYOUT_ONLY_PROPS = new HashSet<>(
Arrays.asList(
ALIGN_SELF,
ALIGN_ITEMS,