Android Question Optimizing label redraws

Alessandro71

Active Member
Licensed User
Despite not being a game, my app has several labels that gets updated several times a second:
text is changed, visible and color attributes are changed, position and size are changed.
How does Android handles redraws?
When I set label.Text, it gets redrawn on screen.
Does it get redrawn even if the new text is the same as the current one?
Is it worth to check if the new string is different from the previous one, and not set it to avoid a redraw?
Is it the same for visibility and color attributes, or does the graphic layer already performs these kind of checks?
 

sorex

Expert
Licensed User
in android you can enable a special mode where you can see how much time it needs for the current redrawings.

it's something about vertical lines.

I read that google was using or planned to use these meassurements to mark 'badly' designed apps.
 

Alessandro71

Active Member
Licensed User
Problem is speeding up screen redraws:

Is it a waste using something like this to update a text label only if it's changed
B4X:
Public Sub setText(text As String)
    If (text <> m_textLabel.m_label.Text) Then
        m_textLabel.m_label.Text = text
    End If
End Sub
just to avoid unnecessary screen redraw?

Does the graphic framework already does this, so it's a useless optimization?
 

emexes

Well-Known Member
Licensed User
What I do when I have a rapidly changing item on the screen, eg a counter of incoming bytes, is:

everytime a on-screen variable is updated, set a flag to indicate that it needs to be refreshed on-screen

B4X:
For I = 1 to 1000000000
    LoopNumber = I
    RefreshFlag = True
 
    DoStuff
    Sleep(0) or Sleep(1) to enable other stuff to happen too
Next

and then in a timer tick (somewhere between 3 and 20 times per second, to balance CPU load vs user dazzle)

B4X:
Sub tmrRefresh_Tick

    If RefreshFlag Then
        RefreshFlag = False    'clear flag first, just in case any updates occur during the next three lines
        lblLoopNumber.Text = LoopNumber
        lblSomethingElse.Text = SomethingElse
        lblButWaitTheresMore.Text = ButWaitTheresMore & " !!!"
    End If

    If RefreshStatusFlag then
        RefreshStatusFlag = False
        lblStatusLine.Text = "S1:" & ThisStatus & " S2:" & ThatStatus & " LS:" & LinkStatus
    End If

End Sub

This has the side-benefits of:
(1) not doing non-string to string conversions if the activity is not on screen (because you disable timers when an activity is paused), and
(2) automatically loading up all the screen display views on the first timer tick when an activity is resumed (or started)
(assuming you set all refresh flags = True in the activity resume routine)
(vs what I often see - and sometimes do myself - is the same code twice: once to initialize the view, and then again to update it)
 
Last edited:

Alessandro71

Active Member
Licensed User
this is actually more convoluted than that.
i actually have a timer for updating some counters, but the most labels (about 150) needs to be updated synchronously with a data capture cycle, which runs several times a second.
i tested 2 versions of the update cycle:
1) directly updating the labels
2) checking previous value before actual update
and timed the 2 runs:

here are the results (average value over about 1000 samples)
1) 11.02 ms
2) 8.17 ms
this is against a total cycle time (data capture + refresh) of about 210 ms

so a small optimization seems to be possible
i will also check your suggestion about avoiding non-string to string conversions, which can be applied to some of my labels
thank you
 

emexes

Well-Known Member
Licensed User
avoiding non-string to string conversions
If you can compare the values before translating them to strings, that'd be good, eg:

B4X:
Dim NewValue, OldValue as Double

NewValue = reading from sensor or whatever
If NewValue <> OldValue Then
    lblValue.Text = NumberFormat2(NewValue, 1, 3, 3, False)
    OldValue = NewValue
End If
will (ignoring time taken to read sensor) be significantly faster than

B4X:
Dim NewText, OldText As String

NewText = NumberFormat2(reading from sensor or whatever, 1, 3, 3, False)
If NewText.CompareTo(OldText) <> 0 Then
    lblValue.Text = NewText
    OldText = NewText
End If
and that's without taking into account the time taken doing garbage collection of all those discarded temporary strings.

Not that it matters here, but: the OldText = NewText string assignment is virtually instantaneous (is just a pointer assignment) and might even be (slightly) faster than the 8-byte Double assignment. Both those times get swamped by the String creation and formatting and disposal operations, though.
 

emexes

Well-Known Member
Licensed User
most labels (about 150)
I missed that bit about 150 labels. Yikes! How big is your screen?!?! Are we talking 150 different distinct label texts (eg, numbers) or just a few (eg, "Disabled", "Active", "Alarm")?

Nonetheless, I'd be expecting each non-string comparison to take less than a microsecond, including array indexing, and so to check all 150 should take less than a millisecond. Although it's currently using about 6% of your CPU (11 ms per 210 ms?) so... is there other low-hanging fruit in the remaining 199 ms of that cycle? Like, is that the time taken to transfer 150 x 1-byte readings over a serial link? Can you compress that down to using bits instead of bytes? Or just send changed data (plus unchanged data but at a lower rate, or by request only)?

Sorry, probably preaching to the converted here. Still, if you'd like a fresh set of eyes on the problem, I'd be interested to look at it.
 
Last edited:

emexes

Well-Known Member
Licensed User
I'm thinking this is either some industrial monitoring system, or a results/scoring system for your kids' athletics carnival. Is either of those guesses close?

edit: ah, vehicle data logging. I used to program dynamometers, including reading OBD using the (then new) ELM chip. Presumably you're using some more-direct link than OBD though, and don't have any say over what format it gets sent in.

If you're using a database rather than a plain text or binary file to save the data, and you're storing one reading per record rather than one set of (150?) readings per record, then I could imagine most of that 199 ms is consumed by the database library :-/

But... no problem.
 
Last edited:

Alessandro71

Active Member
Licensed User

real time driving data visualization, through an already highly optimized OBD data reading process
most of the labels are graphic elements, only some of them are text
i just tested some of the suggested optimizations, but the results are neglectable in my environment
 

emexes

Well-Known Member
Licensed User
Looks good. Certainly not the static industrial indicator panel I'd imagined originally.

You said that the label updates were taking 11 ms out of 210, and that you'd managed to squeeze that down another 25% already, so I agree with you, you're not going to save much more out of that area. I assume the word "Yaris" and other static stuff isn't being updated constantly.

Are there 150 elements on that screen? Is that one screen of several? Is there stuff hidden behind the main screen that we can't see but is using pixel-moving time to keep updated anyway?

Your OBD reading must be pretty highly optimised. My recollection was I could get maybe 20 readings per second if I bent the rules of the ELM protocol by sending the next request before the current request had properly finished. Although most manufacturers were moving to CAN, so that's probably sped things up too.
 

sorex

Expert
Licensed User
from what I see I don't think you get more than 5 updates a second.

why not keep that as reference and update only every 200ms instead of when 1 value changes somewhere in between?

I also don't see why you need a database to keep track of 10 values? you'll have less overhead when using just a map.
The graph just moves on so you only need the latest value while you move the rest to the right.
 
Top