Android Animated timing: interface-only

Summary:
This is (part of) a rewrite of D15390384.

This implements the lifecycle interface only for Fabric to signal to NativeAnimatedModule when preOperations are about to run / operations are about to be dispatched.

We will likely want to remove this mechanism entirely and rewrite NativeAnimatedModule in C++ for Fabric/Venice, but for now, I'm not sure of a better solution to unblock.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D21698193

fbshipit-source-id: a13d445073911fd63d896202a7a1bfbe1167038a
This commit is contained in:
Joshua Gross
2020-05-26 11:39:28 -07:00
committed by Facebook GitHub Bot
parent 8d6cc50d77
commit 0b00d92514
6 changed files with 104 additions and 7 deletions
@@ -8,6 +8,7 @@
package com.facebook.react.animated;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.facebook.common.logging.FLog;
import com.facebook.fbreact.specs.NativeAnimatedModuleSpec;
import com.facebook.infer.annotation.Assertions;
@@ -16,6 +17,8 @@ import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UIManagerListener;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
@@ -26,7 +29,6 @@ import com.facebook.react.uimanager.GuardedFrameCallback;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIManagerModuleListener;
import java.util.ArrayList;
/**
@@ -75,7 +77,7 @@ import java.util.ArrayList;
*/
@ReactModule(name = NativeAnimatedModule.NAME)
public class NativeAnimatedModule extends NativeAnimatedModuleSpec
implements LifecycleEventListener, UIManagerModuleListener {
implements LifecycleEventListener, UIManagerListener {
public static final String NAME = "NativeAnimatedModule";
@@ -132,7 +134,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
reactApplicationContext.addLifecycleEventListener(this);
UIManagerModule uiManager =
Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class));
uiManager.addUIManagerListener(this);
uiManager.addUIManagerEventListener(this);
}
}
@@ -141,8 +143,19 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
enqueueFrameCallback();
}
// For FabricUIManager
@Override
public void willDispatchViewUpdates(final UIManagerModule uiManager) {
public void willDispatchPreMountItems() {}
// For FabricUIManager
@Override
@UiThread
public void willDispatchMountItems() {}
// For non-FabricUIManager
@Override
@UiThread
public void willDispatchViewUpdates(final UIManager uiManager) {
if (mOperations.isEmpty() && mPreOperations.isEmpty()) {
return;
}
@@ -150,7 +163,15 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
final ArrayList<UIThreadOperation> operations = mOperations;
mPreOperations = new ArrayList<>();
mOperations = new ArrayList<>();
uiManager.prependUIBlock(
// This is kind of a hack. Basically UIManagerListener cannot import UIManagerModule
// (that would cause an import cycle) and they're not in the same package. But,
// UIManagerModule is the only thing that calls `willDispatchViewUpdates` so we
// know this is safe.
// This goes away entirely in Fabric/Venice.
UIManagerModule uiManagerModule = (UIManagerModule) uiManager;
uiManagerModule.prependUIBlock(
new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
@@ -160,7 +181,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
}
}
});
uiManager.addUIBlock(
uiManagerModule.addUIBlock(
new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
@@ -91,4 +91,19 @@ public interface UIManager extends JSIModule, PerformanceCounter {
* @param eventType
*/
void sendAccessibilityEvent(int reactTag, int eventType);
/**
* Register a {@link UIManagerListener} with this UIManager to receive lifecycle callbacks.
*
* @param listener
*/
void addUIManagerEventListener(UIManagerListener listener);
/**
* Unregister a {@link UIManagerListener} from this UIManager to stop receiving lifecycle
* callbacks.
*
* @param listener
*/
void removeUIManagerEventListener(UIManagerListener listener);
}
@@ -0,0 +1,21 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge;
/** Listener used to hook into the UIManager update process. */
public interface UIManagerListener {
/**
* Called right before view updates are dispatched at the end of a batch. This is useful if a
* module needs to add UIBlocks to the queue before it is flushed.
*/
void willDispatchViewUpdates(UIManager uiManager);
/** Called on the UI thread right before normal mount items are executed. */
void willDispatchMountItems();
/** Called on the UI thread right before premount items are executed. */
void willDispatchPreMountItems();
}
@@ -42,6 +42,7 @@ import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.RetryableMountingLayerException;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UIManagerListener;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.config.ReactFeatureFlags;
@@ -82,6 +83,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@SuppressLint("MissingNativeLoadLibrary")
public class FabricUIManager implements UIManager, LifecycleEventListener {
@@ -123,6 +125,9 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
@NonNull
private List<DispatchCommandMountItem> mViewCommandMountItems = new ArrayList<>();
@NonNull
private final CopyOnWriteArrayList<UIManagerListener> mListeners = new CopyOnWriteArrayList<>();
@GuardedBy("mMountItemsLock")
@NonNull
private List<MountItem> mMountItems = new ArrayList<>();
@@ -500,6 +505,14 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
}
}
public void addUIManagerEventListener(UIManagerListener listener) {
mListeners.add(listener);
}
public void removeUIManagerEventListener(UIManagerListener listener) {
mListeners.remove(listener);
}
/**
* This method enqueues UI operations directly to the UI thread. This might change in the future
* to enforce execution order using {@link ReactChoreographer#CallbackType}.
@@ -581,6 +594,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
return;
}
for (UIManagerListener listener : mListeners) {
listener.willDispatchMountItems();
}
final boolean didDispatchItems;
try {
didDispatchItems = dispatchMountItems();
@@ -794,6 +811,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
// reentering during dispatchPreMountItems
mInDispatch = true;
for (UIManagerListener listener : mListeners) {
listener.willDispatchPreMountItems();
}
try {
while (true) {
long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000);
@@ -34,6 +34,7 @@ import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UIManagerListener;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
@@ -117,6 +118,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule
private final UIImplementation mUIImplementation;
private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();
private final List<UIManagerModuleListener> mListeners = new ArrayList<>();
private final List<UIManagerListener> mUIManagerListeners = new ArrayList<>();
private @Nullable Map<String, WritableMap> mViewManagerConstantsCache;
private volatile int mViewManagerConstantsCacheSize;
@@ -793,6 +795,9 @@ public class UIManagerModule extends ReactContextBaseJavaModule
for (UIManagerModuleListener listener : mListeners) {
listener.willDispatchViewUpdates(this);
}
for (UIManagerListener listener : mUIManagerListeners) {
listener.willDispatchViewUpdates(this);
}
try {
mUIImplementation.dispatchViewUpdates(batchId);
} finally {
@@ -850,14 +855,24 @@ public class UIManagerModule extends ReactContextBaseJavaModule
mUIImplementation.prependUIBlock(block);
}
@Deprecated
public void addUIManagerListener(UIManagerModuleListener listener) {
mListeners.add(listener);
}
@Deprecated
public void removeUIManagerListener(UIManagerModuleListener listener) {
mListeners.remove(listener);
}
public void addUIManagerEventListener(UIManagerListener listener) {
mUIManagerListeners.add(listener);
}
public void removeUIManagerEventListener(UIManagerListener listener) {
mUIManagerListeners.remove(listener);
}
/**
* Given a reactTag from a component, find its root node tag, if possible. Otherwise, this will
* return 0. If the reactTag belongs to a root node, this will return the same reactTag.
@@ -7,7 +7,11 @@
package com.facebook.react.uimanager;
/** Listener used to hook into the UIManager update process. */
/**
* Listener used to hook into the UIManager update process. Deprecated: use UIManagerListener
* instead. This will be deleted in some future release.
*/
@Deprecated
public interface UIManagerModuleListener {
/**
* Called right before view updates are dispatched at the end of a batch. This is useful if a