27

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?

phlebas
  • 1,210
  • 2
  • 13
  • 24
  • Do you really mean commenting out `setText()`, or do you mean commenting out the `String.format()` call? `String.format()` is expensive. – CommonsWare May 29 '12 at 18:17
  • No. The setText(..). That is the strange thing about it. – phlebas May 29 '12 at 18:32
  • 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 May 29 '12 at 18:36
  • I tried that. Even if I call setText("hugo") the problem persists. – phlebas May 29 '12 at 18:44
  • Then, head to Traceview and see what it turns up. – CommonsWare May 29 '12 at 18:50
  • I'm new to Traceview, so I'm not sure if everything I did was correct. I surrounded *setText* with Debug class method tracing calls... The top 5 excl CPU% invokations are: ViewParent.invalidateChildInParent(16%), View.requestLayout(10%), ViewGroup.invalidateChild(8%), TextView.setText(7%), toplevel(6%) – phlebas May 29 '12 at 19:06
  • 2
    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 May 29 '12 at 19:14
  • Do you use perhaps custom fonts on that TextView? I noticed the same with custom fonts. Without setting a custom font it was blazing fast. – Zsolt Safrany Sep 20 '13 at 13:00

5 Answers5

22

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.

Tim
  • 35,413
  • 11
  • 95
  • 121
  • 4
    Thanks, there was indeed a problem with my layout files. Originally the TextView was within an layout. I simply merged it into the master layout and the CPU% is back to normal. So we can narrow it down to, maybe there is a special include attribute/strategy one has to follow in order to avoid this. – phlebas May 29 '12 at 19:34
  • 2
    @Tim You made my day bro. Its really recommended to have fixed size layout specially when you use a list view. I've used previously "wrap_content" and listview is so laggy, Once i change layout in to fixed dp values, speed become much much faster. – Yasitha Waduge Dec 05 '13 at 04:10
  • Confirmed for sure - we just spent 3 hours debugging this and when we took out the setText() calls, the execution was noticeably different. When we made the text views span the entire width, we could then put the calls to setText() back. – Quintin Balsdon Jun 29 '15 at 17:36
3

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.

shmulik.r
  • 1,101
  • 9
  • 5
  • I encountered the same issue, and setting the `layout_width` and `layout_height` to a fixed size indeed made the `setText()` call much faster. Thanks! – VinceFior Mar 16 '16 at 01:34
  • 1
    Also, before I made this change, I was able to improve performance a bit by only calling `setText()` if the current text (found via `getText()`) was different from my new text, for what it's worth. – VinceFior Mar 16 '16 at 01:45
  • A `getText()` check is gold! I optimized my entire app due a performance problem reported by Google pre-launch report. Two months later I finally figured out that `setText()` was the problem. Like I said... gold! – l33t Mar 09 '21 at 22:55
1

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) .
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Thanks. Although the UI Thread CPU% is at 1%. So the benefit won't be too big. I considered some of those suggestions myself. The TextView is currently updated upon Broadcast Reception, which normally occurs about ~1 sec. Regarding time checks, it depends on the perspective. I expect the time to change most of the time, so I don't check first and therefore reduce overhead if the time changes. If I would expect the time to change not so often you were right. Extracting the interface would of course be more clean. And String formatting... Well as you point out, one has to live with drawbacks. – phlebas May 29 '12 at 19:50
  • Actually it is guaranteed that the time has changed on each invocation. – phlebas May 29 '12 at 19:53
  • yes , about time checks , it depends on your code . if you use the method i've suggested , i think it's better to do the checking , since you need to check every 0.5 seconds (so that you will have a low chance of skipping a second). – android developer May 29 '12 at 19:56
  • btw, can you please post the problematic project ? this could be a nice exercise for other people ... – android developer May 29 '12 at 19:58
  • It is not a clock, so if the broadcast stops the time stops. What do you mean, post the project? The involved code is essentially a BroadcastReceiver that invokes above snippet. – phlebas May 29 '12 at 20:16
  • i meant showing it so that we could all learn from it . that's because you've found the answer - something was wrong on the layout file . maybe publish the bad xml file? – android developer May 29 '12 at 20:48
0

In my case it was this property of TextView:

android:ellipsize="marquee"

Removing it speeded up setting text.

vinga
  • 1,912
  • 20
  • 33
0

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

John Smith
  • 844
  • 8
  • 26
  • Measuring, drawing on the background thread is not thread-safe as it depends on Context values which get mutated without synchronization from the main thread. – mhansen Sep 29 '21 at 23:03