Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[DroidKaigi 2019] The best practice for Android Text

3146dbf1eb68c920101347ce4f0fb4e7?s=47 nonanona
February 08, 2019

[DroidKaigi 2019] The best practice for Android Text

3146dbf1eb68c920101347ce4f0fb4e7?s=128

nonanona

February 08, 2019
Tweet

Transcript

  1. Android Text The performance and features Seigo Nonaka Android Text

    @ Google @ttuusskk
  2. Text Performance Best practice for text on Android Google I/O

    2018 by Clara, Florina, Siyamed.
  3. Wait… Is Text Slow?

  4. Wait… Is Text Slow? It’s just a text. No, Kidding.

    If text causes jank, everything is janky. Yes, but only for the long text.
  5. Well… it depends.

  6. Well… it depends. Text may be a heavy component.

  7. Today’s Goal You can write faster apps if you 1.

    know the what TextView is doing 2. know the tools 3. know the best practice
  8. Identify the bottleneck

  9. Sample App RecyceclerView with • Holds TextViews • Random strings

    and styles
  10. Sample App recyclerView.adapter = object: RecyclerView.Adapter<Holder>() { override fun onCreateViewHolder(parent:

    ViewGroup, viewType: Int): Holder { return Holder( TextView(this@Activity) ) } override fun onBindViewHolder(holder: Holder, position: Int) { holder.view.text = sampleText[position] } }
  11. UI Performance Basics Android Performance: UI Google I/O 2017 by

    Chet, Chris
  12. Takeaways from UI performance talk • Finish within 1 frame

    = 16 ms on UI thread • Analyze performance with systrace
  13. Let’s see our app!

  14. systrace $ systrace.py -a <package name>

  15. systrace

  16. Systrace TextView.onMeasure()

  17. Is TextView slower than other Views?

  18. Another Sample App RecyceclerView with • Little more complex layout

  19. Microbenchmark <ImageView> <TextView> <RelativeLayout> <LinearLayout>

  20. Root.onMeasure 20,028 μsec

  21. TextView.onMeasure 19,508 μsec (97.4%) 2.6% ImageView + RelativeLayout + LinearLayout

    Root.onMeasure 20,028 μsec
  22. What is View.onMeasure()?

  23. View.onMeasure “Measure the view and its content to determine the

    measured width and the measured height. “ https://developer.android.com/reference/android/view/View.html#onMeasure(int,%20int)
  24. ImageView.onMeasure

  25. ImageView.onMeasure onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})

  26. onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480}) ImageView.onMeasure 120px 160px

  27. ImageView.onMeasure 10px 10px 10px 10px onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})

  28. ImageView.onMeasure setMeasuredDimension(140, 180) 140px 180px onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})

  29. TextView.onMeasure()

  30. TextView.onMeasure onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})

  31. TextView.onMeasure val text = "Hello, World!" onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})

  32. TextView.onMeasure val paint = textView.paint (drawing configuration) val text =

    "Hello, World!" onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})
  33. TextView.onMeasure val paint = textView.paint (drawing configuration) val text =

    "Hello, World!" Blackbox onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})
  34. TextView.onMeasure Hello, World Blackbox 240px 24px setMeasuredDimension(240, 24) val paint

    = textView.paint val text = "Hello, World!" onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})
  35. TextView.onMeasure Blackbox onMeasure(width={AT_MOST, 640}, height={AT_MOST, 480})

  36. Open the black box

  37. Systrace again BoringLayout.isBoring() StaticLayout.Builder.build() TextView.onMeasure() BoringLayout.isBoring() StaticLayout.Builder.build()

  38. Layout Build the StaticLayout after options have been set. https://developer.android.com/reference/android/text/StaticLayout.Builder.html

    https://developer.android.com/reference/android/text/BoringLayout.html StaticLayout.Builder.build() Returns null if not boring; the width, ascent, and descent if boring. BoringLayout.isBoring()
  39. Layout Perform line breaking to put longer text in a

    box. StaticLayout.Builder.build() Check if the text fits in a single line. BoringLayout.isBoring()
  40. Layout Perform line breaking to put longer text in a

    box. StaticLayout.Builder.build() Check if the text fits in a single line. BoringLayout.isBoring() Hmm…. still not clear. We need to go deeper.
  41. Text rendering stack TextView Minikin Layout ICU (Unicode Library) HarfBuzz

    (Text Shaping Library) FreeType (Text Drawing Library) Skia (Graphics Library) Paint Canvas Java Native from Google I/O 2018 Best practices for text on Android (Google I/O '18) Trace point
  42. Systrace again and again TextView.onMeasure() BoringLayout.isBoring() StaticLayout.Builder.build() doLayout in Minikin

    The primitive method of computing the layout of the given text.
  43. Systrace again and again Why so many calls? TextView.onMeasure() BoringLayout.isBoring()

    StaticLayout.Builder.build() doLayout in Minikin The primitive method of computing the layout of the given text.
  44. Text Layout

  45. Understanding Text Layout This is an example text. INPUT :

    Serif Corsiva OUTPUT: This is an example text. 240px
  46. This is an example text. ␣ ␣ ␣ ␣ Returns

    null if not boring; the width, ascent, and descent if boring. BoringLayout.isBoring()
  47. This is an example text. ␣ ␣ ␣ ␣ doLayout("This

    is an ", "serif"); This is an 120px Returns null if not boring; the width, ascent, and descent if boring. BoringLayout.isBoring()
  48. This is an example text. ␣ ␣ ␣ ␣ This

    is an 120px example text. 120px Returns null if not boring; the width, ascent, and descent if boring. BoringLayout.isBoring() doLayout("This is an ", "serif"); doLayout("example text", "Corsiva");
  49. This is an example text. This is an examp No

    problem. Go with BoringLayout! Text is too long. Need word wrapping!!! Returns null if not boring; the width, ascent, and descent if boring. BoringLayout.isBoring()
  50. Word Wrapping (Line Breaking) (Not the exact the same line

    breaking algorithm in Android)
  51. StaticLayout.Builder.build() This is an text. ␣ ␣ ␣ ␣ example

  52. This doLayout("This ", "serif"); This is an text. ␣ ␣

    ␣ ␣ StaticLayout.Builder.build() example
  53. This is doLayout("is ", "serif"); This is an text. ␣

    ␣ ␣ ␣ StaticLayout.Builder.build() example
  54. This is an doLayout("an ", "serif"); This is an text.

    ␣ ␣ ␣ ␣ StaticLayout.Builder.build() example
  55. This is an example doLayout("example ", "Corsiva"); This is an

    text. ␣ ␣ ␣ ␣ StaticLayout.Builder.build() example
  56. This is an ex- example ex- ample exam- ple doLayout("ex-",

    "Corsiva"); This is an text. ␣ ␣ ␣ ␣ StaticLayout.Builder.build() example
  57. This is an exam- This is an text. ␣ ␣

    ␣ ␣ doLayout("exam-", "Corsiva"); StaticLayout.Builder.build() example ex- ample exam- ple example
  58. This is an ex- ample This is an text. ␣

    ␣ ␣ ␣ doLayout("ample", "Corsiva"); StaticLayout.Builder.build() example ex- ample exam- ple example
  59. This is an ex- text. ample This is an text.

    ␣ ␣ ␣ ␣ doLayout("text.", "Corsiva"); StaticLayout.Builder.build() example
  60. example␣ This is an ex- text. ample This is an

    text. ␣ ␣ ␣ ␣ StaticLayout.Builder.build() This is an ex- text. ample example exam- This␣ is␣ an␣ text. example␣ For words: 5 times For Hyphenation: 3 times example
  61. This is an ex- text. ample This is an text.

    ␣ ␣ ␣ ␣ StaticLayout.Builder.build() This is an ex- text. ample example exam- This␣ is␣ an␣ example␣ text. example␣ For words: 5 times For Hyphenation: 3 times What happens if we disable hyphenation? example
  62. Turn OFF Hyphenation <style name="MyTextAppearance" parent="TextAppearance.AppCompat"> <item name="android:hyphenationFrequency">none</item> </style> textView.apply

    { hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE } <TextView android:hyphenationFrequency="none" />
  63. TextView.onMeasure() BoringLayout.isBoring() StaticLayout.Builder.build()

  64. TextView.onMeasure() BoringLayout.isBoring() StaticLayout.Builder.build() TextView.onMeasure() BoringLayout.isBoring() Turn OFF hyphenation

  65. TextView.onMeasure() BoringLayout.isBoring() StaticLayout.Builder.build() TextView.onMeasure() BoringLayout.isBoring() Turn OFF hyphenation Why doLayout

    calls have gone in line break?
  66. Layout Cache

  67. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣
  68. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Same Layout Same Layout
  69. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Let’s reuse the previous result!
  70. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Layout Cache Text Shaper .ttf .otf
  71. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache
  72. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache to
  73. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache to to
  74. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache to to
  75. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache be, to to
  76. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache be, to to be,
  77. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache or be, to to be, or
  78. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf Layout Cache not or be, to to be, not or
  79. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf to be, not or Layout Cache not or be to
  80. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf to be, not or to Layout Cache to not or be,
  81. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf to be, not or Layout Cache to not or be, to
  82. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf to be, not or to be, Layout Cache be, to not or
  83. Layout Cache to␣ be, or not to be, ␣ ␣

    ␣ ␣ Text Shaper .ttf .otf to be, not or to be, Layout Cache be, to not or
  84. How this removes doLayout() call from line breaking?

  85. Layout Cache (just after BoringLayout.isBoring) Text Shaper .ttf .otf Layout

    Cache text. example an is This This is an text. ␣ ␣ ␣ ␣ example This is an example text.
  86. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This
  87. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This
  88. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This This
  89. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This This
  90. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This This is
  91. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This This is
  92. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache text. example an is This This is an
  93. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an text. example an is This
  94. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an example text. example an is This
  95. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an text. example an is This ex-
  96. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an text. example an is This ex- ex-
  97. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an text. example an is This ex- exam-
  98. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an exam- text. example an is This ex- exam-
  99. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam-
  100. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam- ample
  101. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam- ample ample
  102. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam- ample ample
  103. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam- ample ample text.
  104. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam- ample ample text.
  105. Layout Cache (Line breaking) Text Shaper .ttf .otf Layout Cache

    text. example an is This This is an text. ␣ ␣ ␣ ␣ example Layout Cache This is an ex- text. example an is This ex- exam- ample ample text.
  106. Disable Hyphenation ALL for hyphenation!!! TextView.onMeasure() BoringLayout.isBoring() StaticLayout.Builder.build()

  107. Disable hyphenation unless it is really really important to you.

  108. Okay, I disabled hyphenation… Is that enough?

  109. TextView.onMeasure() BoringLayout.isBoring() Turn OFF Hyphenation 19ms is still slow.... But,

    all these layout are necessary
  110. Any idea of improvement…?

  111. Hmm...

  112. If we need to pay that cost, Can I do

    it beforehand?
  113. Idea: Use Background Thread

  114. Idea: Use Background Thread

  115. Idea: Use Background Thread

  116. Idea: Use Background Thread

  117. What can we do beforehand?

  118. Idea 1 Can I compute TextView.onMeasure() beforehand?

  119. Idea 1 Can I compute TextView.onMeasure() beforehand? Yes, but it’s

    hard in general. You have to know the measure specs: width, constraints.
  120. Idea 2 Can I warm up the layout cache beforehand?

  121. Idea 2 Can I warm up the layout cache beforehand?

    Yes, you can! This is actually threaded text layout!
  122. Threaded text layout with PrecomputedText(Compat)

  123. Framework vs AndroidX impl PrecomputedText - the framework impl •

    API 28+ • Keeps copy of layout result. PrecomputedTextCompat - the backported version in AndroidX • Effective on API 21+ • Just warms up the layout cache before API 28. • The cache may be purged when needed. (by default, LRU 5000 words limit)
  124. Use PrecomputedTextCompat On API 28+, use framework implementation. Don’t use

    framework impl and always use AndroidX impl. (Crash happens on some environment.) Between API 21 to 28, warm up layout cache. val computedText = PrecomputedTextCompat.create(text, tv.textMetricsParamsCompat)
  125. Utilities for threaded text layout val textFuture = PrecomputedTextCompat.getTextFuture(text, tv.textMetricsParamsCompat,

    null /* You can pass custom Executor */) // AppcompatTextView will call Future.get() to resolve the measurement when needed. tv.setTextFuture(textFuture)
  126. Integrate with RecyclerView

  127. RecyclerView prefetch UI Render

  128. RecyclerView prefetch UI Render

  129. RecyclerView prefetch UI Render

  130. RecyclerView prefetch UI Render

  131. RecyclerView Prefetch RecyclerView Prefetch by Chet https://medium.com/google-developers/recyclerview-prefetch-c2f269075710

  132. Threaded Text Layout with RecyclerView UI Render

  133. Threaded Text Layout with RecyclerView UI Render BG

  134. Threaded Text Layout with RecyclerView UI Render BG

  135. Integrate with RecyclerView recyclerView.adapter = object: RecyclerView.Adapter<Holder>() { override fun

    onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { return Holder( TextView(this@Activity) ) } override fun onBindViewHolder(holder: Holder, position: Int) { holder.view.text = sampleText[position] } }
  136. recyclerView.adapter = object: RecyclerView.Adapter<Holder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType:

    Int): Holder { return Holder( TextView(this@Activity) ) } override fun onBindViewHolder(holder: Holder, position: Int) { holder.view.text = sampleText[position] } } Integrate with RecyclerView
  137. recyclerView.adapter = object: RecyclerView.Adapter<Holder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType:

    Int): Holder { return Holder( AppCompatTextView(this@Activity) ) } override fun onBindViewHolder(holder: Holder, position: Int) { Holder.view.apply { setTextFuture(PrecomputedTextCompat.getTextFuture( sampleText[position], textMetricsParamsCompat, null)) } } } Integrate with RecyclerView
  138. Systrace result

  139. Systrace result PrecomputedText.create() New item comes

  140. Integrate with RecyclerView Prefetch Text Layout in RecyclerView by Chris

    https://medium.com/androiddevelopers/prefetch-text-layout-in-recyclerview-4ac f9103f438
  141. Done!

  142. Conclusion 0.00 5.00 10.00 15.00 25.00 20.00 22.33 Hyphenation On

    UI Thread Load milliseconds
  143. Conclusion 0.00 5.00 10.00 15.00 25.00 20.00 22.33 7.20 Hyphenation

    Off Hyphenation On UI Thread Load milliseconds
  144. Conclusion 0.00 5.00 10.00 15.00 25.00 20.00 22.33 7.20 1.17

    0.62 Hyphenation Off Hyphenation On Hyphenation Off Hyphenation On Precomputed UI Thread Load milliseconds
  145. Note • Always use PrecomputedTextCompat in AndroidX • There was

    a giant mutex lock during doing text layout. ◦ Removed from API 28 • Hyphenation depends on the language. ◦ No hyphenation for Japanese, i.e. no performance gain by turning off
  146. New and old features

  147. What’s new in fonts?

  148. Downloadable Fonts What’s New in Android Support Library by Clara

    Bayarri
  149. Typeface.Builder • Replacement for createFromAsset, createFromFile. • Variable Font Support

    (later) • Font Collection Support textView1.typeface = Typeface.Builder(assets, "NotoSansCJK-Regular.ttc") .setTtcIndex(0).build() textView2.typeface = Typeface.Builder(assets, "NotoSansCJK-Regular.ttc") .setTtcIndex(1).build() textView3.typeface = Typeface.Builder(assets, "NotoSansCJK-Regular.ttc") .setTtcIndex(2).build() textView4.typeface = Typeface.Builder(assets, "NotoSansCJK-Regular.ttc") .setTtcIndex(3).build()
  150. Variable Font • OpenType Variable Font • Either string representation

    or FontVariationAxis class can be used. textView1.typeface = Typeface.Builder(assets, "AdobeVFPrototype.ttf") .setFontVariationSettings("'wght' 200").build() textView2.typeface = Typeface.Builder(assets, "AdobeVFPrototype.ttf") .setFontVariationSettings("'wght' 400").build() textView3.typeface = Typeface.Builder(assets, "AdobeVFPrototype.ttf") .setFontVariationSettings("'wght' 700").build() textView4.typeface = Typeface.Builder(assets, "AdobeVFPrototype.ttf") .setFontVariationSettings("'wght' 900").build()
  151. Locale List Fallback • Font selector is now aware of

    locale list • By default, system locale settings is used. textView1.textLocales = LocaleList.forLanguageTags("en-US,ja-JP") textView2.textLocales = LocaleList.forLanguageTags("en-US,zh-CN") textView3.textLocales = LocaleList.forLanguageTags("en-US,zh-TW") textView4.textLocales = LocaleList.forLanguageTags("ja-JP,zh-TW")
  152. Serif Fallback • System font is now aware of serif

    font. • Use serif font if fontFamily=”serif” is specified.
  153. What’s new in Layout

  154. Performance Improvement • Removed giant mutex lock during doing text

    layout. 0 25 50 75 100 milliseconds 125 150 175 API 27 API 27 API 27 API 28 1 thread 2 threads 4 thread 8 threads 34.9 49.0 82.1 318.7 22.1 22.5 26.5 82.6
  155. PrecomputedText You are already familiar with this feature.

  156. Justification • Justification by whitespaces • API is available since

    API 26, but due to bug, please use it API 28+
  157. Line Spacing Improvement • Avoid glyph overlap for the taller

    glyph languages. • Enabled by default API 28+ • You can move back to old impl by specifying fallbackLineSpacing=”false”
  158. Thank you for your attention!

  159. Q&A