mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add trace markers to ReactTextView (#46214)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46214 This tries to represent a few operations which have previously been observed to be costly in a sampling profiler (showing more granularity than the trace events): 1. TextView getting measurements via `onMeasure()` when updating layout metrics during mount, which may [trigger UI-thread text layout](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/TextView.java;l=11217). 2. Text drawing, which may do layout as well 3. State updates, where we construct a new Spannable and set content to it Changelog: [Internal] Reviewed By: tdn120, mdvacca Differential Revision: D61705770 fbshipit-source-id: 199a6c65c18296f2ff948642701a331ba656e9d9
This commit is contained in:
committed by
Facebook GitHub Bot
parent
b86e8ef95f
commit
1ffe74a5dc
@@ -7663,6 +7663,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi
|
||||
protected fun onDraw (Landroid/graphics/Canvas;)V
|
||||
public fun onFinishTemporaryDetach ()V
|
||||
protected fun onLayout (ZIIII)V
|
||||
protected fun onMeasure (II)V
|
||||
public fun onStartTemporaryDetach ()V
|
||||
public fun reactTagForTouch (FF)I
|
||||
public fun setAdjustFontSizeToFit (Z)V
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and 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.internal
|
||||
|
||||
import com.facebook.systrace.Systrace
|
||||
|
||||
/**
|
||||
* Helper to guarantee firing Systrace begin and end markers around a try with resources statement.
|
||||
*/
|
||||
public class SystraceSection(sectionName: String) : AutoCloseable {
|
||||
init {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, sectionName)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE)
|
||||
}
|
||||
}
|
||||
+84
-72
@@ -35,6 +35,7 @@ import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.internal.SystraceSection;
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
|
||||
import com.facebook.react.uimanager.BackgroundStyleApplicator;
|
||||
import com.facebook.react.uimanager.LengthPercentage;
|
||||
@@ -375,85 +376,96 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mAdjustsFontSizeToFit && getSpanned() != null && mShouldAdjustSpannableFontSize) {
|
||||
mShouldAdjustSpannableFontSize = false;
|
||||
TextLayoutManager.adjustSpannableFontToFit(
|
||||
getSpanned(),
|
||||
getWidth(),
|
||||
YogaMeasureMode.EXACTLY,
|
||||
getHeight(),
|
||||
YogaMeasureMode.EXACTLY,
|
||||
mMinimumFontSize,
|
||||
mNumberOfLines,
|
||||
getIncludeFontPadding(),
|
||||
getBreakStrategy(),
|
||||
getHyphenationFrequency(),
|
||||
// always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on
|
||||
// how exacly lines are aligned, just their width
|
||||
Layout.Alignment.ALIGN_NORMAL);
|
||||
setText(getSpanned());
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
|
||||
if (mOverflow != Overflow.VISIBLE) {
|
||||
BackgroundStyleApplicator.clipToPaddingBox(this, canvas);
|
||||
try (SystraceSection s = new SystraceSection("ReactTextView.onDraw")) {
|
||||
if (mAdjustsFontSizeToFit && getSpanned() != null && mShouldAdjustSpannableFontSize) {
|
||||
mShouldAdjustSpannableFontSize = false;
|
||||
TextLayoutManager.adjustSpannableFontToFit(
|
||||
getSpanned(),
|
||||
getWidth(),
|
||||
YogaMeasureMode.EXACTLY,
|
||||
getHeight(),
|
||||
YogaMeasureMode.EXACTLY,
|
||||
mMinimumFontSize,
|
||||
mNumberOfLines,
|
||||
getIncludeFontPadding(),
|
||||
getBreakStrategy(),
|
||||
getHyphenationFrequency(),
|
||||
// always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on
|
||||
// how exacly lines are aligned, just their width
|
||||
Layout.Alignment.ALIGN_NORMAL);
|
||||
setText(getSpanned());
|
||||
}
|
||||
} else {
|
||||
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
|
||||
}
|
||||
|
||||
super.onDraw(canvas);
|
||||
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
|
||||
if (mOverflow != Overflow.VISIBLE) {
|
||||
BackgroundStyleApplicator.clipToPaddingBox(this, canvas);
|
||||
}
|
||||
} else {
|
||||
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
|
||||
}
|
||||
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
try (SystraceSection s = new SystraceSection("ReactTextView.onMeasure")) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
public void setText(ReactTextUpdate update) {
|
||||
mContainsImages = update.containsImages();
|
||||
// Android's TextView crashes when it tries to relayout if LayoutParams are
|
||||
// null; explicitly set the LayoutParams to prevent this crash. See:
|
||||
// https://github.com/facebook/react-native/pull/7011
|
||||
if (getLayoutParams() == null) {
|
||||
setLayoutParams(EMPTY_LAYOUT_PARAMS);
|
||||
}
|
||||
Spannable spannable = update.getText();
|
||||
if (mLinkifyMaskType > 0) {
|
||||
Linkify.addLinks(spannable, mLinkifyMaskType);
|
||||
setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
setText(spannable);
|
||||
float paddingLeft = update.getPaddingLeft();
|
||||
float paddingTop = update.getPaddingTop();
|
||||
float paddingRight = update.getPaddingRight();
|
||||
float paddingBottom = update.getPaddingBottom();
|
||||
|
||||
// In Fabric padding is set by the update of Layout Metrics and not as part of the "setText"
|
||||
// operation
|
||||
// TODO T56559197: remove this condition when we migrate 100% to Fabric
|
||||
if (paddingLeft != ReactConstants.UNSET
|
||||
&& paddingTop != ReactConstants.UNSET
|
||||
&& paddingRight != ReactConstants.UNSET
|
||||
&& paddingBottom != ReactConstants.UNSET) {
|
||||
|
||||
setPadding(
|
||||
(int) Math.floor(paddingLeft),
|
||||
(int) Math.floor(paddingTop),
|
||||
(int) Math.floor(paddingRight),
|
||||
(int) Math.floor(paddingBottom));
|
||||
}
|
||||
|
||||
int nextTextAlign = update.getTextAlign();
|
||||
if (nextTextAlign != getGravityHorizontal()) {
|
||||
setGravityHorizontal(nextTextAlign);
|
||||
}
|
||||
if (getBreakStrategy() != update.getTextBreakStrategy()) {
|
||||
setBreakStrategy(update.getTextBreakStrategy());
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (getJustificationMode() != update.getJustificationMode()) {
|
||||
setJustificationMode(update.getJustificationMode());
|
||||
try (SystraceSection s = new SystraceSection("ReactTextView.setText(ReactTextUpdate)")) {
|
||||
mContainsImages = update.containsImages();
|
||||
// Android's TextView crashes when it tries to relayout if LayoutParams are
|
||||
// null; explicitly set the LayoutParams to prevent this crash. See:
|
||||
// https://github.com/facebook/react-native/pull/7011
|
||||
if (getLayoutParams() == null) {
|
||||
setLayoutParams(EMPTY_LAYOUT_PARAMS);
|
||||
}
|
||||
}
|
||||
Spannable spannable = update.getText();
|
||||
if (mLinkifyMaskType > 0) {
|
||||
Linkify.addLinks(spannable, mLinkifyMaskType);
|
||||
setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
setText(spannable);
|
||||
float paddingLeft = update.getPaddingLeft();
|
||||
float paddingTop = update.getPaddingTop();
|
||||
float paddingRight = update.getPaddingRight();
|
||||
float paddingBottom = update.getPaddingBottom();
|
||||
|
||||
// Ensure onLayout is called so the inline views can be repositioned.
|
||||
requestLayout();
|
||||
// In Fabric padding is set by the update of Layout Metrics and not as part of the "setText"
|
||||
// operation
|
||||
// TODO T56559197: remove this condition when we migrate 100% to Fabric
|
||||
if (paddingLeft != ReactConstants.UNSET
|
||||
&& paddingTop != ReactConstants.UNSET
|
||||
&& paddingRight != ReactConstants.UNSET
|
||||
&& paddingBottom != ReactConstants.UNSET) {
|
||||
|
||||
setPadding(
|
||||
(int) Math.floor(paddingLeft),
|
||||
(int) Math.floor(paddingTop),
|
||||
(int) Math.floor(paddingRight),
|
||||
(int) Math.floor(paddingBottom));
|
||||
}
|
||||
|
||||
int nextTextAlign = update.getTextAlign();
|
||||
if (nextTextAlign != getGravityHorizontal()) {
|
||||
setGravityHorizontal(nextTextAlign);
|
||||
}
|
||||
if (getBreakStrategy() != update.getTextBreakStrategy()) {
|
||||
setBreakStrategy(update.getTextBreakStrategy());
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (getJustificationMode() != update.getJustificationMode()) {
|
||||
setJustificationMode(update.getJustificationMode());
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure onLayout is called so the inline views can be repositioned.
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+26
-21
@@ -17,6 +17,7 @@ import com.facebook.react.R;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.common.mapbuffer.MapBuffer;
|
||||
import com.facebook.react.internal.SystraceSection;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.IViewManagerWithChildren;
|
||||
import com.facebook.react.uimanager.ReactAccessibilityDelegate;
|
||||
@@ -86,24 +87,26 @@ public class ReactTextViewManager
|
||||
|
||||
@Override
|
||||
public void updateExtraData(ReactTextView view, Object extraData) {
|
||||
ReactTextUpdate update = (ReactTextUpdate) extraData;
|
||||
Spannable spannable = update.getText();
|
||||
if (update.containsImages()) {
|
||||
TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view);
|
||||
}
|
||||
view.setText(update);
|
||||
try (SystraceSection s = new SystraceSection("ReactTextViewManager.updateExtraData")) {
|
||||
ReactTextUpdate update = (ReactTextUpdate) extraData;
|
||||
Spannable spannable = update.getText();
|
||||
if (update.containsImages()) {
|
||||
TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view);
|
||||
}
|
||||
view.setText(update);
|
||||
|
||||
// If this text view contains any clickable spans, set a view tag and reset the accessibility
|
||||
// delegate so that these can be picked up by the accessibility system.
|
||||
ReactClickableSpan[] clickableSpans =
|
||||
spannable.getSpans(0, update.getText().length(), ReactClickableSpan.class);
|
||||
// If this text view contains any clickable spans, set a view tag and reset the accessibility
|
||||
// delegate so that these can be picked up by the accessibility system.
|
||||
ReactClickableSpan[] clickableSpans =
|
||||
spannable.getSpans(0, update.getText().length(), ReactClickableSpan.class);
|
||||
|
||||
if (clickableSpans.length > 0) {
|
||||
view.setTag(
|
||||
R.id.accessibility_links,
|
||||
new ReactAccessibilityDelegate.AccessibilityLinks(clickableSpans, spannable));
|
||||
ReactAccessibilityDelegate.resetDelegate(
|
||||
view, view.isFocusable(), view.getImportantForAccessibility());
|
||||
if (clickableSpans.length > 0) {
|
||||
view.setTag(
|
||||
R.id.accessibility_links,
|
||||
new ReactAccessibilityDelegate.AccessibilityLinks(clickableSpans, spannable));
|
||||
ReactAccessibilityDelegate.resetDelegate(
|
||||
view, view.isFocusable(), view.getImportantForAccessibility());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,11 +138,13 @@ public class ReactTextViewManager
|
||||
@Override
|
||||
public Object updateState(
|
||||
ReactTextView view, ReactStylesDiffMap props, StateWrapper stateWrapper) {
|
||||
MapBuffer stateMapBuffer = stateWrapper.getStateDataMapBuffer();
|
||||
if (stateMapBuffer != null) {
|
||||
return getReactTextUpdate(view, props, stateMapBuffer);
|
||||
} else {
|
||||
return null;
|
||||
try (SystraceSection s = new SystraceSection("ReactTextViewManager.updateState")) {
|
||||
MapBuffer stateMapBuffer = stateWrapper.getStateDataMapBuffer();
|
||||
if (stateMapBuffer != null) {
|
||||
return getReactTextUpdate(view, props, stateMapBuffer);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user