ScrollListener to improve performance#561
Conversation
|
I forgot to ask: Btw. the code of WeakHashMap can be found here . |
|
Im sorry, my fault, found squares code formatting for Intellij https://github.com/square/java-code-styles |
|
Regarding Question 3:
The same problem could appear if you fling both ListViews with different speed which means that first ListView who stops flinging will trigger However, in a real world application the described scenario is unlikely to happen. And if it will happen it will only have impact on this scrolling gesture. So beginning a new scrolling gesture on any ListView would not cause problems. Furthermore it will work like expected with interrupting and continuing dispatching afterwards. To solve this problem something like a counter should be implemented. I guess using |
|
To solve question 3: require a tag when creating the listener and only start and stop requests with that tag. |
|
I will be reviewing this, this week. |
|
Hi, thanks. Im not sure if I understand @JakeWharton correctly, but from my point of view adding a Tag (are we talking about an String?) for each request would be a clean solution, however it feels like an overkill to me to do so for each request. Summary of what I have implemented so far:
|
|
thanks for reviewing. I have pushed the desired changes. |
|
RecyclerView is not accepting this ScrollListner!! |
|
Of course not! They wrap the already existing one PicassoScrollListener into a RecyclerView.OnScrollListener ... However, you could simply copy the code of PicassoScrollListener and make it extend RecyclerView.OnSrollListener(). Btw. Im not sure if this pull request will ever be merged. So be careful if you have plans to use it in real apps in play store because there is no guaranty that this Pull request will be merged into Picasso 2.4 release. @dnkoutso @JakeWharton Any status updates here? Are you working (or have plans to ) on your own solution? |
|
so I guess it didn't get merged? too bad... |
|
FWIW, I really like @JakeWharton's 'tag' suggestion. Something like:
Picasso.with(context).
.load(imageUrl)
.into(imageView)
.withTag("mylist");
Picasso.with(context).pauseRequests("mylist");
Picasso.with(context).resumeRequests("mylist"); |
|
I agree. I will try to find time to implement this next week. However, in many of my apps I need this scrolllistener for every screen (I use a lot of viewpager/fragments that displays ListViews). So setting a tag for each request seems a little bit to much boilerplatte code. Any suggestion how to deal with that or do you think that that's not a common use case? From my point of view it's the default use case: If you need this scrolllistener feature to reduce GC then its likely that you need this in your whole app. |
|
@sockeqwe The scroll listener's code would be so simple using these APIs that I wonder if it's worth having it as part of Picasso at all. But that's up to the maintainers to decide :-) FWIW, I'm considering generalizing Smoothie's core functionality (https://github.com/lucasr/smoothie) to integrate with libraries like Picasso. Smoothie does some slightly fancier gesture tracking to figure out when it makes sense to trigger async loading in ListViews/GridViews. |
|
@lucasr Yep. I think the tag infrastructure is much more important than an explicit scroll listener. And then we can also have a cancel method for a whole tag which is also likely more important than pause/resume. |
|
@lucasr you are right, maybe my question was unclear. I thought it would be useful to make every request have a "default tag". So in my apps where I need the scroll listener everywhere, I do not want to specify a tag for every request. Instead every request will have a default tag. If you set a tag then the default tag will be overidden and the specified tag will be used. so that you can cancel/resume/pause all of them with the default tag (useful in my apps)
What do you think of a default tag? |
|
@sockeqwe Ah, ok, now I see what you mean. This could be accomplished by simply:
Picasso.with(context).pauseAllRequests();
Picasso.with(context).resumeAllRequests();Having an pauseAll/resumeAll API seems safer/cleaner than exposing a public constant for the default tag. But maybe that's just me. |
|
@JakeWharton Yeah, a cancelRequests(String) would be useful too. I guess the relevance pause/resume depends on their semantics. Roughly speaking (as I'm not very familiar with how requests are managed in Picasso), the way I see them being used is something like:
|
|
@sockeqwe Do you mind if I take over this issue? I think I have a pretty good idea of what needs to be done here. I already have a work-in-progress patch anyway. |
|
@lucasr No, you are welcome. I have started by implementing my own |
|
FYI: I've just created #665 with my suggested changes. |
|
@sockeqwe As far as I understand, this change shouldn't really be done on the executor/queue level. There is not necessarily a 1-to-1 mapping between a As for your request to not requite a group tag in order to pause/resume requests, I ended up going with a default group marker defined as |
|
@lucasr You are right, I had an error in reasoning. Your code looks good. Well done! |
|
@lucasr from where I can get jar file for this fix? I really need it ASAP. Thanks |
|
Build a jar from On Wed, Jun 18, 2014 at 6:07 AM, sockeqwe notifications@github.com wrote:
|
Hi,
as promised I have spent some hours to work on a solution for the scroll performance issue. The result looks promising.
Please note that I would like to discuss my solution and a some critical points with you before merging this pull request.
So what I have did:
DispatchingQueueand have integrated it into the already existingDispatcherclass. I have added two methods toDispatcher:void interruptDispatching()void continueDispatching()So when
Dispatcher.dispatchComplete(BitmapHunter)will be called the dispatcher will check if the dispatching has been interrupted before (by callinginterruptDispatching(). If no, the "complete event" will dispatched directly. If the dispatching has been interrupted, then dispatching the "complete event" will be added / enqueued to theDispatchingQueue(internally stored in a class calledDispatchJob). So after continuing dispatching (by callingDispatcher.continueDispatching()) all waitingDispatchJobswill be executed. The same workflow will run forDispatcher.dispatchFailed(BitmapHunter hunter).QUESTION 1: Is it correct to do this only for this two methods or should
Dispatcher.dispatchRetry()etc. run also with DispatchingQueue? I guess this two methods are enough right?The big advantage by doing this in the
Dispatcheris, that the BitmapHunter can do his work asynchronously and only the fading will be interrupted . Also setting placeholder or the image from memory cache works immediately.Dispatcher.performCancel(Action)while dispatching is interrupted (which means that its likely that there are DispatchJobs waiting in the DispatchingQueue) will also dequeue any waitingDispatchJobassociated with this Action (to be more precise: with the associated BitmapHunter).QUESTION 2: Is there another way a Action / BitmapHunter could be canceled that I have missed?
The
DispatchingQueuewill be cleaned up (clearing the queue) afterDispatcher.shutdown()has been called.DispatchingQueueTest)Picassoclass. So this two methods will be the public API for dispatching:Picasso.interruptDispatching(): calls Dispatcher.interruptDispatching()Picasso.continueDispatching(): calls call Dispatcher.continueDispatching()OnScrollListenerimplementations which can be found in the packagecom.squareup.picasso.scrollingPicassoScrollListener: interrupts / continues if and only if the scroll state is IDLE. So fling or simply scrolling will cause the dispatcher to be interrupted. I think that the most users who have gerneral performance issues while scrolling will also have performance issues while scrolling a few pixels (not flinging). Therefore I have decided that this one should be "the default" behaviourPicassoFlingScrollListener: like the name alredy suggests this OnScrollListener will interrupt while flinging (so normal scrolling will not interrupt dispatching).Last but not least there is one issue:
As you see in the code
DispatchingQueue.interruptDispatching()will simply increment a counter andDispatchingQueue.continueDispatching()will decrement the counter. Dispatching is enabled if tcounter == 0I did that because I think there could be more than one ListViews displayed at the same time (like in a master / detail view on a tablet, or viewpager , etc. ). So far so good, but there is one critical issue with this counter solution (you can reproduce it also in the samples: for instance GridView with Scrolling). If dispatching has interrupted, for example by starting scrolling (the counter will be incremented to 1) and let the finger on the screen and rotate your device to landscape the PicassoScrollLister will not receive the IDLE Scroll State and the counter will not be decremented --> No further dispatching ...QUESTION 3: Do you have any smart solution for this problem? My solution would be to use something like a
WeakHashSetand changing interrupt / continue methods to something like thisDispatcher.interruptDispatching( Object interruptRequestor )andDispatcher.continueDispatching( Object interruptRequestor ). The interruptRequest could be the PicassoScrollListener. So the idea is to add the interruptRequest to the WeakHashSet and if the weakHashSet is empty that dispatching is enabled. So this should fix the problem described above. However there is no "WeakHashSet structure" in java. We could use WeakHashMap<Object, Void> instead. However I can't see a way how to get notified if an entry has been removed from WeakHashMap, because after removing an entry we have to check if dispatching should be continued or not.Any suggestion or even smarter solution is welcome.
The sample apk file can be found here and the picasso library jar here