27
votes

Running my application causes ~40% CPU usage on my Phone:

final String position = String.format("%02d:%02d:%02d", time.getHours(), time.getMinutes(),
                time.getSeconds());
getActivity().runOnUiThread(new Runnable() {
    @Override
    public void run() {
         c.mTxtPosition.setText(position);
         ...

By commenting out the setText method the CPU Usage drops to the expected level of ~4%. The method is invoked every second and does refresh ImageViews, CustomViews ... without causing the same load excess. Besides the CPU Usage dalvik constantly reports garbage collecting of about 10-1000 objects just by calling setText().

Creating a tracefile like this:

Debug.startMethodTracing("setText");
c.mTxtPosition.setText(position);
Debug.stopMethodTracing();

traceview lists the following methods as Top 5 by their respective exclusive CPU%:

  • ViewParent.invalidateChildInParent(16%)
  • View.requestLayout(11%)
  • ViewGroup.invalidateChild(9%)
  • TextView.setText(7%)
  • toplevel(6%)

Has anybody an explanation for this?

5
Do you really mean commenting out setText(), or do you mean commenting out the String.format() call? String.format() is expensive.CommonsWare
No. The setText(..). That is the strange thing about it.phlebas
On a whim, try replacing final String position = String.format(...); with final String position = "Hi, Mom!"; and see what happens. It is possible that some compiler optimizations are delaying the format() call until you use the position variable for the first time. I wouldn't expect this, but it makes more sense than one random setText() call being dramatically more expensive than other things that you are doing. Also, you might consider using Traceview to get more precise information on where the time is being taken up.CommonsWare
I tried that. Even if I call setText("hugo") the problem persists.phlebas
That might be useful, but it's not what I had in mind. Log more of your app, perhaps using the DDMS perspective to toggle on/off method tracing rather than tracing some section of code. Then, see where the time is being spent, not on a percentage basis, but on a milliseconds basis. Comparing this with and without the setText() call should dramatically highlight something if your CPU utilization values are accurate.CommonsWare

5 Answers

22
votes

I noticed this myself a while ago, I think the problem is that every time you call setText, the size of the textbox can change, thus requiring the entire screen to go through relayout (expensive).

I haven't tried this myself yet, but if your textbox is simple and can be made to be a relatively fixed size, maybe try to subclass TextView and create a view that does not resize itself on setText, but rather just draws whatever it can into the existing area? That would save a lot of time.

Perhaps theres already a flag to setText that can make it do this, but I'm not aware of it, though I haven't searched closely.

3
votes

In my case, I update a TextView from touch event, which cause a lot of updating The solution was to change the TextView layout_width & layout_height to fixed sized.

1
votes

some possible improvements :

  1. try using a handler which updates the textview every 0.5 seconds instead of a thread that does it.
  2. make the runnable a final constant object instead of craeting a new one every second.
  3. consider checking that the time has changed (newTimeInMs-LastPublishedTimeInMs>=1000) before telling the textview to update itself.
  4. instead of String.format , try using StringBuilder . however , you won't enjoy the locale solution that the String.format gives (for example , for arabic digits) .
0
votes

In my case it was this property of TextView:

android:ellipsize="marquee"

Removing it speeded up setting text.

0
votes

If you look at the source code of setText method you can see that it does a lot of heavy lifting - there is measuring, drawing and object allocations, all of which run on the main thread.

You can use the new PrecomputedText API in order to do all of this on the background thread and make setText faster.

You can use the following working example using kotlin & coroutines

private fun TextView.setTextAsync(text: String) {
    val textView = this
    lifecycleScope.launch {
        val params = TextViewCompat.getTextMetricsParams(textView)
        val precomputedText = withContext(Dispatchers.Default) {
            PrecomputedTextCompat.create(text, params)
        }
        TextViewCompat.setPrecomputedText(textView, precomputedText)
    }
}

For more details you can read an article about it on my blog https://androidexplained.github.io/android/ui/2020/10/21/improving-textview-settext-performance.html