Implement lazy discovery for ViewManagers.

Reviewed By: kathryngray

Differential Revision: D5865095

fbshipit-source-id: c94970e4cd7aafb20cf844c48feea053ac8b6b0f
This commit is contained in:
Dmitry Zakharov
2017-09-28 09:40:39 -07:00
committed by Facebook Github Bot
parent c4f7ce9afd
commit da30b04703
24 changed files with 469 additions and 169 deletions

View File

@@ -55,6 +55,18 @@ public class UIImplementation {
private long mLastCalculateLayoutTime = 0;
public UIImplementation(
ReactApplicationContext reactContext,
UIManagerModule.ViewManagerResolver viewManagerResolver,
EventDispatcher eventDispatcher,
int minTimeLeftInFrameForNonBatchedOperationMs) {
this(
reactContext,
new ViewManagerRegistry(viewManagerResolver),
eventDispatcher,
minTimeLeftInFrameForNonBatchedOperationMs);
}
public UIImplementation(
ReactApplicationContext reactContext,
List<ViewManager> viewManagers,

View File

@@ -18,10 +18,25 @@ import java.util.List;
public class UIImplementationProvider {
public UIImplementation createUIImplementation(
ReactApplicationContext reactContext,
List<ViewManager> viewManagers,
UIManagerModule.ViewManagerResolver viewManagerResolver,
EventDispatcher eventDispatcher,
int minTimeLeftInFrameForNonBatchedOperationMs) {
return new UIImplementation(
reactContext, viewManagers, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
reactContext,
viewManagerResolver,
eventDispatcher,
minTimeLeftInFrameForNonBatchedOperationMs);
}
public UIImplementation createUIImplementation(
ReactApplicationContext reactContext,
List<ViewManager> viewManagerList,
EventDispatcher eventDispatcher,
int minTimeLeftInFrameForNonBatchedOperationMs) {
return new UIImplementation(
reactContext,
viewManagerList,
eventDispatcher,
minTimeLeftInFrameForNonBatchedOperationMs);
}
}

View File

@@ -17,7 +17,9 @@ import android.content.res.Configuration;
import com.facebook.common.logging.FLog;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.animation.Animation;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.LifecycleEventListener;
@@ -29,6 +31,7 @@ import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
@@ -72,6 +75,21 @@ import javax.annotation.Nullable;
public class UIManagerModule extends ReactContextBaseJavaModule implements
OnBatchCompleteListener, LifecycleEventListener, PerformanceCounter {
/**
* Enables lazy discovery of a specific {@link ViewManager} by its name.
*/
public interface ViewManagerResolver {
/**
* {@class UIManagerModule} class uses this method to get a ViewManager by its name.
* This is the same name that comes from JS by {@code UIManager.ViewManagerName} call.
*/
@Nullable ViewManager getViewManager(String viewManagerName);
/**
* Provides a list of view manager names to register in JS as {@code UIManager.ViewManagerName}
*/
List<String> getViewManagerNames();
}
/**
* Resolves a name coming from native side to a name of the event that is exposed to JS.
@@ -90,6 +108,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
private final EventDispatcher mEventDispatcher;
private final Map<String, Object> mModuleConstants;
private final Map<String, Object> mCustomDirectEvents;
private final UIImplementation mUIImplementation;
private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();
@@ -97,24 +116,45 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
public UIManagerModule(
ReactApplicationContext reactContext,
List<ViewManager> viewManagerList,
ViewManagerResolver viewManagerResolver,
UIImplementationProvider uiImplementationProvider,
boolean lazyViewManagersEnabled,
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
mEventDispatcher = new EventDispatcher(reactContext);
mModuleConstants = createConstants(viewManagerList, lazyViewManagersEnabled);
mModuleConstants = createConstants(viewManagerResolver);
mCustomDirectEvents = UIManagerModuleConstants.getDirectEventTypeConstants();
mUIImplementation =
uiImplementationProvider.createUIImplementation(
reactContext,
viewManagerList,
viewManagerResolver,
mEventDispatcher,
minTimeLeftInFrameForNonBatchedOperationMs);
reactContext.addLifecycleEventListener(this);
}
public UIManagerModule(
ReactApplicationContext reactContext,
List<ViewManager> viewManagersList,
UIImplementationProvider uiImplementationProvider,
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
mEventDispatcher = new EventDispatcher(reactContext);
mModuleConstants = createConstants(viewManagersList);
mCustomDirectEvents =
(Map<String, Object>) mModuleConstants.get(
UIManagerModuleConstantsHelper.CUSTOM_DIRECT_EVENTS_KEY);
mUIImplementation =
uiImplementationProvider.createUIImplementation(
reactContext,
viewManagersList,
mEventDispatcher,
minTimeLeftInFrameForNonBatchedOperationMs);
reactContext.addLifecycleEventListener(this);
}
/**
* This method gives an access to the {@link UIImplementation} object that can be used to execute
* operations on the view hierarchy.
@@ -163,21 +203,55 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
ViewManagerPropertyUpdater.clear();
}
private static Map<String, Object> createConstants(
List<ViewManager> viewManagerList,
boolean lazyViewManagersEnabled) {
private static Map<String, Object> createConstants(ViewManagerResolver viewManagerResolver) {
ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants");
try {
return UIManagerModuleConstantsHelper.createConstants(
viewManagerList,
lazyViewManagersEnabled);
return UIManagerModuleConstantsHelper.createConstants(viewManagerResolver);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END);
}
}
private static Map<String, Object> createConstants(List<ViewManager> viewManagers) {
ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants");
try {
return UIManagerModuleConstantsHelper.createConstants(viewManagers);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END);
}
}
@DoNotStrip
@ReactMethod(isBlockingSynchronousMethod = true)
public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) {
ViewManager targetView =
viewManagerName != null ? mUIImplementation.resolveViewManager(viewManagerName) : null;
if (targetView == null) {
return null;
}
SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "constants for ViewManager")
.arg("ViewManager", targetView.getName())
.arg("Lazy", true)
.flush();
try {
Map<String, Object> viewManagerConstants =
UIManagerModuleConstantsHelper.createConstantsForViewManager(
targetView,
UIManagerModuleConstants.getBubblingEventTypeConstants(),
UIManagerModuleConstants.getDirectEventTypeConstants(),
null,
mCustomDirectEvents);
return viewManagerConstants != null ? Arguments.makeNativeMap(viewManagerConstants) : null;
} finally {
SystraceMessage.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
/**
* Resolves Direct Event name exposed to JS from the one known to the Native side.
*/
@@ -185,14 +259,10 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
return new CustomEventNamesResolver() {
@Override
public @Nullable String resolveCustomEventName(String eventName) {
Map<String, Map> directEventTypes =
(Map<String, Map>) getConstants().get(
UIManagerModuleConstantsHelper.CUSTOM_DIRECT_EVENT_TYPES_KEY);
if (directEventTypes != null) {
Map<String, String> customEventType = (Map<String, String>) directEventTypes.get(eventName);
if (customEventType != null) {
return customEventType.get("registrationName");
}
Map<String, String> customEventType =
(Map<String, String>) mCustomDirectEvents.get(eventName);
if (customEventType != null) {
return customEventType.get("registrationName");
}
return eventName;
}

View File

@@ -9,14 +9,14 @@
package com.facebook.react.uimanager;
import java.util.List;
import java.util.Map;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
import com.facebook.react.common.MapBuilder;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Helps generate constants map for {@link UIManagerModule} by collecting and merging constants from
@@ -24,8 +24,22 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
*/
/* package */ class UIManagerModuleConstantsHelper {
/* package */ static final String CUSTOM_BUBBLING_EVENT_TYPES_KEY = "customBubblingEventTypes";
/* package */ static final String CUSTOM_DIRECT_EVENT_TYPES_KEY = "customDirectEventTypes";
/* package */ static final String CUSTOM_BUBBLING_EVENTS_KEY = "customBubblingEventTypes";
/* package */ static final String CUSTOM_DIRECT_EVENTS_KEY = "customDirectEventTypes";
/**
* Generates a lazy discovery enabled version of {@link UIManagerModule} constants. It only
* contains a list of view manager names, so that JS side is aware of the managers there are.
* Actual ViewManager instantiation happens when {@code UIManager.SpecificViewManager} call happens.
* The View Manager is then registered on the JS side with the help of
* {@code UIManagerModule.getConstantsForViewManager}.
*/
/* package */ static Map<String, Object> createConstants(
UIManagerModule.ViewManagerResolver resolver) {
Map<String, Object> constants = UIManagerModuleConstants.getConstants();
constants.put("ViewManagerNames", resolver.getViewManagerNames());
return constants;
}
/**
* Generates map of constants that is then exposed by {@link UIManagerModule}.
@@ -39,8 +53,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
* {@link UIManagerModuleConstants}.
* TODO(6845124): Create a test for this
*/
/* package */ static Map<String, Object> createConstants(
List<ViewManager> viewManagers, boolean lazyViewManagersEnabled) {
/* package */ static Map<String, Object> createConstants(List<ViewManager> viewManagers) {
Map<String, Object> constants = UIManagerModuleConstants.getConstants();
// Generic/default event types:
@@ -58,43 +71,22 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
allDirectEventTypes.putAll(genericDirectEventTypes);
for (ViewManager viewManager : viewManagers) {
final String viewManagerName = viewManager.getName();
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "constants for ViewManager")
.arg("ViewManager", viewManager.getName())
.flush();
.arg("ViewManager", viewManagerName)
.arg("Lazy", false)
.flush();
try {
Map viewManagerConstants = MapBuilder.newHashMap();
Map viewManagerBubblingEvents = viewManager.getExportedCustomBubblingEventTypeConstants();
if (viewManagerBubblingEvents != null) {
recursiveMerge(allBubblingEventTypes, viewManagerBubblingEvents);
recursiveMerge(viewManagerBubblingEvents, genericBubblingEventTypes);
} else {
viewManagerBubblingEvents = genericBubblingEventTypes;
}
viewManagerConstants.put("bubblingEventTypes", viewManagerBubblingEvents);
Map viewManagerDirectEvents = viewManager.getExportedCustomDirectEventTypeConstants();
if (viewManagerDirectEvents != null) {
recursiveMerge(allDirectEventTypes, viewManagerDirectEvents);
recursiveMerge(viewManagerDirectEvents, genericDirectEventTypes);
} else {
viewManagerDirectEvents = genericDirectEventTypes;
}
viewManagerConstants.put("directEventTypes", viewManagerDirectEvents);
Map customViewConstants = viewManager.getExportedViewConstants();
if (customViewConstants != null) {
viewManagerConstants.put("Constants", customViewConstants);
}
Map viewManagerCommands = viewManager.getCommandsMap();
if (viewManagerCommands != null) {
viewManagerConstants.put("Commands", viewManagerCommands);
}
Map<String, String> viewManagerNativeProps = viewManager.getNativeProps();
if (!viewManagerNativeProps.isEmpty()) {
viewManagerConstants.put("NativeProps", viewManagerNativeProps);
}
Map viewManagerConstants = createConstantsForViewManager(
viewManager,
genericBubblingEventTypes,
genericDirectEventTypes,
allBubblingEventTypes,
allDirectEventTypes);
if (!viewManagerConstants.isEmpty()) {
constants.put(viewManager.getName(), viewManagerConstants);
constants.put(viewManagerName, viewManagerConstants);
}
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
@@ -102,13 +94,57 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
}
// Used by https://fburl.com/6nskr82o
constants.put(CUSTOM_BUBBLING_EVENT_TYPES_KEY, allBubblingEventTypes);
constants.put(CUSTOM_DIRECT_EVENT_TYPES_KEY, allDirectEventTypes);
constants.put("AndroidLazyViewManagersEnabled", lazyViewManagersEnabled);
constants.put(CUSTOM_BUBBLING_EVENTS_KEY, allBubblingEventTypes);
constants.put(CUSTOM_DIRECT_EVENTS_KEY, allDirectEventTypes);
return constants;
}
/* package */ static Map<String, Object> createConstantsForViewManager(
ViewManager viewManager,
Map defaultBubblingEvents,
Map defaultDirectEvents,
@Nullable Map cumulativeBubblingEventTypes,
@Nullable Map cumulativeDirectEventTypes) {
Map<String, Object> viewManagerConstants = MapBuilder.newHashMap();
Map viewManagerBubblingEvents = viewManager.getExportedCustomBubblingEventTypeConstants();
if (viewManagerBubblingEvents != null) {
if (cumulativeBubblingEventTypes != null) {
recursiveMerge(cumulativeBubblingEventTypes, viewManagerBubblingEvents);
}
recursiveMerge(viewManagerBubblingEvents, defaultBubblingEvents);
} else {
viewManagerBubblingEvents = defaultBubblingEvents;
}
viewManagerConstants.put("bubblingEventTypes", viewManagerBubblingEvents);
Map viewManagerDirectEvents = viewManager.getExportedCustomDirectEventTypeConstants();
if (viewManagerDirectEvents != null) {
if (cumulativeDirectEventTypes != null) {
recursiveMerge(cumulativeDirectEventTypes, viewManagerBubblingEvents);
}
recursiveMerge(viewManagerDirectEvents, defaultDirectEvents);
} else {
viewManagerDirectEvents = defaultDirectEvents;
}
viewManagerConstants.put("directEventTypes", viewManagerDirectEvents);
Map customViewConstants = viewManager.getExportedViewConstants();
if (customViewConstants != null) {
viewManagerConstants.put("Constants", customViewConstants);
}
Map viewManagerCommands = viewManager.getCommandsMap();
if (viewManagerCommands != null) {
viewManagerConstants.put("Commands", viewManagerCommands);
}
Map<String, String> viewManagerNativeProps = viewManager.getNativeProps();
if (!viewManagerNativeProps.isEmpty()) {
viewManagerConstants.put("NativeProps", viewManagerNativeProps);
}
return viewManagerConstants;
}
/**
* Merges {@param source} map into {@param dest} map recursively
*/

View File

@@ -9,35 +9,53 @@
package com.facebook.react.uimanager;
import java.util.HashMap;
import com.facebook.react.common.MapBuilder;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Class that stores the mapping between native view name used in JS and the corresponding instance
* of {@link ViewManager}.
*/
public class ViewManagerRegistry {
public final class ViewManagerRegistry {
private final Map<String, ViewManager> mViewManagers;
private final @Nullable UIManagerModule.ViewManagerResolver mViewManagerResolver;
public ViewManagerRegistry(UIManagerModule.ViewManagerResolver viewManagerResolver) {
mViewManagers = MapBuilder.newHashMap();
mViewManagerResolver = viewManagerResolver;
}
public ViewManagerRegistry(List<ViewManager> viewManagerList) {
mViewManagers = new HashMap<>();
Map<String, ViewManager> viewManagerMap = MapBuilder.newHashMap();
for (ViewManager viewManager : viewManagerList) {
mViewManagers.put(viewManager.getName(), viewManager);
viewManagerMap.put(viewManager.getName(), viewManager);
}
mViewManagers = viewManagerMap;
mViewManagerResolver = null;
}
public ViewManagerRegistry(Map<String, ViewManager> viewManagerMap) {
mViewManagers = viewManagerMap;
mViewManagers =
viewManagerMap != null ? viewManagerMap : MapBuilder.<String, ViewManager>newHashMap();
mViewManagerResolver = null;
}
public ViewManager get(String className) {
ViewManager viewManager = mViewManagers.get(className);
if (viewManager != null) {
return viewManager;
} else {
throw new IllegalViewOperationException("No ViewManager defined for class " + className);
}
if (mViewManagerResolver != null) {
viewManager = mViewManagerResolver.getViewManager(className);
if (viewManager != null) {
mViewManagers.put(className, viewManager);
return viewManager;
}
}
throw new IllegalViewOperationException("No ViewManager defined for class " + className);
}
}