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.
|
* and not crash, no matter what.
|
||||||
*/
|
*/
|
||||||
public class ReactNoCrashSoftException extends RuntimeException {
|
public class ReactNoCrashSoftException extends RuntimeException {
|
||||||
public ReactNoCrashSoftException(String detailMessage) {
|
public ReactNoCrashSoftException(String m) {
|
||||||
super(detailMessage);
|
super(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactNoCrashSoftException(String detailMessage, Throwable ex) {
|
public ReactNoCrashSoftException(Throwable e) {
|
||||||
super(detailMessage, ex);
|
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.ReactContext;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||||
import com.facebook.react.bridge.SoftAssertions;
|
import com.facebook.react.bridge.SoftAssertions;
|
||||||
import com.facebook.react.bridge.UiThreadUtil;
|
import com.facebook.react.bridge.UiThreadUtil;
|
||||||
import com.facebook.react.config.ReactFeatureFlags;
|
import com.facebook.react.config.ReactFeatureFlags;
|
||||||
|
@ -762,7 +763,7 @@ public class NativeViewHierarchyManager {
|
||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
View view = mTagsToViews.get(reactTag);
|
View view = mTagsToViews.get(reactTag);
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
throw new IllegalViewOperationException(
|
throw new RetryableMountingLayerException(
|
||||||
"Trying to send command to a non-existing view with tag ["
|
"Trying to send command to a non-existing view with tag ["
|
||||||
+ reactTag
|
+ reactTag
|
||||||
+ "] and command "
|
+ "] and command "
|
||||||
|
@ -777,7 +778,7 @@ public class NativeViewHierarchyManager {
|
||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
View view = mTagsToViews.get(reactTag);
|
View view = mTagsToViews.get(reactTag);
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
throw new IllegalViewOperationException(
|
throw new RetryableMountingLayerException(
|
||||||
"Trying to send command to a non-existing view with tag ["
|
"Trying to send command to a non-existing view with tag ["
|
||||||
+ reactTag
|
+ reactTag
|
||||||
+ "] and command "
|
+ "] and command "
|
||||||
|
|
|
@ -17,9 +17,11 @@ import com.facebook.react.bridge.Callback;
|
||||||
import com.facebook.react.bridge.GuardedRunnable;
|
import com.facebook.react.bridge.GuardedRunnable;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.bridge.ReactContext;
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||||
import com.facebook.react.bridge.ReactSoftException;
|
import com.facebook.react.bridge.ReactSoftException;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||||
import com.facebook.react.bridge.SoftAssertions;
|
import com.facebook.react.bridge.SoftAssertions;
|
||||||
import com.facebook.react.bridge.UiThreadUtil;
|
import com.facebook.react.bridge.UiThreadUtil;
|
||||||
import com.facebook.react.common.ReactConstants;
|
import com.facebook.react.common.ReactConstants;
|
||||||
|
@ -878,27 +880,26 @@ public class UIViewOperationQueue {
|
||||||
for (DispatchCommandViewOperation op : viewCommandOperations) {
|
for (DispatchCommandViewOperation op : viewCommandOperations) {
|
||||||
try {
|
try {
|
||||||
op.executeWithExceptions();
|
op.executeWithExceptions();
|
||||||
} catch (Throwable e) {
|
} catch (RetryableMountingLayerException e) {
|
||||||
// Catch errors in DispatchCommands. We allow all commands to be retried
|
// Catch errors in DispatchCommands. We allow all commands to be retried
|
||||||
// exactly
|
// exactly once, after the current batch of other mountitems. If the second
|
||||||
// once, after the current batch of other mountitems. If the second attempt
|
// attempt fails, then we log a soft error. This will still crash only in
|
||||||
// fails,
|
// debug. We do this because it is a ~relatively common pattern to dispatch a
|
||||||
// then we log a soft error. This will still crash only in debug.
|
// command during render, for example, to scroll to the bottom of a ScrollView
|
||||||
// We do this because it is a ~relatively common pattern to dispatch a command
|
// in render. This dispatches the command before that View is even mounted. By
|
||||||
// during render, for example, to scroll to the bottom of a ScrollView in
|
// retrying once, we can still dispatch the vast majority of commands faster,
|
||||||
// render.
|
// avoid errors, and still operate correctly for most commands even when
|
||||||
// This dispatches the command before that View is even mounted. By retrying
|
// they're executed too soon.
|
||||||
// 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) {
|
if (op.getRetries() == 0) {
|
||||||
op.incrementRetries();
|
op.incrementRetries();
|
||||||
mViewCommandOperations.add(op);
|
mViewCommandOperations.add(op);
|
||||||
} else {
|
} 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.graphics.Color;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.View;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||||
import com.facebook.react.common.MapBuilder;
|
import com.facebook.react.common.MapBuilder;
|
||||||
import com.facebook.react.module.annotations.ReactModule;
|
import com.facebook.react.module.annotations.ReactModule;
|
||||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||||
|
@ -272,8 +274,13 @@ public class ReactScrollViewManager extends ViewGroupManager<ReactScrollView>
|
||||||
@Override
|
@Override
|
||||||
public void scrollToEnd(
|
public void scrollToEnd(
|
||||||
ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
|
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
|
// 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) {
|
if (data.mAnimated) {
|
||||||
scrollView.reactSmoothScrollTo(scrollView.getScrollX(), bottom);
|
scrollView.reactSmoothScrollTo(scrollView.getScrollX(), bottom);
|
||||||
} else {
|
} else {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче