diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK index fef2490d0..f027fd8d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK @@ -7,14 +7,15 @@ SUB_PROJECTS = [ android_library( name = 'common', srcs = glob(['**/*.java'], excludes=SUB_PROJECTS), - deps = [ - ':build_config', - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - ], exported_deps = [ react_native_dep('java/com/facebook/proguard/annotations:annotations'), ], + deps = [ + ':build_config', + react_native_dep('third-party/android/support/v4:lib-support-v4'), + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + ], visibility = [ 'PUBLIC', ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/ClearableSynchronizedPool.java b/ReactAndroid/src/main/java/com/facebook/react/common/ClearableSynchronizedPool.java new file mode 100644 index 000000000..783b2035a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/common/ClearableSynchronizedPool.java @@ -0,0 +1,48 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.common; + +import android.support.v4.util.Pools; + +/** + * Like {@link android.support.v4.util.Pools.SynchronizedPool} with the option to clear the pool + * (e.g. on memory pressure). + */ +public class ClearableSynchronizedPool implements Pools.Pool { + + private final Object[] mPool; + private int mSize = 0; + + public ClearableSynchronizedPool(int maxSize) { + mPool = new Object[maxSize]; + } + + @Override + public synchronized T acquire() { + if (mSize == 0) { + return null; + } + mSize--; + final int lastIndex = mSize; + T toReturn = (T) mPool[lastIndex]; + mPool[lastIndex] = null; + return toReturn; + } + + @Override + public synchronized boolean release(T obj) { + if (mSize == mPool.length) { + return false; + } + mPool[mSize] = obj; + mSize++; + return true; + } + + public synchronized void clear() { + for (int i = 0; i < mSize; i++) { + mPool[i] = null; + } + mSize = 0; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/CSSNodePool.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/CSSNodePool.java new file mode 100644 index 000000000..f1658e923 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/CSSNodePool.java @@ -0,0 +1,28 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.uimanager; + +import com.facebook.csslayout.CSSNode; +import com.facebook.react.common.ClearableSynchronizedPool; + +/** + * Static holder for a recycling pool of CSSNodes. + */ +public class CSSNodePool { + + private static final Object sInitLock = new Object(); + private static ClearableSynchronizedPool sPool; + + public static ClearableSynchronizedPool get() { + if (sPool != null) { + return sPool; + } + + synchronized (sInitLock) { + if (sPool == null) { + sPool = new ClearableSynchronizedPool(1024); + } + return sPool; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 8017ffedc..6193b187b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -74,7 +74,15 @@ public class ReactShadowNode { private float mAbsoluteBottom; private final Spacing mDefaultPadding = new Spacing(0); private final Spacing mPadding = new Spacing(CSSConstants.UNDEFINED); - private final CSSNode mCSSNode = new CSSNode(); + private final CSSNode mCSSNode; + + public ReactShadowNode() { + CSSNode node = CSSNodePool.get().acquire(); + if (node == null) { + node = new CSSNode(); + } + mCSSNode = node; + } /** * Nodes that return {@code true} will be treated as "virtual" nodes. That is, nodes that are not @@ -190,7 +198,7 @@ public class ReactShadowNode { return mChildren == null ? -1 : mChildren.indexOf(child); } - public void removeAllChildren() { + public void removeAndDisposeAllChildren() { if (getChildCount() == 0) { return; } @@ -202,6 +210,8 @@ public class ReactShadowNode { } ReactShadowNode toRemove = getChildAt(i); toRemove.mParent = null; + toRemove.dispose(); + decrease += toRemove.mIsLayoutOnly ? toRemove.mTotalNativeChildren : 1; } Assertions.assertNotNull(mChildren).clear(); @@ -654,4 +664,9 @@ public class ReactShadowNode { public String toString() { return mCSSNode.toString(); } + + public void dispose() { + mCSSNode.reset(); + CSSNodePool.get().release(mCSSNode); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index aa0089069..017083c3a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -652,12 +652,17 @@ public class UIImplementation { } protected final void removeShadowNode(ReactShadowNode nodeToRemove) { - mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); + removeShadowNodeRecursive(nodeToRemove); + nodeToRemove.dispose(); + } + + private void removeShadowNodeRecursive(ReactShadowNode nodeToRemove) { + NativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag()); for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { - removeShadowNode(nodeToRemove.getChildAt(i)); + removeShadowNodeRecursive(nodeToRemove.getChildAt(i)); } - nodeToRemove.removeAllChildren(); + nodeToRemove.removeAndDisposeAllChildren(); } private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) {