EDIT 03/12/14: Posted for a few weeks but no activity. Is my question clear enough? Or TL;DR? Unfortunately since I'm a new member I can't offer a bounty :-(
My question can be summarised as:
- How can I determine when a View which contains asynchronously loaded elements (such as web images) has fully loaded and rendered without resorting to a bit of a hack - see below.
- Is there a deterministic way to find out when the View has finished loading without simply using a longish, defensive delay value for
View.postDelayed()
?
Please read on for the background and specifics of this question if you think you can help.
Background of question
I'm making use of a third party library within my app to attempt to do the following:
- Third party library asynchronously loads some data via a REST API
- Library processes JSON data and uses Picasso API to download image URLs contained within the JSON response and then load them into a customised
View
(library view, not mine) which I then display in myActivity
.- Note: the Picasso calls are encapsulated by the library that I am using and the 3rd party library opted not to use the callbacks provided by Picasso which indicates when the images have loaded. I merely have access to a callback when the underlying JSON request has returned successfully.
- Once the custom
View
has loaded and is fully rendered, I want to save thisView
to storage as a PNG.
My problem occurs at step 3: I am unable to deterministically detect when the custom View
has finished loading so that I can save the PNG after the view has been fully rendered. Picasso, by design, loads images asynchronously into android View
objects and, I think as a result, the methods I have attempted below have not worked:
Use a
GlobalLayoutListener
to determine when the View has finished loading as suggested here.myView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // Try to begin saving PNG here savePNG(); });
- This didn't work as
onGlobalLayout()
fires an unpredictable number of times before theView
is completely rendered depending on how quickly the images are being loaded in the background. - I have used
View.postDelayed(Runnable, delay)
inonGlobalLayout()
but, again, it's not possible to know how long the delay should be due to device power and network bandwidth plus it introduces a race condition of sorts.
- This didn't work as
Many answers to similar questions on SO have suggested simply posting the
savePNG()
as a Runnable on the UI message queue. I think this works well for most scenarios but not when images are being asynchronously loaded into Views over the internet. Most the time this approach leads to attempting to save the image before the View is fully loaded and rendered. Of course, I canpostDelayed
but this leads to the same uncertainty over delay length and race conditions as in the previous point.I have attempted to wait for the
MessageQueue.IdleHandler.queueIdle()
event to dosavePNG()
but sadly this also occurs before all the resources have been loaded and rendered.Maintain the last time the
onGlobalLayout
was called and have another thread periodically check for "no updates in XXX ms" to determine that the view has settled down. This is not a great solution and could make the app appear unnecessarily slow but I could at least get it to work.I tried sub-classing the
View
and overridingdispatchDraw()
(suggested here) and calling back to my application - same problem asonGlobalLayout
- this fires multiples times before the View is fully loaded and rendered.
Wow, long post but hopefully it means I won't look so silly if there's a very obvious "gotcha" that has been staring me in the face all this time :-)