Reset leaked StaticLayout bounds-for-width flags on Android 15 (gated)
Summary:
Problem
45 of 47 FB4A Flytrap reports in https://fburl.com/scuba/flytrap_facebook_canonical/3sm0dpk4 describe the same bug: after tapping “See more” on a long post, the text slides off the right edge and only a letter or two per line stays visible. In a sample of
incorrect_uireports, 68 of 300 on 582 / Android 15 are this bug. There were 0 of 300 on 582 / Android 16 and about 0 of 200 on 581 / Android 15. It still happens on 583. Almost all the posts are Hebrew, Arabic, or Urdu with several paragraphs.Root cause, confirmed in a live app process on an Android 15 emulator with live Kotlin eval:
StaticLayout.Builder.obtain()takes builders from a static pool of 3. On Android 15 it does not resetuseBoundsForWidth(orshiftDrawingOffsetForStartOverhang) when it hands one out. ATextViewin an app targeting SDK 35+ builds withuseBoundsForWidth = true, so the next pooled builder still has it. A builder freshly obtained in the bad process readmUseBoundsForWidth = true.TextLayoutBuilder(the layout engine behind LithoText, and so behind feed post text) never sets these flags. Its layout inherits whatever the pooled builder had.- With
useBoundsForWidth = true, a multi-paragraph RTL text with empty lines getsmDrawingBounds.left = -652.8at width 672.Layout.draw()then shifts the canvas by-left, about 653 px to the right. The Litho layout tree, theTextDrawablebounds, and the translation are all correct; only the framework draw is offset.- It is intermittent because it depends on which pooled builder the layout gets and whether a
TextViewused it last. That matches “doesn’t happen all the time” in the reports. A freshStaticLayoutbuilt from the same text with a fresh paint draws offset in the bad process. The same text with either flag set to false draws correctly.BEFORE (Android 15, gate off) TextView (target >= 35) --build(useBoundsForWidth=true)--> [Builder pool x3] (obtain() does not reset it) | Litho Text -> TextLayoutBuilder -> StaticLayoutHelper.make -> obtain() -> builder still has useBoundsForWidth=true | multi-paragraph RTL text -> drawingBounds.left = -653 -> Layout.draw() translates +653 px | screen: | ...ט| <- only the line ends are visible | ...ה| AFTER (gate on) StaticLayoutHelper.make on API 35+: builder.setUseBoundsForWidth(false).setShiftDrawingOffsetForStartOverhang(false) | drawingBounds unused -> text drawn inside the content box (x = 24..696) screen: |בדיקה שלישית עם הקונפיגורציה של המדווח, פוסט|Diagram (Excalidraw): https://pxl.cl/gr3vG
Fix
StaticLayoutHelper.make()now sets both flags to false explicitly on API 35+. False is the valueTextLayoutBuilderhas always assumed, so this only undoes the leak. The switch is a new process-wideTextLayoutBuilderConfiguration.resetStaticLayoutBoundsForWidth(default false, so other apps that use the library are unchanged). FB4A sets it at startup from MobileConfig inFbLithoStartupConfiguration, next to the other Litho text flags.The fix is in
TextLayoutBuilder, not in feed or FDS code, because the corruption reaches every LithoTextand primitive text that measures throughTextLayoutBuilder. Feed post text is where it shows most, because long multi-paragraph RTL posts are what produce the large negative drawing bounds.Knobs
Knob Type / scope Default What it does Recommended litho_core.reset_static_layout_bounds_for_width(key 158)bool, ADMIN_ID, read once at app startup false When true, every StaticLayoutthatTextLayoutBuilderbuilds on API 35+ hasuseBoundsForWidthandshiftDrawingOffsetForStartOverhangset to false, so the flags leaked by Android 15’s builder pool cannot reach Litho text. When false, behavior is unchanged.true. Ramp with an A/B on FB4A and watch the Android 15 “See more” text reports in Flytrap plus text-heavy perf metrics. Expect a no-op on Android 14 and below (the code path is API 35+ only). On Android 16+ it should also be a no-op, assuming the framework already resets the flags there. MC actualization: https://fburl.com/metaconfig/he3ftgyv
Not in this diff
BkBloksTextSpanStyleResizeToFitStyleProvidercallsStaticLayout.Builder.obtaindirectly and can inherit the same flags. It is outsideTextLayoutBuilder; follow-up if Bloks text shows the same symptom.- The pool not resetting these flags is a framework bug. The 0-of-300 result on Android 16 suggests it is fixed there; not confirmed on a device.
Differential Revision: D126788375
fbshipit-source-id: 07051d9061163f8caf12a6db792ee8bbaec2ee35
版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9
京公网安备 11010802047560号
TextLayoutBuilder
Build text Layouts easily on Android.
Features
Layouts easily.Layouts.Layouts of commonly used strings.Download
If using Gradle, add this to your
build.gradle:or, if using Maven:
Usage
Set the properties on the
TextLayoutBuilder:Call
build()on the builder to get aLayout:Use the
Layoutin your code:Additional Usage
Cache the layouts for commonly used strings by turning on caching in the
TextLayoutBuilder.Glyph warming provides significant performance boost for large blurbs of text. Turn this on and pass in a
GlyphWarmerfor theTextLayoutBuilder.Import a style defined in XML into a
TextLayoutBuilderobject.License
TextLayoutBuilder is Apache-2-licensed.