/** * Instantiates a new {@link ReactInstanceManagerImpl}. Before calling {@code build}, the * following must be called: * * <ul> * <li>{@link #setApplication} * <li>{@link #setJSBundleFile} or {@link #setJSMainModuleName} * </ul> */ public ReactInstanceManager build() { Assertions.assertCondition( mUseDeveloperSupport || mJSBundleFile != null, "JS Bundle File has to be provided when dev support is disabled"); Assertions.assertCondition( mJSMainModuleName != null || mJSBundleFile != null, "Either MainModuleName or JS Bundle File needs to be provided"); if (mUIImplementationProvider == null) { // create default UIImplementationProvider if the provided one is null. mUIImplementationProvider = new UIImplementationProvider(); } return new ReactInstanceManagerImpl( Assertions.assertNotNull( mApplication, "Application property has not been set with this builder"), mJSBundleFile, mJSMainModuleName, mPackages, mUseDeveloperSupport, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), mUIImplementationProvider, mNativeModuleCallExceptionHandler, mJSCConfig); }
public void runJSBundle() { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CatalystInstance_runJSBundle"); try { final CountDownLatch initLatch = new CountDownLatch(1); mCatalystQueueConfiguration .getJSQueueThread() .runOnQueue( new Runnable() { @Override public void run() { Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!"); mJSBundleHasLoaded = true; incrementPendingJSCalls(); mJSBundleLoader.loadScript(mBridge); initLatch.countDown(); } }); Assertions.assertCondition( initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS), "Timed out loading JS!"); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } }
/** * Recreate the react application and context. This should be called if configuration has changed * or the developer has requested the app to be reloaded. It should only be called after an * initial call to createReactContextInBackground. * * <p>Called from UI thread. */ public void recreateReactContextInBackground() { Assertions.assertCondition( mHasStartedCreatingInitialContext, "recreateReactContextInBackground should only be called after the initial " + "createReactContextInBackground call."); recreateReactContextInBackgroundInner(); }
private void setupReactContext(ReactApplicationContext reactContext) { Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "setupReactContext"); UiThreadUtil.assertOnUiThread(); Assertions.assertCondition(mCurrentReactContext == null); mCurrentReactContext = Assertions.assertNotNull(reactContext); CatalystInstance catalystInstance = Assertions.assertNotNull(reactContext.getCatalystInstance()); catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); mMemoryPressureRouter.addMemoryPressureListener(catalystInstance); moveReactContextToCurrentLifecycleState(); for (ReactRootView rootView : mAttachedRootViews) { attachMeasuredRootViewToInstance(rootView, catalystInstance); } ReactInstanceEventListener[] listeners = new ReactInstanceEventListener[mReactInstanceEventListeners.size()]; listeners = mReactInstanceEventListeners.toArray(listeners); for (ReactInstanceEventListener listener : listeners) { listener.onReactContextInitialized(reactContext); } Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); }
private ReactBridge initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { mReactQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor"); ReactBridge bridge; try { bridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mReactQueueConfiguration.getNativeModulesQueueThread()); mMainExecutorToken = bridge.getMainExecutorToken(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig"); try { bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); bridge.setGlobalVariable( "__RCTProfileIsProfiling", Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false"); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } mJavaRegistry.notifyReactBridgeInitialized(bridge); return bridge; }
/** Initialize all the native modules */ @VisibleForTesting public void initialize() { UiThreadUtil.assertOnUiThread(); Assertions.assertCondition( !mInitialized, "This catalyst instance has already been initialized"); mInitialized = true; mJavaRegistry.notifyCatalystInstanceInitialized(); }
/*package*/ void removeAllViewsWithSubviewClippingEnabled() { Assertions.assertCondition(mRemoveClippedSubviews); Assertions.assertNotNull(mAllChildren); for (int i = 0; i < mAllChildrenCount; i++) { mAllChildren[i].removeOnLayoutChangeListener(mChildrenLayoutChangeListener); } removeAllViewsInLayout(); mAllChildrenCount = 0; }
/** * Trigger react context initialization asynchronously in a background async task. This enables * applications to pre-load the application JS, and execute global code before {@link * ReactRootView} is available and measured. This should only be called the first time the * application is set up, which is enforced to keep developers from accidentally creating their * application multiple times without realizing it. * * <p>Called from UI thread. */ @Override public void createReactContextInBackground() { Assertions.assertCondition( !mHasStartedCreatingInitialContext, "createReactContextInBackground should only be called when creating the react " + "application for the first time. When reloading JS, e.g. from a new file, explicitly" + "use recreateReactContextInBackground"); mHasStartedCreatingInitialContext = true; recreateReactContextInBackgroundInner(); }
@Override protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) { Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); try { JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create(); return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader())); } catch (Exception e) { // Pass exception to onPostExecute() so it can be handled on the main thread return Result.of(e); } }
private void initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread(); Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); mBridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mCatalystQueueConfiguration.getNativeModulesQueueThread()); mBridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); }
private void decrementPendingJSCalls() { int newPendingCalls = mPendingJSCalls.decrementAndGet(); Assertions.assertCondition(newPendingCalls >= 0); boolean isNowIdle = newPendingCalls == 0; Systrace.traceCounter( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, mJsPendingCallsTitleForTrace, newPendingCalls); if (isNowIdle && !mBridgeIdleListeners.isEmpty()) { for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { listener.onTransitionToBridgeIdle(); } } }
/*package*/ void addViewWithSubviewClippingEnabled(View child, int index, LayoutParams params) { Assertions.assertCondition(mRemoveClippedSubviews); Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); addInArray(child, index); // we add view as "clipped" and then run {@link #updateSubviewClipStatus} to conditionally // attach it int clippedSoFar = 0; for (int i = 0; i < index; i++) { if (mAllChildren[i].getParent() == null) { clippedSoFar++; } } updateSubviewClipStatus(mClippingRect, index, clippedSoFar); child.addOnLayoutChangeListener(mChildrenLayoutChangeListener); }
/*package*/ void removeViewWithSubviewClippingEnabled(View view) { Assertions.assertCondition(mRemoveClippedSubviews); Assertions.assertNotNull(mClippingRect); Assertions.assertNotNull(mAllChildren); view.removeOnLayoutChangeListener(mChildrenLayoutChangeListener); int index = indexOfChildInAllChildren(view); if (mAllChildren[index].getParent() != null) { int clippedSoFar = 0; for (int i = 0; i < index; i++) { if (mAllChildren[i].getParent() == null) { clippedSoFar++; } } super.removeViewsInLayout(index - clippedSoFar, 1); } removeFromArray(index); }
@Override protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) { // TODO(t11687218): Look over all threading // Default priority is Process.THREAD_PRIORITY_BACKGROUND which means we'll be put in a cgroup // that only has access to a small fraction of CPU time. The priority will be reset after // this task finishes: // https://android.googlesource.com/platform/frameworks/base/+/d630f105e8bc0021541aacb4dc6498a49048ecea/core/java/android/os/AsyncTask.java#256 Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); Assertions.assertCondition(params != null && params.length > 0 && params[0] != null); try { JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create(); return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader())); } catch (Exception e) { // Pass exception to onPostExecute() so it can be handled on the main thread return Result.of(e); } }
/** Sends the given Event to JS, coalescing eligible events if JS is backed up. */ public void dispatchEvent(Event event) { Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized"); synchronized (mEventsStagingLock) { mEventStaging.add(event); Systrace.startAsyncFlow( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); } if (mRCTEventEmitter != null) { // If the host activity is paused, the frame callback may not be currently // posted. Ensure that it is so that this event gets delivered promptly. mCurrentFrameCallback.maybePostFromNonUI(); } else { // No JS application has started yet, or resumed. This can happen when a ReactRootView is // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any // touch event dispatch will hit this codepath, and we simply queue them so that they // are dispatched once ReactContext creation completes and JS app is running. } }
private void setupReactContext(ReactApplicationContext reactContext) { UiThreadUtil.assertOnUiThread(); Assertions.assertCondition(mCurrentReactContext == null); mCurrentReactContext = Assertions.assertNotNull(reactContext); CatalystInstance catalystInstance = Assertions.assertNotNull(reactContext.getCatalystInstance()); catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); mMemoryPressureRouter.onNewReactContextCreated(reactContext); moveReactContextToCurrentLifecycleState(reactContext); for (ReactRootView rootView : mAttachedRootViews) { attachMeasuredRootViewToInstance(rootView, catalystInstance); } for (ReactInstanceEventListener listener : mReactInstanceEventListeners) { listener.onReactContextInitialized(reactContext); } }
private void addLayoutOnlyNodeToNonLayoutOnlyNode( ReactShadowNode nonLayoutOnlyNode, ReactShadowNode layoutOnlyNode, int index) { // Add all of the layout-only node's children to its parent instead int currentIndex = index; for (int i = 0; i < layoutOnlyNode.getChildCount(); i++) { ReactShadowNode childToAdd = layoutOnlyNode.getChildAt(i); Assertions.assertCondition(childToAdd.getNativeParent() == null); if (childToAdd.isLayoutOnly()) { // Adding this layout-only child could result in adding multiple native views int childCountBefore = nonLayoutOnlyNode.getNativeChildCount(); addLayoutOnlyNodeToNonLayoutOnlyNode(nonLayoutOnlyNode, childToAdd, currentIndex); int childCountAfter = nonLayoutOnlyNode.getNativeChildCount(); currentIndex += childCountAfter - childCountBefore; } else { addNonLayoutNodeToNonLayoutNode(nonLayoutOnlyNode, childToAdd, currentIndex); currentIndex++; } } }
private void transitionLayoutOnlyViewToNativeView( ReactShadowNode node, @Nullable CatalystStylesDiffMap props) { ReactShadowNode parent = node.getParent(); if (parent == null) { node.setIsLayoutOnly(false); return; } // First, remove the node from its parent. This causes the parent to update its native children // count. The removeNodeFromParent call will cause all the view's children to be detached from // their native parent. int childIndex = parent.indexOf(node); parent.removeChildAt(childIndex); removeNodeFromParent(node, false); node.setIsLayoutOnly(false); // Create the view since it doesn't exist in the native hierarchy yet mUIViewOperationQueue.enqueueCreateView( node.getRootNode().getReactTag(), node.getReactTag(), node.getViewClass(), props); // Add the node and all its children as if we are adding a new nodes parent.addChildAt(node, childIndex); addNodeToNode(parent, node, childIndex); for (int i = 0; i < node.getChildCount(); i++) { addNodeToNode(node, node.getChildAt(i), i); } // Update layouts since the children of the node were offset by its x/y position previously. // Bit of a hack: we need to update the layout of this node's children now that it's no longer // layout-only, but we may still receive more layout updates at the end of this batch that we // don't want to ignore. Assertions.assertCondition(mTagsWithLayoutVisited.size() == 0); applyLayoutBase(node); for (int i = 0; i < node.getChildCount(); i++) { applyLayoutBase(node.getChildAt(i)); } mTagsWithLayoutVisited.clear(); }
private CatalystInstance( final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec, final JavaScriptExecutor jsExecutor, final NativeModuleRegistry registry, final JavaScriptModulesConfig jsModulesConfig, final JSBundleLoader jsBundleLoader, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { mCatalystQueueConfiguration = CatalystQueueConfiguration.create( catalystQueueConfigurationSpec, new NativeExceptionHandler()); mBridgeIdleListeners = new CopyOnWriteArrayList<>(); mJavaRegistry = registry; mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstance.this, jsModulesConfig); mJSBundleLoader = jsBundleLoader; mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; mTraceListener = new JSProfilerTraceListener(); Systrace.registerListener(mTraceListener); final CountDownLatch initLatch = new CountDownLatch(1); mCatalystQueueConfiguration .getJSQueueThread() .runOnQueue( new Runnable() { @Override public void run() { initializeBridge(jsExecutor, jsModulesConfig); initLatch.countDown(); } }); try { Assertions.assertCondition( initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS), "Timed out waiting for bridge to initialize!"); } catch (InterruptedException e) { throw new RuntimeException(e); } }