com.facebook.react.uimanager.UIViewOperationQueue.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.react.uimanager.UIViewOperationQueue.java

Source

/*
 * 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.uimanager;

import android.os.SystemClock;
import android.view.View;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * This class acts as a buffer for command executed on {@link NativeViewHierarchyManager}. It expose
 * similar methods as mentioned classes but instead of executing commands immediately it enqueues
 * those operations in a queue that is then flushed from {@link UIManagerModule} once JS batch of ui
 * operations is finished. This is to make sure that we execute all the JS operation coming from a
 * single batch a single loop of the main (UI) android looper.
 *
 * <p>TODO(7135923): Pooling of operation objects TODO(5694019): Consider a better data structure
 * for operations queue to save on allocations
 */
public class UIViewOperationQueue {

    public static final int DEFAULT_MIN_TIME_LEFT_IN_FRAME_FOR_NONBATCHED_OPERATION_MS = 8;

    private final int[] mMeasureBuffer = new int[4];

    /** A mutation or animation operation on the view hierarchy. */
    public interface UIOperation {

        void execute();
    }

    /** A spec for an operation on the native View hierarchy. */
    private abstract class ViewOperation implements UIOperation {

        public int mTag;

        public ViewOperation(int tag) {
            mTag = tag;
        }
    }

    private final class RemoveRootViewOperation extends ViewOperation {

        public RemoveRootViewOperation(int tag) {
            super(tag);
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.removeRootView(mTag);
        }
    }

    private final class UpdatePropertiesOperation extends ViewOperation {

        private final ReactStylesDiffMap mProps;

        private UpdatePropertiesOperation(int tag, ReactStylesDiffMap props) {
            super(tag);
            mProps = props;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.updateProperties(mTag, mProps);
        }
    }

    private final class EmitOnLayoutEventOperation extends ViewOperation {

        private final int mScreenX;
        private final int mScreenY;
        private final int mScreenWidth;
        private final int mScreenHeight;

        public EmitOnLayoutEventOperation(int tag, int screenX, int screenY, int screenWidth, int screenHeight) {
            super(tag);
            mScreenX = screenX;
            mScreenY = screenY;
            mScreenWidth = screenWidth;
            mScreenHeight = screenHeight;
        }

        @Override
        public void execute() {
            mReactApplicationContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
                    .dispatchEvent(OnLayoutEvent.obtain(mTag, mScreenX, mScreenY, mScreenWidth, mScreenHeight));
        }
    }

    private final class UpdateInstanceHandleOperation extends ViewOperation {

        private final long mInstanceHandle;

        private UpdateInstanceHandleOperation(int tag, long instanceHandle) {
            super(tag);
            mInstanceHandle = instanceHandle;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.updateInstanceHandle(mTag, mInstanceHandle);
        }
    }

    /**
     * Operation for updating native view's position and size. The operation is not created directly
     * by a {@link UIManagerModule} call from JS. Instead it gets inflated using computed position and
     * size values by CSSNodeDEPRECATED hierarchy.
     */
    private final class UpdateLayoutOperation extends ViewOperation {

        private final int mParentTag, mX, mY, mWidth, mHeight;

        public UpdateLayoutOperation(int parentTag, int tag, int x, int y, int width, int height) {
            super(tag);
            mParentTag = parentTag;
            mX = x;
            mY = y;
            mWidth = width;
            mHeight = height;
            Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
        }

        @Override
        public void execute() {
            Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
            mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight);
        }
    }

    private final class CreateViewOperation extends ViewOperation {

        private final ThemedReactContext mThemedContext;
        private final String mClassName;
        private final @Nullable ReactStylesDiffMap mInitialProps;

        public CreateViewOperation(ThemedReactContext themedContext, int tag, String className,
                @Nullable ReactStylesDiffMap initialProps) {
            super(tag);
            mThemedContext = themedContext;
            mClassName = className;
            mInitialProps = initialProps;
            Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
        }

        @Override
        public void execute() {
            Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
            mNativeViewHierarchyManager.createView(mThemedContext, mTag, mClassName, mInitialProps);
        }
    }

    private final class ManageChildrenOperation extends ViewOperation {

        private final @Nullable int[] mIndicesToRemove;
        private final @Nullable ViewAtIndex[] mViewsToAdd;
        private final @Nullable int[] mTagsToDelete;
        private final @Nullable int[] mIndicesToDelete;

        public ManageChildrenOperation(int tag, @Nullable int[] indicesToRemove, @Nullable ViewAtIndex[] viewsToAdd,
                @Nullable int[] tagsToDelete, @Nullable int[] indicesToDelete) {
            super(tag);
            mIndicesToRemove = indicesToRemove;
            mViewsToAdd = viewsToAdd;
            mTagsToDelete = tagsToDelete;
            mIndicesToDelete = indicesToDelete;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.manageChildren(mTag, mIndicesToRemove, mViewsToAdd, mTagsToDelete,
                    mIndicesToDelete);
        }
    }

    private final class SetChildrenOperation extends ViewOperation {

        private final ReadableArray mChildrenTags;

        public SetChildrenOperation(int tag, ReadableArray childrenTags) {
            super(tag);
            mChildrenTags = childrenTags;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.setChildren(mTag, mChildrenTags);
        }
    }

    private final class UpdateViewExtraData extends ViewOperation {

        private final Object mExtraData;

        public UpdateViewExtraData(int tag, Object extraData) {
            super(tag);
            mExtraData = extraData;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.updateViewExtraData(mTag, mExtraData);
        }
    }

    private final class ChangeJSResponderOperation extends ViewOperation {

        private final int mInitialTag;
        private final boolean mBlockNativeResponder;
        private final boolean mClearResponder;

        public ChangeJSResponderOperation(int tag, int initialTag, boolean clearResponder,
                boolean blockNativeResponder) {
            super(tag);
            mInitialTag = initialTag;
            mClearResponder = clearResponder;
            mBlockNativeResponder = blockNativeResponder;
        }

        @Override
        public void execute() {
            if (!mClearResponder) {
                mNativeViewHierarchyManager.setJSResponder(mTag, mInitialTag, mBlockNativeResponder);
            } else {
                mNativeViewHierarchyManager.clearJSResponder();
            }
        }
    }

    @Deprecated
    private final class DispatchCommandOperation extends ViewOperation {

        private final int mCommand;
        private final @Nullable ReadableArray mArgs;

        public DispatchCommandOperation(int tag, int command, @Nullable ReadableArray args) {
            super(tag);
            mCommand = command;
            mArgs = args;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs);
        }
    }

    private final class DispatchStringCommandOperation extends ViewOperation {

        private final String mCommand;
        private final @Nullable ReadableArray mArgs;

        public DispatchStringCommandOperation(int tag, String command, @Nullable ReadableArray args) {
            super(tag);
            mCommand = command;
            mArgs = args;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs);
        }
    }

    private final class ShowPopupMenuOperation extends ViewOperation {

        private final ReadableArray mItems;
        private final Callback mError;
        private final Callback mSuccess;

        public ShowPopupMenuOperation(int tag, ReadableArray items, Callback error, Callback success) {
            super(tag);
            mItems = items;
            mError = error;
            mSuccess = success;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.showPopupMenu(mTag, mItems, mSuccess, mError);
        }
    }

    private final class DismissPopupMenuOperation implements UIOperation {
        @Override
        public void execute() {
            mNativeViewHierarchyManager.dismissPopupMenu();
        }
    }

    /** A spec for animation operations (add/remove) */
    private abstract static class AnimationOperation implements UIViewOperationQueue.UIOperation {

        protected final int mAnimationID;

        public AnimationOperation(int animationID) {
            mAnimationID = animationID;
        }
    }

    private class SetLayoutAnimationEnabledOperation implements UIOperation {
        private final boolean mEnabled;

        private SetLayoutAnimationEnabledOperation(final boolean enabled) {
            mEnabled = enabled;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled);
        }
    }

    private class ConfigureLayoutAnimationOperation implements UIOperation {
        private final ReadableMap mConfig;
        private final Callback mAnimationComplete;

        private ConfigureLayoutAnimationOperation(final ReadableMap config, final Callback animationComplete) {
            mConfig = config;
            mAnimationComplete = animationComplete;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.configureLayoutAnimation(mConfig, mAnimationComplete);
        }
    }

    private final class MeasureOperation implements UIOperation {

        private final int mReactTag;
        private final Callback mCallback;

        private MeasureOperation(final int reactTag, final Callback callback) {
            super();
            mReactTag = reactTag;
            mCallback = callback;
        }

        @Override
        public void execute() {
            try {
                mNativeViewHierarchyManager.measure(mReactTag, mMeasureBuffer);
            } catch (NoSuchNativeViewException e) {
                // Invoke with no args to signal failure and to allow JS to clean up the callback
                // handle.
                mCallback.invoke();
                return;
            }

            float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
            float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
            float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
            float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
            mCallback.invoke(0, 0, width, height, x, y);
        }
    }

    private final class MeasureInWindowOperation implements UIOperation {

        private final int mReactTag;
        private final Callback mCallback;

        private MeasureInWindowOperation(final int reactTag, final Callback callback) {
            super();
            mReactTag = reactTag;
            mCallback = callback;
        }

        @Override
        public void execute() {
            try {
                mNativeViewHierarchyManager.measureInWindow(mReactTag, mMeasureBuffer);
            } catch (NoSuchNativeViewException e) {
                // Invoke with no args to signal failure and to allow JS to clean up the callback
                // handle.
                mCallback.invoke();
                return;
            }

            float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
            float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
            float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
            float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
            mCallback.invoke(x, y, width, height);
        }
    }

    private final class FindTargetForTouchOperation implements UIOperation {

        private final int mReactTag;
        private final float mTargetX;
        private final float mTargetY;
        private final Callback mCallback;

        private FindTargetForTouchOperation(final int reactTag, final float targetX, final float targetY,
                final Callback callback) {
            super();
            mReactTag = reactTag;
            mTargetX = targetX;
            mTargetY = targetY;
            mCallback = callback;
        }

        @Override
        public void execute() {
            try {
                mNativeViewHierarchyManager.measure(mReactTag, mMeasureBuffer);
            } catch (IllegalViewOperationException e) {
                mCallback.invoke();
                return;
            }

            // Because React coordinates are relative to root container, and measure() operates
            // on screen coordinates, we need to offset values using root container location.
            final float containerX = (float) mMeasureBuffer[0];
            final float containerY = (float) mMeasureBuffer[1];

            final int touchTargetReactTag = mNativeViewHierarchyManager.findTargetTagForTouch(mReactTag, mTargetX,
                    mTargetY);

            try {
                mNativeViewHierarchyManager.measure(touchTargetReactTag, mMeasureBuffer);
            } catch (IllegalViewOperationException e) {
                mCallback.invoke();
                return;
            }

            float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0] - containerX);
            float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1] - containerY);
            float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
            float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
            mCallback.invoke(touchTargetReactTag, x, y, width, height);
        }
    }

    private final class LayoutUpdateFinishedOperation implements UIOperation {

        private final ReactShadowNode mNode;
        private final UIImplementation.LayoutUpdateListener mListener;

        private LayoutUpdateFinishedOperation(ReactShadowNode node,
                UIImplementation.LayoutUpdateListener listener) {
            mNode = node;
            mListener = listener;
        }

        @Override
        public void execute() {
            mListener.onLayoutUpdated(mNode);
        }
    }

    private class UIBlockOperation implements UIOperation {
        private final UIBlock mBlock;

        public UIBlockOperation(UIBlock block) {
            mBlock = block;
        }

        @Override
        public void execute() {
            mBlock.execute(mNativeViewHierarchyManager);
        }
    }

    private final class SendAccessibilityEvent extends ViewOperation {

        private final int mEventType;

        private SendAccessibilityEvent(int tag, int eventType) {
            super(tag);
            mEventType = eventType;
        }

        @Override
        public void execute() {
            mNativeViewHierarchyManager.sendAccessibilityEvent(mTag, mEventType);
        }
    }

    private final NativeViewHierarchyManager mNativeViewHierarchyManager;
    private final Object mDispatchRunnablesLock = new Object();
    private final Object mNonBatchedOperationsLock = new Object();
    private final DispatchUIFrameCallback mDispatchUIFrameCallback;
    private final ReactApplicationContext mReactApplicationContext;

    // Only called from the UIManager queue?
    private ArrayList<UIOperation> mOperations = new ArrayList<>();

    @GuardedBy("mDispatchRunnablesLock")
    private ArrayList<Runnable> mDispatchUIRunnables = new ArrayList<>();

    @GuardedBy("mNonBatchedOperationsLock")
    private ArrayDeque<UIOperation> mNonBatchedOperations = new ArrayDeque<>();

    private @Nullable NotThreadSafeViewHierarchyUpdateDebugListener mViewHierarchyUpdateDebugListener;
    private boolean mIsDispatchUIFrameCallbackEnqueued = false;
    private boolean mIsInIllegalUIState = false;
    private boolean mIsProfilingNextBatch = false;
    private long mNonBatchedExecutionTotalTime;
    private long mProfiledBatchCommitStartTime;
    private long mProfiledBatchCommitEndTime;
    private long mProfiledBatchLayoutTime;
    private long mProfiledBatchDispatchViewUpdatesTime;
    private long mProfiledBatchRunStartTime;
    private long mProfiledBatchRunEndTime;
    private long mProfiledBatchBatchedExecutionTime;
    private long mProfiledBatchNonBatchedExecutionTime;
    private long mThreadCpuTime;
    private long mCreateViewCount;
    private long mUpdatePropertiesOperationCount;

    public UIViewOperationQueue(ReactApplicationContext reactContext,
            NativeViewHierarchyManager nativeViewHierarchyManager, int minTimeLeftInFrameForNonBatchedOperationMs) {
        mNativeViewHierarchyManager = nativeViewHierarchyManager;
        mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext,
                minTimeLeftInFrameForNonBatchedOperationMs == -1
                        ? DEFAULT_MIN_TIME_LEFT_IN_FRAME_FOR_NONBATCHED_OPERATION_MS
                        : minTimeLeftInFrameForNonBatchedOperationMs);
        mReactApplicationContext = reactContext;
    }

    /*package*/ NativeViewHierarchyManager getNativeViewHierarchyManager() {
        return mNativeViewHierarchyManager;
    }

    public void setViewHierarchyUpdateDebugListener(
            @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) {
        mViewHierarchyUpdateDebugListener = listener;
    }

    public void profileNextBatch() {
        mIsProfilingNextBatch = true;
        mProfiledBatchCommitStartTime = 0;
        mCreateViewCount = 0;
        mUpdatePropertiesOperationCount = 0;
    }

    public Map<String, Long> getProfiledBatchPerfCounters() {
        Map<String, Long> perfMap = new HashMap<>();
        perfMap.put("CommitStartTime", mProfiledBatchCommitStartTime);
        perfMap.put("CommitEndTime", mProfiledBatchCommitEndTime);
        perfMap.put("LayoutTime", mProfiledBatchLayoutTime);
        perfMap.put("DispatchViewUpdatesTime", mProfiledBatchDispatchViewUpdatesTime);
        perfMap.put("RunStartTime", mProfiledBatchRunStartTime);
        perfMap.put("RunEndTime", mProfiledBatchRunEndTime);
        perfMap.put("BatchedExecutionTime", mProfiledBatchBatchedExecutionTime);
        perfMap.put("NonBatchedExecutionTime", mProfiledBatchNonBatchedExecutionTime);
        perfMap.put("NativeModulesThreadCpuTime", mThreadCpuTime);
        perfMap.put("CreateViewCount", mCreateViewCount);
        perfMap.put("UpdatePropsCount", mUpdatePropertiesOperationCount);
        return perfMap;
    }

    public boolean isEmpty() {
        return mOperations.isEmpty();
    }

    public void addRootView(final int tag, final View rootView) {
        mNativeViewHierarchyManager.addRootView(tag, rootView);
    }

    /**
     * Enqueues a UIOperation to be executed in UI thread. This method should only be used by a
     * subclass to support UIOperations not provided by UIViewOperationQueue.
     */
    protected void enqueueUIOperation(UIOperation operation) {
        SoftAssertions.assertNotNull(operation);
        mOperations.add(operation);
    }

    public void enqueueRemoveRootView(int rootViewTag) {
        mOperations.add(new RemoveRootViewOperation(rootViewTag));
    }

    public void enqueueSetJSResponder(int tag, int initialTag, boolean blockNativeResponder) {
        mOperations.add(
                new ChangeJSResponderOperation(tag, initialTag, false /*clearResponder*/, blockNativeResponder));
    }

    public void enqueueClearJSResponder() {
        // Tag is 0 because JSResponderHandler doesn't need one in order to clear the responder.
        mOperations.add(new ChangeJSResponderOperation(0, 0, true /*clearResponder*/, false));
    }

    @Deprecated
    public void enqueueDispatchCommand(int reactTag, int commandId, @Nullable ReadableArray commandArgs) {
        mOperations.add(new DispatchCommandOperation(reactTag, commandId, commandArgs));
    }

    public void enqueueDispatchCommand(int reactTag, String commandId, @Nullable ReadableArray commandArgs) {
        mOperations.add(new DispatchStringCommandOperation(reactTag, commandId, commandArgs));
    }

    public void enqueueUpdateExtraData(int reactTag, Object extraData) {
        mOperations.add(new UpdateViewExtraData(reactTag, extraData));
    }

    public void enqueueShowPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
        mOperations.add(new ShowPopupMenuOperation(reactTag, items, error, success));
    }

    public void enqueueDismissPopupMenu() {
        mOperations.add(new DismissPopupMenuOperation());
    }

    public void enqueueCreateView(ThemedReactContext themedContext, int viewReactTag, String viewClassName,
            @Nullable ReactStylesDiffMap initialProps) {
        synchronized (mNonBatchedOperationsLock) {
            mCreateViewCount++;
            mNonBatchedOperations
                    .addLast(new CreateViewOperation(themedContext, viewReactTag, viewClassName, initialProps));
        }
    }

    public void enqueueUpdateInstanceHandle(int reactTag, long instanceHandle) {
        mOperations.add(new UpdateInstanceHandleOperation(reactTag, instanceHandle));
    }

    public void enqueueUpdateProperties(int reactTag, String className, ReactStylesDiffMap props) {
        mUpdatePropertiesOperationCount++;
        mOperations.add(new UpdatePropertiesOperation(reactTag, props));
    }

    public void enqueueOnLayoutEvent(int tag, int screenX, int screenY, int screenWidth, int screenHeight) {
        mOperations.add(new EmitOnLayoutEventOperation(tag, screenX, screenY, screenWidth, screenHeight));
    }

    public void enqueueUpdateLayout(int parentTag, int reactTag, int x, int y, int width, int height) {
        mOperations.add(new UpdateLayoutOperation(parentTag, reactTag, x, y, width, height));
    }

    public void enqueueManageChildren(int reactTag, @Nullable int[] indicesToRemove,
            @Nullable ViewAtIndex[] viewsToAdd, @Nullable int[] tagsToDelete, @Nullable int[] indicesToDelete) {
        mOperations.add(
                new ManageChildrenOperation(reactTag, indicesToRemove, viewsToAdd, tagsToDelete, indicesToDelete));
    }

    public void enqueueSetChildren(int reactTag, ReadableArray childrenTags) {
        mOperations.add(new SetChildrenOperation(reactTag, childrenTags));
    }

    public void enqueueSetLayoutAnimationEnabled(final boolean enabled) {
        mOperations.add(new SetLayoutAnimationEnabledOperation(enabled));
    }

    public void enqueueConfigureLayoutAnimation(final ReadableMap config, final Callback onAnimationComplete) {
        mOperations.add(new ConfigureLayoutAnimationOperation(config, onAnimationComplete));
    }

    public void enqueueMeasure(final int reactTag, final Callback callback) {
        mOperations.add(new MeasureOperation(reactTag, callback));
    }

    public void enqueueMeasureInWindow(final int reactTag, final Callback callback) {
        mOperations.add(new MeasureInWindowOperation(reactTag, callback));
    }

    public void enqueueFindTargetForTouch(final int reactTag, final float targetX, final float targetY,
            final Callback callback) {
        mOperations.add(new FindTargetForTouchOperation(reactTag, targetX, targetY, callback));
    }

    public void enqueueSendAccessibilityEvent(int tag, int eventType) {
        mOperations.add(new SendAccessibilityEvent(tag, eventType));
    }

    public void enqueueLayoutUpdateFinished(ReactShadowNode node, UIImplementation.LayoutUpdateListener listener) {
        mOperations.add(new LayoutUpdateFinishedOperation(node, listener));
    }

    public void enqueueUIBlock(UIBlock block) {
        mOperations.add(new UIBlockOperation(block));
    }

    public void prependUIBlock(UIBlock block) {
        mOperations.add(0, new UIBlockOperation(block));
    }

    public void dispatchViewUpdates(final int batchId, final long commitStartTime, final long layoutTime) {
        SystraceMessage
                .beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIViewOperationQueue.dispatchViewUpdates")
                .arg("batchId", batchId).flush();
        try {
            final long dispatchViewUpdatesTime = SystemClock.uptimeMillis();
            final long nativeModulesThreadCpuTime = SystemClock.currentThreadTimeMillis();

            // Store the current operation queues to dispatch and create new empty ones to continue
            // receiving new operations
            final ArrayList<UIOperation> batchedOperations;
            if (!mOperations.isEmpty()) {
                batchedOperations = mOperations;
                mOperations = new ArrayList<>();
            } else {
                batchedOperations = null;
            }

            final ArrayDeque<UIOperation> nonBatchedOperations;
            synchronized (mNonBatchedOperationsLock) {
                if (!mNonBatchedOperations.isEmpty()) {
                    nonBatchedOperations = mNonBatchedOperations;
                    mNonBatchedOperations = new ArrayDeque<>();
                } else {
                    nonBatchedOperations = null;
                }
            }

            if (mViewHierarchyUpdateDebugListener != null) {
                mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateEnqueued();
            }

            Runnable runOperations = new Runnable() {
                @Override
                public void run() {
                    SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchUI")
                            .arg("BatchId", batchId).flush();
                    try {
                        long runStartTime = SystemClock.uptimeMillis();

                        // All nonBatchedOperations should be executed before regular operations as
                        // regular operations may depend on them
                        if (nonBatchedOperations != null) {
                            for (UIOperation op : nonBatchedOperations) {
                                op.execute();
                            }
                        }

                        if (batchedOperations != null) {
                            for (UIOperation op : batchedOperations) {
                                op.execute();
                            }
                        }

                        if (mIsProfilingNextBatch && mProfiledBatchCommitStartTime == 0) {
                            mProfiledBatchCommitStartTime = commitStartTime;
                            mProfiledBatchCommitEndTime = SystemClock.uptimeMillis();
                            mProfiledBatchLayoutTime = layoutTime;
                            mProfiledBatchDispatchViewUpdatesTime = dispatchViewUpdatesTime;
                            mProfiledBatchRunStartTime = runStartTime;
                            mProfiledBatchRunEndTime = mProfiledBatchCommitEndTime;
                            mThreadCpuTime = nativeModulesThreadCpuTime;

                            Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
                                    "delayBeforeDispatchViewUpdates", 0, mProfiledBatchCommitStartTime * 1000000);
                            Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
                                    "delayBeforeDispatchViewUpdates", 0,
                                    mProfiledBatchDispatchViewUpdatesTime * 1000000);
                            Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
                                    "delayBeforeBatchRunStart", 0, mProfiledBatchDispatchViewUpdatesTime * 1000000);
                            Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
                                    "delayBeforeBatchRunStart", 0, mProfiledBatchRunStartTime * 1000000);
                        }

                        // Clear layout animation, as animation only apply to current UI operations batch.
                        mNativeViewHierarchyManager.clearLayoutAnimation();

                        if (mViewHierarchyUpdateDebugListener != null) {
                            mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateFinished();
                        }
                    } catch (Exception e) {
                        mIsInIllegalUIState = true;
                        throw e;
                    } finally {
                        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
                    }
                }
            };

            SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "acquiring mDispatchRunnablesLock")
                    .arg("batchId", batchId).flush();
            synchronized (mDispatchRunnablesLock) {
                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
                mDispatchUIRunnables.add(runOperations);
            }

            // In the case where the frame callback isn't enqueued, the UI isn't being displayed or is
            // being
            // destroyed. In this case it's no longer important to align to frames, but it is important to
            // make
            // sure any late-arriving UI commands are executed.
            if (!mIsDispatchUIFrameCallbackEnqueued) {
                UiThreadUtil.runOnUiThread(new GuardedRunnable(mReactApplicationContext) {
                    @Override
                    public void runGuarded() {
                        flushPendingBatches();
                    }
                });
            }
        } finally {
            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
        }
    }

    /* package */ void resumeFrameCallback() {
        mIsDispatchUIFrameCallbackEnqueued = true;
        ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI,
                mDispatchUIFrameCallback);
    }

    /* package */ void pauseFrameCallback() {
        mIsDispatchUIFrameCallbackEnqueued = false;
        ReactChoreographer.getInstance().removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI,
                mDispatchUIFrameCallback);
        flushPendingBatches();
    }

    private void flushPendingBatches() {
        if (mIsInIllegalUIState) {
            FLog.w(ReactConstants.TAG, "Not flushing pending UI operations because of previously thrown Exception");
            return;
        }

        final ArrayList<Runnable> runnables;
        synchronized (mDispatchRunnablesLock) {
            if (!mDispatchUIRunnables.isEmpty()) {
                runnables = mDispatchUIRunnables;
                mDispatchUIRunnables = new ArrayList<>();
            } else {
                return;
            }
        }

        final long batchedExecutionStartTime = SystemClock.uptimeMillis();
        for (Runnable runnable : runnables) {
            runnable.run();
        }

        if (mIsProfilingNextBatch) {
            mProfiledBatchBatchedExecutionTime = SystemClock.uptimeMillis() - batchedExecutionStartTime;
            mProfiledBatchNonBatchedExecutionTime = mNonBatchedExecutionTotalTime;
            mIsProfilingNextBatch = false;

            Systrace.beginAsyncSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "batchedExecutionTime", 0,
                    batchedExecutionStartTime * 1000000);
            Systrace.endAsyncSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "batchedExecutionTime", 0);
        }
        mNonBatchedExecutionTotalTime = 0;
    }

    /**
     * Choreographer FrameCallback responsible for actually dispatching view updates on the UI thread
     * that were enqueued via {@link #dispatchViewUpdates(int)}. The reason we don't just enqueue
     * directly to the UI thread from that method is to make sure our Runnables actually run before
     * the next traversals happen:
     *
     * <p>ViewRootImpl#scheduleTraversals (which is called from invalidate, requestLayout, etc) calls
     * Looper#postSyncBarrier which keeps any UI thread looper messages from being processed until
     * that barrier is removed during the next traversal. That means, depending on when we get updates
     * from JS and what else is happening on the UI thread, we can sometimes try to post this runnable
     * after ViewRootImpl has posted a barrier.
     *
     * <p>Using a Choreographer callback (which runs immediately before traversals), we guarantee we
     * run before the next traversal.
     */
    private class DispatchUIFrameCallback extends GuardedFrameCallback {

        private static final int FRAME_TIME_MS = 16;
        private final int mMinTimeLeftInFrameForNonBatchedOperationMs;

        private DispatchUIFrameCallback(ReactContext reactContext, int minTimeLeftInFrameForNonBatchedOperationMs) {
            super(reactContext);
            mMinTimeLeftInFrameForNonBatchedOperationMs = minTimeLeftInFrameForNonBatchedOperationMs;
        }

        @Override
        public void doFrameGuarded(long frameTimeNanos) {
            if (mIsInIllegalUIState) {
                FLog.w(ReactConstants.TAG,
                        "Not flushing pending UI operations because of previously thrown Exception");
                return;
            }

            Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "dispatchNonBatchedUIOperations");
            try {
                dispatchPendingNonBatchedOperations(frameTimeNanos);
            } finally {
                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
            }

            flushPendingBatches();

            ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, this);
        }

        private void dispatchPendingNonBatchedOperations(long frameTimeNanos) {
            while (true) {
                long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000);
                if (timeLeftInFrame < mMinTimeLeftInFrameForNonBatchedOperationMs) {
                    break;
                }

                UIOperation nextOperation;
                synchronized (mNonBatchedOperationsLock) {
                    if (mNonBatchedOperations.isEmpty()) {
                        break;
                    }

                    nextOperation = mNonBatchedOperations.pollFirst();
                }

                try {
                    long nonBatchedExecutionStartTime = SystemClock.uptimeMillis();
                    nextOperation.execute();
                    mNonBatchedExecutionTotalTime += SystemClock.uptimeMillis() - nonBatchedExecutionStartTime;
                } catch (Exception e) {
                    mIsInIllegalUIState = true;
                    throw e;
                }
            }
        }
    }
}