Does Android batch UI operations or re-draw every single command? - android

This question came to my mind when re-binding data to views in RecycleView. Before selectively applying UI changes to the views, I usually reset them all to their default states.
Such as
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/* Reseting to normal/default state */
holder.title.visibility = View.VISIBLE
holder.poster.visibility = View.VISIBLE
/* Applying data */
if (data.poster.url == null) {
holder.poster.visibility = View.GONE
}
}
Here the poster View has potentially its visibility changed to VISIBLE and GONE again within a very short time interval.
Does Android actually invalidate and request drawing for both visibility changes? For example, if before binding, holder.poster view was GONE, then Android would draw it visible and then gone again?
Or does it batch them and only execute the latest state? For example, if we want the app to run at 60fps, it might batch UI operations in 16ms intervals. I can change Visibility hundreds of times, but it will actually draw the very last state within that 16ms batch.

The 2nd point holds true, but for a much more simple reason in your use-case.
The onBindViewHolder method runs on the main thread. Thus no other operation is possible on that thread until it returns.
Every change to a view property might invalidate the view. Layouting and drawing happens once, after onBindViewHolder has returned.

Yes, ViewHolders are recycled. That's the reason for their existence: so that expensive-to-build ViewHolders can be recycled to display different items. Yes, you need to zero out any changes to the contents of the ViewHolder that may have been made while an item was bound to it.
Rendering is also batched. Any change you make to a View property invalidates rendering or layout (as appropriate), and layout and rendering passes occur later. Property changes on views ultimately make calls to RequestLayout(), and/or RequestRender() to schedule layout and/or rendering passes at a later time. There is virtually zero cost for the second and subsequent changes.
I'm not quite sure when "a later time" is, but there's no delay involved. Right away, for all practical purposes, but batched. Something along the lines of process all queued messages, and when there are none, start a layout pass if it's been requested.
Rendering then takes place in a separate pass, after the layout pass has taken place.
Recycler view actual does a LOT of work figuring out where items were displayed, where items will be displayed and animating between the old and new location. This occurs in the layout pass, after you have notified the RecylerView that items in your adapter have changed. But it's done in a batch. You can toggle away on visibility to your hearts content. Only one render pass, and one layout pass takes place. (That's a sight simplification; atypically for an Android view, internally, RecyclerView, actually executes two layout passes, but that's not something you need to know or are supposed to know. It's a hack. I THINK you're being called during the first layout pass).
With respect to toggling bits and pieces in an onBindVieHolder call... rest assured that layout and rendering for the contents of the ViewHolder have been thoroughly and completely invalidated by the time you get the call. You are about to display something else, somewhere else. So go to it!

Related

Does View gets redrawn when property programatically changed to same value?

Let's have some View in fragment (e.g. LinearLayout or ImageView). If I change property of this View programatically (e.g. change backgroundColor) to same property that is already set, is view redrawn/rerendered?
Example of double set:
binding.someLinearLayout.setBackgroundColor(redColor)
binding.someLinearLayout.setBackgroundColor(grayColor);
binding.someLinearLayout.setBackgroundColor(grayColor);
binding.someLinearLayout.setBackgroundColor(grayColor);
LinearLayout will be redrawn twice or 4 times? If 4 times, should I implement something like this to prevent it?
fun setBackgroundColor(ll: LinearLayout, color: Int){
val current = binding.progressBarChunk4.background as ColorDrawable
if(current.getColor() != color)
ll.setBackgroundColor(color)
}
setBackgroundColor(binding.someLinearLayout, redColor); // Color set
setBackgroundColor(binding.someLinearLayout, grayColor); // Color set
setBackgroundColor(binding.someLinearLayout, grayColor); // ignored
setBackgroundColor(binding.someLinearLayout, grayColor); // ignored
So basically I am asking if multiple set of same property affects performance, because view gets redrawn everytime.
If you call setBackgroundColor four times in a row like that, it doesn't cause four redraws. The view is only redrawn one time on the next loop of the main thread, after your current function has returned.
Changing these properties usually does invalidate the view, so your first call to setBackgroundColor will trigger a redraw on the next loop of the main thread, even if it matches the existing color. I can't guarantee that's true of every existing property in the core Views, because I haven't checked them all. It is definitely true for setBackgroundColor and setText.
Your defensive check of the current color could potentially prevent an unnecessary redraw, but only if you ultimately don't mutate any views in this iteration of your main thread. I can't think of a case where it would be worth worrying about. If this is in onCreate() or onViewCreated(), the view hasn't been drawn yet anyway. If this is in a click listener, your view is getting redrawn anyway because your button's visual state is changing.
It might be worth doing defensive checks for changes that affect the size of the view, because then you could be preventing a re-layout. But probably only if this happening during some animation, because otherwise the savings would not be noticeable.

When to use executePendingBindings() and when its not required?

I am looking for a practical example for both the cases that what to use when? I have seen similar threads but they only tell this "when binding must be executed immediately" but there is no real time example for any case where you have to force the binding to be executed. So please if anyone can explain with any example that when to use it and when its not that required!
The case when you should use executePendingBindings() is (but there could be more):
Every time the binding update could cause a View to change its size and postponing the calculation in the next frame could cause the measurement to read wrong values.
In case of a RecyclerView, this happens if:
You have multiple viewTypes with different sizes (different XML)
Your row height changes based on view contents.
RecyclerView will measure the row size after the onBindViewHolder has completed. If based on the data you set in this method the height of the row changes, the measurement could not take into account the extra or reduced space occupied by your new content if you do not call executePendingBindings().
Forcing the binding to update data synchronously and not in the next frame keeps you safe from wrong row size measurement (and potentially missing content)

What's the difference between RecyclerView.setItemViewCacheSize and RecycledViewPool.setMaxRecycledViews?

The documentation says that setItemViewCacheSize
sets the number of offscreen views to retain before adding them to the
potentially shared recycled view pool.
and setMaxRecycledViews
sets the maximum number of ViewHolders to hold in the pool before
discarding.
But don't they both function as a cache where views are taken from (i.e., the first sets the number of views cached by the RV, while the second sets that of the RVP)?
Also, when a view is needed, where is it taken first, from the RVP or from the RV's cache?
And what's the optimal (scrolling-wise, ignoring memory) configuration for the two for a simple unnested recyclerview?
Here's the full documentation for setItemViewCacheSize():
Set the number of offscreen views to retain before adding them to the potentially shared recycled view pool.
The offscreen view cache stays aware of changes in the attached adapter, allowing a LayoutManager to reuse those views unmodified without needing to return to the adapter to rebind them.
In other words, when you scroll the RecyclerView such that there's a view that is just barely completely off-screen, the RecyclerView will keep it around so that you can scroll it back into view without having to re-execute onBindViewHolder().
This is different from the recycled view pool, which is a pool of views that the RecyclerView has said it doesn't need anymore, but which is kept around to avoid the expensive task of inflating new views.
In short, the "item view cache" holds elements in such a way that the RecyclerView can avoid calling both onCreateViewHolder() and onBindViewHolder(), whereas the recycled view pool holds elements such that the RecyclerView can avoid calling onCreateViewHolder() but will still have to call onBindViewHolder().
Also, when a view is needed, where is it taken first, from the RVP or from the RV's cache?
I don't think it really matters, and I don't know of a precise definition, but generally I think you can imagine that views that have just exited the device's viewport and then return to the viewport will be taken from the "item view cache", while views that come on-screen but were not previously on-screen will come from the recycled view pool.
And what's the optimal (scrolling-wise, ignoring memory) configuration for the two for a simple unnested recyclerview?
Just use the defaults. I would never consider changing these unless I had profiled my app and determined beyond a doubt that the defaults weren't working for me. But, if I just take you at your word, ignoring memory, the larger the cache size the better. But really, just use the defaults.
I've read this article by Pavel Shmakov and it explains the difference between Pool and Cache
Pool and Cache in Action
If a ViewHolder was found nowhere, it is created and bound.
If a ViewHolder was found in pool, it is bound.
If a ViewHolder was found in cache, there is nothing to be done.
So, as long as the cache isn’t full, ViewHolders go there. If it’s
full, a new ViewHolder pushes a ViewHolder from the “other end” of the
cache into the pool. If a pool is already full, that ViewHolder is
pushed into oblivion, to the garbage collector that is
Now let’s look at the way pool and cache behave in a couple of actual
RecyclerView usage scenarios.
Consider scrolling:
As we scroll downwards, there is a “tail” behind the currently seen
items consisting of cached items and then a pooled item. When the item
8 appears on screen, no suitable ViewHolder is found in cache: no
ViewHolder associated with position 8 there. So we use a pooled
ViewHolder, which was previously at position 3. When item 6 disappears
on top, it goes to the cache, pushing 4 into the pool.
The picture is different when we start scrolling in the opposite
direction:
Here we find a ViewHolder for position 5 in view cache and reuse it
right away, without rebinding. And that seems to be the main use-case
of the cache — to make scrolling in opposite direction, to the items
we’ve just seen, more efficient. So if you have a news feed, the cache
might be useless, since users won’t go back too often. But if it’s a
list of things to choose from, say a gallery of wallpapers, you might
want to extend the capacity of the cache.
Read more here https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714

Should I use SurfaceView or View for a frequently updated view (15fps)

I am building an application where I need to display signal traces in real time (think the kind you see on cardiac monitors in hospitals). For my line animation to appear smoothly I'd like the frame rate to be around 15 frames per second (I did some experimenting to come to the conclusion this was the lowest acceptable frame rate). It probably does not matter in the context of this post but I potentially have numerous such view in a ListView (~20 is common with about 5 being displayed each time).
Until now I've been doing this using a custom view, extending the View class. I have a thread in the containing fragment that calls invalidate() on the view every ~70ms that redraws the view. This is not causing problems per se as I've optimized my onDraw() function to run in under 2ms most of the time.
Now I have added a Spinner to the fragment and while debugging it I noticed that once I opened the Spinner the adapter was constantly hitting getView() calls, even though I was not touching the Spinner (i.e. open but not scrolling) and also lagging a bit. This led me to realize that my whole fragment was being redrawn every ~70ms which to me sounds bad. Now the questions:
Is there a way to trigger onDraw() on a child view without it causing a redraw of the complete hierachy?
Should I just resort to a SurfaceView? (that would not cause a complete view hierarchy redraw, right?)
I've noticed that the SurfaceView is not HW accelerated. Does that matter if I'm only using basic draw functions (drawLine() and drawText())?
Would GLSurfaceView be any better in my case?
I'll try to answer my question so that it might be useful to other people who might encounter the same.
Containing a bunch of SurfaceViews inside a ListView is a fail. It seems to me since the SurfaceView drawing is not synced with the rest of the UI you will get black lines flickering between views when you scroll (which is probably since the SurfaceView is reassigned to new data source via getView() and displayed before it gets a chance to redraw itself). Triggering a redraw inside getView() was not enough, apparently.
Delyan's first comment to my question was valid even though it might not always apply. If I go through each view in the ListView by hand and call invalidate() on it the redraw will not bubble up through the hierarchy (I did not need the invalidate(l,t,r,b) signature, but it's probably a smart idea to try it out if you're having problems with excessive redraws). So, even though Romain Guy mentions in his talk that invalidate() will bubble up to the root it's apparently not always the case. Delyan's comment about different implementation of HW/SW might be the reason - I'm rendering my views in HW.
Anyhow, to work around the issue I created this method inside my adapter:
public void redrawTraces(ListView lv) {
final int viewCount = lv.getChildCount();
for(int i=0; i < viewCount; i++) {
final View stv = lv.getChildAt(i);
if(stv != null) {
stv.invalidate();
}
}
}
Where the views were not SurfaceViews. Finally, to make it clear what I was doing wrong, I was doing this:
adapter.notifyDataSetChanged();
This call did bubble up and caused a redraw through the entire hierarchy. My assumption was that this was roughly the equivalent of calling listView.invalidate() in terms of updating the whole hierarchy; perhaps that's correct I didn't look into it.

best practice for updating an adapter

I have an adapter that displays a grid of thumbnails with a text. These thumbnails are heavy to load, heavy to draw, etc.
The thumbnail gridview is constantly filled with new content, let's say, 1 new item every 2 seconds.
My adapter has a function that I call from outside to inject new items:
public void postNew(Item i) {
arrayStuff.put(i);
notifyDataSetChanged();
}
What happens is, with my current approach, when I insert a new element in the gridview, it refreshes everything, even if the added item is not going to be visible. The refresh process kind of breaks the experience, specially if the user is browsing the gridview and new content arrives.
How would you recommend improving this? is there a lighter 'notifyDataSetChanged()' or something like that?
I do not know of any lighter version of notify data set, but you can always use ListView.getFirstVisiblePosition and ListView.getLastVisiblePosition to determine whether your latest added position is visible, and only call notifyDataSetChanged if it is.
As for "heavy" bitmaps, as heavy as it is I think you should resample or scale it to the minimum size you need, using LruCache you can reduce the need of re-drawing on notify data set changed.
It sounds like you probably need to implement some form of caching, it's not very good memory management to have images which are not visible loaded into memory, ideally you would retrieve them from cache when they become (or are about to become) visible.
An alternative approach could be to add some form of visual indicator when new content arrives and then implement "pull down to refresh" or similar, then make a call to notifyDataSetChanged() on your adapter to refresh the content. I can imagine that refreshing every couple of seconds would not give a great UX because it would be hard to follow if the screen content is constantly changing.
You need create custom view(dynamic at runtime) that adds multiple imageview and appropriate textview, the container view should be LinearLayout, after that you can able to update a particular view or element.

Categories

Resources