Only retry ViewCommand mount items if exception is marked as "Retryable"
Summary: Instead of just blindly retrying all ViewCommands if they fail - which could be dangerous, since it's arbitrary imperative commands we'd be executing twice, potentially with bad app state - we only retry if the ViewCommand throws a "RetryableMountingLayerException". Changelog: [Internal] Optimization to ViewCommands Reviewed By: mdvacca Differential Revision: D20529985 fbshipit-source-id: 0217b43f4bf92442bcc7ca48c8ae2b9a9e543dc9
This commit is contained in:
Родитель
7561adac77
Коммит
0fe548aa2a
|
@ -13,11 +13,15 @@ package com.facebook.react.bridge;
|
|||
* and not crash, no matter what.
|
||||
*/
|
||||
public class ReactNoCrashSoftException extends RuntimeException {
|
||||
public ReactNoCrashSoftException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
public ReactNoCrashSoftException(String m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
public ReactNoCrashSoftException(String detailMessage, Throwable ex) {
|
||||
super(detailMessage, ex);
|
||||
public ReactNoCrashSoftException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public ReactNoCrashSoftException(String m, Throwable e) {
|
||||
super(m, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* ViewCommands can throw this Exception. If this is caught during the execution of a ViewCommand
|
||||
* mounting instruction, it indicates that the mount item can be safely retried.
|
||||
*/
|
||||
public class RetryableMountingLayerException extends RuntimeException {
|
||||
public RetryableMountingLayerException(String msg, Throwable e) {
|
||||
super(msg, e);
|
||||
}
|
||||
|
||||
public RetryableMountingLayerException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public RetryableMountingLayerException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
|
@ -762,7 +763,7 @@ public class NativeViewHierarchyManager {
|
|||
UiThreadUtil.assertOnUiThread();
|
||||
View view = mTagsToViews.get(reactTag);
|
||||
if (view == null) {
|
||||
throw new IllegalViewOperationException(
|
||||
throw new RetryableMountingLayerException(
|
||||
"Trying to send command to a non-existing view with tag ["
|
||||
+ reactTag
|
||||
+ "] and command "
|
||||
|
@ -777,7 +778,7 @@ public class NativeViewHierarchyManager {
|
|||
UiThreadUtil.assertOnUiThread();
|
||||
View view = mTagsToViews.get(reactTag);
|
||||
if (view == null) {
|
||||
throw new IllegalViewOperationException(
|
||||
throw new RetryableMountingLayerException(
|
||||
"Trying to send command to a non-existing view with tag ["
|
||||
+ reactTag
|
||||
+ "] and command "
|
||||
|
|
|
@ -17,9 +17,11 @@ 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.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftException;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
|
@ -878,27 +880,26 @@ public class UIViewOperationQueue {
|
|||
for (DispatchCommandViewOperation op : viewCommandOperations) {
|
||||
try {
|
||||
op.executeWithExceptions();
|
||||
} catch (Throwable e) {
|
||||
} catch (RetryableMountingLayerException e) {
|
||||
// Catch errors in DispatchCommands. We allow all commands to be retried
|
||||
// exactly
|
||||
// once, after the current batch of other mountitems. If the second attempt
|
||||
// fails,
|
||||
// then we log a soft error. This will still crash only in debug.
|
||||
// We do this because it is a ~relatively common pattern to dispatch a command
|
||||
// during render, for example, to scroll to the bottom of a ScrollView in
|
||||
// render.
|
||||
// This dispatches the command before that View is even mounted. By retrying
|
||||
// once,
|
||||
// we can still dispatch the vast majority of commands faster, avoid errors,
|
||||
// and
|
||||
// still operate correctly for most commands even when they're executed too
|
||||
// soon.
|
||||
// exactly once, after the current batch of other mountitems. If the second
|
||||
// attempt fails, then we log a soft error. This will still crash only in
|
||||
// debug. We do this because it is a ~relatively common pattern to dispatch a
|
||||
// command during render, for example, to scroll to the bottom of a ScrollView
|
||||
// in render. This dispatches the command before that View is even mounted. By
|
||||
// retrying once, we can still dispatch the vast majority of commands faster,
|
||||
// avoid errors, and still operate correctly for most commands even when
|
||||
// they're executed too soon.
|
||||
if (op.getRetries() == 0) {
|
||||
op.incrementRetries();
|
||||
mViewCommandOperations.add(op);
|
||||
} else {
|
||||
ReactSoftException.logSoftException(TAG, e);
|
||||
// Retryable exceptions should be logged, but never crash in debug.
|
||||
ReactSoftException.logSoftException(TAG, new ReactNoCrashSoftException(e));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Non-retryable exceptions should be logged in prod, and crash in Debug.
|
||||
ReactSoftException.logSoftException(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,11 @@ package com.facebook.react.views.scroll;
|
|||
|
||||
import android.graphics.Color;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
|
@ -272,8 +274,13 @@ public class ReactScrollViewManager extends ViewGroupManager<ReactScrollView>
|
|||
@Override
|
||||
public void scrollToEnd(
|
||||
ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
|
||||
View child = scrollView.getChildAt(0);
|
||||
if (child == null) {
|
||||
throw new RetryableMountingLayerException("scrollToEnd called on ScrollView without child");
|
||||
}
|
||||
|
||||
// ScrollView always has one child - the scrollable area
|
||||
int bottom = scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom();
|
||||
int bottom = child.getHeight() + scrollView.getPaddingBottom();
|
||||
if (data.mAnimated) {
|
||||
scrollView.reactSmoothScrollTo(scrollView.getScrollX(), bottom);
|
||||
} else {
|
||||
|
|
Загрузка…
Ссылка в новой задаче