[MUSIC PLAYING]
SIYAMED SINIR: Hi, everybody.
My name is Siyamed Sinir,
and I work on Android Text.
And today, I would like to talk
about why improving your text
measuring performance
is important for
your overall app's performance.
In order to do this, I wanted
to gather some numbers.
And therefore, I wrote a simple
sample app that has a feed
and is very similar
to the applications
that we use every day.
It has a list of items, and each
item has an image and some text
into it.
When we look at the text, as
you can see in the picture,
the user name and the
title for each feed item
is what we call
single style text.
And the content contains a text
that has different colors, text
sizes, fonts, and et cetera.
One important point here that
I would like to emphasize--
on our platform right
now, the hyphenation
is turned on by
default. However,
in this app, as
you can see there,
is no hyphenation applied.
And the reason is Android
works by some dictionaries
in order to be able to
hyphenate the words.
And since these words are just
random characters side by side,
it cannot do any hyphenation.
When we look at
each feed item, we
see that it has a very
simple structure--
a linear layout at top, an
image with a relative layout,
and three TextViews for user
name, title, and content.
And I'm pretty sure
it's either the same
or very similar to one of your
apps' existing design or layout
structure.
What I wanted to see
was for each feed item,
what is the percentage
of the time spent on text
versus the whole layout.
Therefore, what I did is I
wrote a benchmark where I called
the linearLayout.measure()
on one end.
And meanwhile, I recorded
all the parameters
that were passed to those
different TextViews,
and I simulated the same
thing on those TextViews.
Because of the
relative layout here,
the two TextViews at the top
were getting two measure()
calls each.
In order to have
consistent numbers
or repeatable measurements,
I locked the CPU.
I ahead of time compiled
my budget benchmark app,
and I set the debuggable false.
And these numbers that we will
see soon are for a Pixel X
Large that is running Android P.
So the first question
is how much time does it
require for linear
layout to measure
versus how much time it requires
to measure all those three
TextViews?
This number was interesting.
4.60 milliseconds
for the whole layout,
and 98% of that time
is spent on text.
Since the CPU is locked,
maybe normally the numbers
on the real device would
be lower than four.
But still, the ratio
will remain the same.
The next thing I
wondered was if I
were to disable hyphenation,
how much improvement would I
get from my linear
layout, the full layout?
And just turning
the hyphenation off
drops all the times by 100%.
So it improves the
measurement performance by 2x.
At this point, I
want to remind you
that hyphenation was
turned on, but there
was no visible hyphenation.
So turning off hyphenation did
not have any effect on the UI.
Finally, I wanted to check
how the numbers would improve
if I were to use a new API that
we added on Android P and also
Android X--
PrecomputedText.
These numbers were crazier.
Four milliseconds
become 0.2 milliseconds.
It's almost 20% to
any X improvement.
I would like to describe
what PrecomputedText does
and how it works.
But before that, I would like to
talk about what kind of things
we went through
while implementing
the PrecomputedText.
We were asking
ourselves questions
like why is text
measurement so expensive
and can we make it faster.
Since almost the
beginning, we have been
improving the text performance.
But as you would understand
from the number 98%,
it's always going
to be a bottleneck.
Therefore, we try
to see can we move
all these expensive measurement
to a background thread
and provide an API.
In terms of why it
is so expensive,
whenever the system calls
a measure() on TextView,
it first does some preliminary
work and tries to identify
the intrinsic width and height
of the text that it contains.
Then, according to the
parameters that are passed
through on measure() and also
the numbers that it finds,
it creates a layout.
All these processes goes
into our native code
because we have to
use some open source
libraries such as Free
Type and [INAUDIBLE]
in order to measure our text.
And when we look from the
scope of our measure, 90%--
actually more than 90%--
of our time is spent
on the native code.
And when we pass
a string like this
to our native code,
what it does is
it first divides it into words.
And then for each word,
it applies text shaping.
It finds the form that it
can render the character
and tries to bring those
characters in the word
together.
Then we check how
much time this takes
compared to all the time that's
spent on the native code.
It is, again, more than 90%.
So a text measurement taking the
90% of the whole measurement,
native code taking
the 90% of the text,
and shaping taking more
than 90% of the native code
means that most of
the measurement time,
at least for this app,
is spent on text shaping.
The system applies the same
rules for all of the words.
And for each word that is
measured, it caches them.
One of the reasons to cache
them is just after the measure
or layout there, will
be a draw operation,
and the draw will need
the same information.
Then it applies line
breaking and hyphenation.
So one valid question here
is why does hyphenation--
turning it off-- improve
the performance twice?
It's mainly because of
whenever it tries to hyphenate,
it has to apply text
shaping for more words.
Let's say in here, example is
divided into ex- and -ample.
It has to calculate two more--
it has to do two more
shaping on two more words.
So the next question
is, if we were
to move this expensive
measurement to background
thread, what we would need.
The first issue we encountered
was before Android P,
the native code was acquiring
giant synchronization locks.
And we fixed it on Android
P. We made the locks smaller.
Then we wondered, can we
use the existing layout
classes for such a goal?
And there were two issues,
two important issues,
at that point.
One of them--
layout classes need
a width to be provided
to them so that they
can do their calculations.
And that width, you
would not have that width
before measuring, so it was
a chicken and egg problem.
The other one is
the layout objects
are just blueprints for
the text that contain kind
of a cache on the Java side.
And they do not know about
the previously calculated
native word layout objects.
All they know is how
many lines there,
where do they start and end,
and what are their coordinates.
So we wondered,
what if we created
a construct, first of all,
that doesn't need the width?
Second, it should
be able to create it
on a background thread safely.
And third, it has to
have strong references
to those native layout objects.
This is important because
in terms of layout,
if you were to create a
layout before you need it,
then maybe when it's time
to measure or render it,
you will lose the
word layout objects.
They will be evicted
from the cache,
and therefore there
was no guarantee
that you would have
the speed improvement.
We wanted to extend this
construct from CharSequence
and Spannable
because we wanted it
to be compatible
with our current APIs
and also your applications.
Since it is mostly interested
in how the text will look,
this construct needed
some parameters
that would change
the text styling,
such as the text size,
color, locale, and et cetera.
One important
point here is there
was a reason why previously
those native objects were being
cached and evicted, and
it is the amount of memory
that they use.
Right now when you
use PrecomputedText,
you will be spending 20
kilobytes for 500 characters.
Even though it
implements Spannable,
which is a mutable
interface, every calculation
that PrecomputedText does is
done at the construction time.
Therefore, you should not be
calling SetSpan or RemoveSpan
functions with spans or
styling information that will
change how the text will look.
Otherwise, you will
get an exception
because that would invalidate
all the computation,
and it would be useless.
Then we look at the parameters
that PrecomputedText requires.
Since most of the text
styling information
right now is encapsulated
in the text pane,
it requires the text pane as a
mandatory constructed argument.
The others are BreakStrategy,
HyphenationFrequency,
and TextDirection
are the functions
that you would already know
from the static layout builder.
Since most of the time you will
be designing your text styling
in your XML, either
layout.xml or styles.xml,
we wanted to add
a helper function
where you can create
the PrecomputedText
parameters using a TextView.
However, at this
point, I would like
to emphasize that
TextView is not
required to create a
PrecomputedText.Params.
It is a helper function,
and this function
is going to make
more sense when we
go through how to
use PrecomputedText
with RecyclerView.
Since version 25, RecyclerView
has a Prefetch feature,
and we wanted to provide
ways of using PrecomputedText
with RecyclerView.Prefetch.
And normally during on
your onBind View Holder,
you will call setText,
and then the RecyclerView
would measure the whole
layout that you just created.
And we know that the measurement
part of the TextViews
are expensive.
Then you use RecyclerView
with PrecomputedText,
you will just change setText
text to setTextFeature.
And what Prefetch will do
is before an item is shown
on the screen, it will go
to the background thread,
do the text measurement,
and as a result will
create the PrecomputedText.
When the computation
is ready, it
will switch back to
the [INAUDIBLE] thread,
and it will call setText with
this PrecomputedText, which
will make the
measurement part much
faster than the previous case.
Using it is pretty easy.
Instead of calling setText
in your onBind View Holder,
you just change it
to be setTextFeature
and some configuration
for PrecomputedText.
And here, what you
tell the system
is please run this task
on a background thread
and precompute my text with this
CharSequence, which is my data,
and this display configuration.
As you can see here,
TextMetricsParamCompat()
becomes very handy
to be able to create
the PrecomputedText.Params.
One point here is, though,
if you are changing your text
styling according to
the data you have,
you have to apply
all the styling
on your TextView or Spannables
before calling this function.
I will continue with how to
turn off hyphenation in your app
globally so that you can only
enable it when you need it.
This thing is also
pretty simple,
and you already saw the effect.
It improves the measurement
performance by 2x.
What you do is first you define
a new style in your styles.xml
and turn off hyphenation
for that style.
You extend this style
from a base style
in order to handle pre-Lollipop
and after-Lollipop cases.
And for version 21 and above,
you extend this base TextView
style from Material TextView.
And finally, on your team,
you set AndroidTextViewStyle
to be your new style.
If I were to summarize
all the talk,
I would say, first go and turn
off hyphenation in your app
globally so that your
measurement can be faster.
And it's not only going to
be the text measurement.
The whole screen
measurement, depending
on how much text you have,
will be almost twice faster.
And then please check
the PrecomputedText API.
And if you are
using RecyclerView,
please apply the PrecomputedText
and RecyclerView.Prefetch
code that I showed
you so that you
will get a more smooth scrolling
experience for your users.
This was my talk.
Thank you very
much for listening.
[APPLAUSE]
[MUSIC PLAYING]
