Skip to content

Commit

Permalink
Clean up View Recycling
Browse files Browse the repository at this point in the history
Summary:
Followups to View Recycling diffs to improve things / clean up things a bit. This also fixes memory warnings which were not hooked up before.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D36707792

fbshipit-source-id: 410e70bf0eeec5569566138af547e1601394d0e6
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed May 31, 2022
1 parent bffad43 commit a68dca3
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public void onFabricCommitEnd(DevToolsReactPerfLogger.FabricCommitPoint commitPo
@NonNull private final MountingManager mMountingManager;
@NonNull private final EventDispatcher mEventDispatcher;
@NonNull private final MountItemDispatcher mMountItemDispatcher;
@NonNull private final ViewManagerRegistry mViewManagerRegistry;

@NonNull private final EventBeatManager mEventBeatManager;

Expand Down Expand Up @@ -226,6 +227,9 @@ public FabricUIManager(
mShouldDeallocateEventDispatcher = false;
mEventBeatManager = eventBeatManager;
mReactApplicationContext.addLifecycleEventListener(this);

mViewManagerRegistry = viewManagerRegistry;
mReactApplicationContext.registerComponentCallbacks(viewManagerRegistry);
}

public FabricUIManager(
Expand All @@ -244,6 +248,9 @@ public FabricUIManager(
mShouldDeallocateEventDispatcher = true;
mEventBeatManager = eventBeatManager;
mReactApplicationContext.addLifecycleEventListener(this);

mViewManagerRegistry = viewManagerRegistry;
mReactApplicationContext.registerComponentCallbacks(viewManagerRegistry);
}

// TODO (T47819352): Rename this to startSurface for consistency with xplat/iOS
Expand Down Expand Up @@ -451,6 +458,8 @@ public void onCatalystInstanceDestroy() {
mEventDispatcher.removeBatchEventDispatchedListener(mEventBeatManager);
mEventDispatcher.unregisterEventEmitter(FABRIC);

mReactApplicationContext.unregisterComponentCallbacks(mViewManagerRegistry);

// Remove lifecycle listeners (onHostResume, onHostPause) since the FabricUIManager is going
// away. Then stop the mDispatchUIFrameCallback false will cause the choreographer
// callbacks to stop firing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ protected T prepareToRecycleView(@NonNull ThemedReactContext reactContext, T vie
view.setOutlineAmbientShadowColor(Color.BLACK);
view.setOutlineSpotShadowColor(Color.BLACK);

// Focus IDs
// Also see in AOSP source:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#4493
view.setNextFocusDownId(View.NO_ID);
view.setNextFocusForwardId(View.NO_ID);
view.setNextFocusRightId(View.NO_ID);
view.setNextFocusUpId(View.NO_ID);

// This is possibly subject to change and overrideable per-platform, but these
// are the default view flags in View.java:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#2712
// `mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT`
// Therefore we set the following options as such:
view.setFocusable(false);
view.setFocusableInTouchMode(false);

// https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r96/core/java/android/view/View.java#5491
view.setElevation(0);

// Predictably, alpha defaults to 1:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#2186
// This accounts for resetting mBackfaceOpacity and mBackfaceVisibility
view.setAlpha(1);

// setPadding is a noop for most View types, but it is not for Text
setPadding(view, 0, 0, 0, 0);

// Other stuff
view.setForeground(null);

return view;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ public Map<String, Object> getConstants() {
@Override
public void initialize() {
getReactApplicationContext().registerComponentCallbacks(mMemoryTrimCallback);
getReactApplicationContext().registerComponentCallbacks(mViewManagerRegistry);
mEventDispatcher.registerEventEmitter(
DEFAULT, getReactApplicationContext().getJSModule(RCTEventEmitter.class));
}
Expand Down Expand Up @@ -234,6 +235,7 @@ public void onCatalystInstanceDestroy() {

ReactApplicationContext reactApplicationContext = getReactApplicationContext();
reactApplicationContext.unregisterComponentCallbacks(mMemoryTrimCallback);
reactApplicationContext.unregisterComponentCallbacks(mViewManagerRegistry);
YogaNodePool.get().clear();
ViewManagerPropertyUpdater.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,23 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode>
* null signals that View Recycling is disabled. `enableViewRecycling` must be explicitly called
* in a concrete constructor to enable View Recycling per ViewManager.
*/
private HashMap<Integer, Stack<T>> mRecyclableViews = null;
@Nullable private HashMap<Integer, Stack<T>> mRecyclableViews = null;

private int mRecyclableViewsBufferSize = 1024;

/** Call in constructor of concrete ViewManager class to enable. */
protected void enableViewRecycling() {
protected void setupViewRecycling() {
if (ReactFeatureFlags.enableViewRecycling) {
mRecyclableViews = new HashMap<>();
}
}

/** Call in constructor of concrete ViewManager class to enable. */
protected void setupViewRecycling(int bufferSize) {
mRecyclableViewsBufferSize = bufferSize;
setupViewRecycling();
}

private @Nullable Stack<T> getRecyclableViewStack(int surfaceId) {
if (mRecyclableViews == null) {
return null;
Expand Down Expand Up @@ -191,12 +199,16 @@ public C createShadowNodeInstance() {
* {@link ViewManager} subclass.
*/
public void onDropViewInstance(@NonNull T view) {
@Nullable
Stack<T> recyclableViews =
getRecyclableViewStack(((ThemedReactContext) view.getContext()).getSurfaceId());
// By default we treat views as recyclable
if (recyclableViews != null) {
recyclableViews.push(prepareToRecycleView((ThemedReactContext) view.getContext(), view));
// View recycling
ThemedReactContext themedReactContext = (ThemedReactContext) view.getContext();
int surfaceId = themedReactContext.getSurfaceId();
@Nullable Stack<T> recyclableViews = getRecyclableViewStack(surfaceId);

// Any max buffer size <0 results in an infinite buffer size
if (recyclableViews != null
&& (mRecyclableViewsBufferSize < 0
|| recyclableViews.size() < mRecyclableViewsBufferSize)) {
recyclableViews.push(prepareToRecycleView(themedReactContext, view));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
* Class that stores the mapping between native view name used in JS and the corresponding instance
* of {@link ViewManager}.
*/
public final class ViewManagerRegistry {
public final class ViewManagerRegistry implements ComponentCallbacks2 {

private final Map<String, ViewManager> mViewManagers;
private final @Nullable ViewManagerResolver mViewManagerResolver;
private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();

public ViewManagerRegistry(ViewManagerResolver viewManagerResolver) {
mViewManagers = MapBuilder.newHashMap();
Expand All @@ -46,40 +45,6 @@ public ViewManagerRegistry(Map<String, ViewManager> viewManagerMap) {
mViewManagerResolver = null;
}

/**
* Trim View Recycling memory aggressively. Whenever the system is running even slightly low on
* memory or is backgrounded, we immediately flush all recyclable Views. GC and memory swaps cause
* intense CPU pressure, so we always favor low memory usage over View recycling, even if there is
* only "moderate" pressure.
*/
private class MemoryTrimCallback implements ComponentCallbacks2 {
@Override
public void onTrimMemory(int level) {
Runnable runnable =
new Runnable() {
@Override
public void run() {
for (Map.Entry<String, ViewManager> entry : mViewManagers.entrySet()) {
entry.getValue().trimMemory();
}
}
};
if (UiThreadUtil.isOnUiThread()) {
runnable.run();
} else {
UiThreadUtil.runOnUiThread(runnable);
}
}

@Override
public void onConfigurationChanged(Configuration newConfig) {}

@Override
public void onLowMemory() {
this.onTrimMemory(0);
}
}

/**
* @param className {@link String} that identifies the {@link ViewManager} inside the {@link
* ViewManagerRegistry}. This methods {@throws IllegalViewOperationException} if there is no
Expand Down Expand Up @@ -147,4 +112,33 @@ public void run() {
UiThreadUtil.runOnUiThread(runnable);
}
}

/** ComponentCallbacks2 method. */
@Override
public void onTrimMemory(int level) {
Runnable runnable =
new Runnable() {
@Override
public void run() {
for (Map.Entry<String, ViewManager> entry : mViewManagers.entrySet()) {
entry.getValue().trimMemory();
}
}
};
if (UiThreadUtil.isOnUiThread()) {
runnable.run();
} else {
UiThreadUtil.runOnUiThread(runnable);
}
}

/** ComponentCallbacks2 method. */
@Override
public void onConfigurationChanged(Configuration newConfig) {}

/** ComponentCallbacks2 method. */
@Override
public void onLowMemory() {
this.onTrimMemory(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,18 @@ private void initView() {
mSpanned = null;
}

/* package */ void recycleView(ReactTextView defaultView) {
/* package */ void recycleView() {
// Set default field values
initView();

setForeground(null);
// Defaults for these fields:
// https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L1061
setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
setMovementMethod(getDefaultMovementMethod());
setJustificationMode(Layout.JUSTIFICATION_MODE_NONE);

// reset text
setLayoutParams(EMPTY_LAYOUT_PARAMS);
setMovementMethod(defaultView.getMovementMethod());
setBreakStrategy(defaultView.getBreakStrategy());
super.setText(null);

// Call setters to ensure that any super setters are called
Expand All @@ -124,30 +126,16 @@ private void initView() {
// reset data detectors
setLinkifyMask(0);

setJustificationMode(defaultView.getJustificationMode());

setEllipsizeLocation(mEllipsizeLocation);

// Focus IDs
// Also see in AOSP source:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#4493
setNextFocusDownId(View.NO_ID);
setNextFocusForwardId(View.NO_ID);
setNextFocusRightId(View.NO_ID);
setNextFocusUpId(View.NO_ID);

// https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r96/core/java/android/view/View.java#5491
setElevation(0);

// View flags - defaults are here:
// https://android.googlesource.com/platform/frameworks/base/+/98e54bb941cb6feb07127b75da37833281951d52/core/java/android/view/View.java#5311
// mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED |
// LAYOUT_DIRECTION_INHERIT;
setEnabled(true);
setFocusable(View.FOCUSABLE_AUTO);

// Things that could be set as a result of updateText/setText
setPadding(0, 0, 0, 0);
setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);

updateView(); // call after changing ellipsizeLocation in particular
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,25 @@ public class ReactTextViewManager

@VisibleForTesting public static final String REACT_CLASS = "RCTText";

private ReactTextView mDefaultViewForRecycling = null;

protected @Nullable ReactTextViewManagerCallback mReactTextViewManagerCallback;

public ReactTextViewManager() {
super();

enableViewRecycling();
setupViewRecycling();
}

@Override
protected ReactTextView prepareToRecycleView(
@NonNull ThemedReactContext reactContext, ReactTextView view) {
// TODO: use context as key
if (mDefaultViewForRecycling == null) {
mDefaultViewForRecycling = createViewInstance(reactContext);
}

// BaseViewManager
super.prepareToRecycleView(reactContext, view);

// Resets background and borders
view.recycleView(mDefaultViewForRecycling);
view.recycleView();

// Defaults from ReactTextAnchorViewManager
setSelectionColor(view, null);
setAndroidHyphenationFrequency(view, null);

return view;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,32 +176,6 @@ private void initView() {
// Reset background, borders
updateBackgroundDrawable(null);

setForeground(null);

// This is possibly subject to change and overrideable per-platform, but these
// are the default view flags in View.java:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#2712
// `mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT`
// Therefore we set the following options as such:
setFocusable(false);
setFocusableInTouchMode(false);

// Focus IDs
// Also see in AOSP source:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#4493
setNextFocusDownId(View.NO_ID);
setNextFocusForwardId(View.NO_ID);
setNextFocusRightId(View.NO_ID);
setNextFocusUpId(View.NO_ID);

// Predictable, alpha defaults to 1:
// https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#2186
// This accounts for resetting mBackfaceOpacity and mBackfaceVisibility
setAlpha(1);

// https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r96/core/java/android/view/View.java#5491
setElevation(0);

resetPointerEvents();
}

Expand Down Expand Up @@ -813,11 +787,6 @@ public void setOverflowInset(int left, int top, int right, int bottom) {
mOverflowInset.set(left, top, right, bottom);
}

public void setOverflowInset(Rect overflowInset) {
mOverflowInset.set(
overflowInset.left, overflowInset.top, overflowInset.right, overflowInset.bottom);
}

@Override
public Rect getOverflowInset() {
return mOverflowInset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,10 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
private static final int CMD_SET_PRESSED = 2;
private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate";

private ReactViewGroup mDefaultViewForRecycling = null;

public ReactViewManager() {
super();

enableViewRecycling();
setupViewRecycling();
}

@Override
Expand Down

0 comments on commit a68dca3

Please sign in to comment.