Description
I have the following use case:
I have a RecyclerView
and each item contains an ImageView
. To determine which image goes into an item I first have to make a HTTP request, fetching the url to load. When I have the url I can start the Glide load.
As the user is scrolling, the HTTP request is made when the ViewHolder
gets bound. The simplest scenario is that the ViewHolder
is still visible when the request finishes enabling me to use a simple Glide.with(...).load(...).into(...)
.
The other case is where the user is scolling fast and when the request finishes, the ViewHolder
is already bound to new data. In that case I want to only download the image to the disk cache so it is available when the user visits this position in the dataset the next time.
I'm confused which method to use fot that, either downloadOnly
or preload
seem applicable. From the javadocs I can't really tell the difference in behaviour.
I want to load and save the source image in its original size. Glide.with(...).load(...).diskCacheStrategy(DiskCacheStrategy.SOURCE).preload()
is what I have been using until now. But this post suggests that preload
loads the image into memory cache while downloadOnly
loads it into disk cache. I don't need the image to immediately be in memory cache, so downloadOnly
seems the right choice accordingly. But then I have to specify a size and I don't know how big my source image is going to be beforehand.
Any help to clear up my confusion is highly appreciated.
Activity
TWiStErRob commentedon Aug 5, 2016
The post is correct. The trick is that the w/h for downloadOnly is not user for resizing the image but for bucketing the url (select the closest size on the server side). If you track the flow of those arguments you'll see. Use
Target.SIZE_ORIGINAL
as it conveys your intention best. Using preload is tricky, that helps with making the list scroll as if it had an infinite memory cache (see ListPreloader class).Take a look at #634 (comment) and the conversation after to see how you may be able to bend Glide to your benefit. Note that this method may be preferable to firing downloadOnly requests blindly. Notice that if you follow the logic in your opening comment and the user scrolls a lot, all the items will be queued up for download.
Note: you have to enable SOURCE caching to pick up downloadOnly'd version of the file. For this you have to add
diskCacheStrategy(SOURCE or ALL)
to your thumbnail load in bind.BobOtike commentedon Aug 5, 2016
So if I use
downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
the load would do what I want it to do?I have taken a lool at the comment you mentioned. As I see it this is only a fancier way to incorporate my HTTP request into the Glide load so I don't have to do it explicitly before the Glide load or is there some other advantage to it?
My reasoning for the 'blind firing" is the following: although the images are not predetermined from the beginning, once I have an url it stays valid throughout the app's usage by one user, i.e. different users may get different images, but once the images for one user are determined, they do not change anymore. I also assume that a user will view every item that is in the RecyclerView eventually.
According to these facts/assumptions I can state about the usage, it seems, to me at least, legitimate to download every image I have reveived a url for.
TWiStErRob commentedon Aug 5, 2016
Yes, it'll simply populate the SOURCE cache.
Yes you get automatic caching, request merging and cancellation for free.
The request merging happens when you load the same model twice (only one HTTP stream will be opened). The cancellation will happen when you start a new load into the same view. This is useful not to queue up to download "the world" while the user is waiting for an actual image to show up. A simple fling could clog the image queue. Caching is also based on the original model and not the actual url, so if you need the same thing in different parts of the app, there's no need to implement/use the url retrieval, you just load the model and Glide gives you an image.
Then fire up a background Service, and call downloadOnly for each of them one by one, with lower priority. This will make sure you "download the world", and that the app is still unable in the foreground. Obviously, since you're pre-populating the cache, any image that is shown won't be downloaded again, and vice versa.
BobOtike commentedon Aug 5, 2016
Okay, then I will have a look at
downloadOnly
. One thing I already noticed that it lacks the ability to attach a listener which I could do withpreload
, or am I missing something?I see how that can simplify things, alas I didn't know about that approach until now.
Just some thoughts on the matter and my current implementation:
If my model is the string representation of the url I retrieved then I can still get automatic caching and request merging if I use that same string more than once. For the retrieval of said url I have implemented a singleton which also does request merging for the HTTP requests. So I still can benefit from these two factors I think.
I see how you think that a fling could clog the image queue, but for simplicity's sake I had previously left out some details of my implementation. Any image should be downloaded after the url is available, but I don't do that immediately. Because I have to respect some rate limits for the HTTP requests they are also only being queued when the ViewHolder is bound. The queue is being emptied by a looper processing each request one after another. The looper processes one request per second, so while there are requests, a Glide load will be initiated only once per second. Furthermore the queue has a concept of priority so the images visible to the user are always being loaded first and issuing the same request again only increases its priority.
If I had known from the beginning I could put everything in the Glide load I probably might have done it, but because of certain constraints and the complexity of my current approach I am not sure I will change it again. If I do at some point, I will definitely keep your advice in mind!
Going with the service might also have been an option, but with my current approach it is also being factored in when the user scrolls, i.e. loading what is visible first and not only starting at 0 and then stepping through one by one.
Thanks a lot for your suggestions though, maybe they come in handy for any future project.
TWiStErRob commentedon Aug 5, 2016
I think
downloadOnly
was designed to be used as fire-and-forget, or in the background, hence it returnsFutureTarget<File>
. In this case (background) you wouldn't really have async requests, just block onFuture.get()
. This is where a background service would come in handy. What do you want to do in the listener?Well, that sounds like you re-implemented a lot of Glide's features. Glide is really good for acquiring data in any format, and by that I mean from any source, in any format loaded as any object. You can write a lot of negative code if you move to Glide ;)
That sounds like how
ListPreloader
works. Even if the background service is downloading, the user could still scroll and the images would be loaded and cached. When the background service reaches those images, it will just skip them because of the cache hit.In the end it's your choice. If you need more help later feel free to open new issues. I'll close this one as I think it's quite clear now what the difference is between the two methods in the title:
downloadOnly
fills theSOURCE
cache by downloading the data, andpreload
fills theRESULT
and memory cache, by decoding and transforming the input and then saving it. Usingpreload
to fill just theSOURCE
cache is wasteful as it decodes the image.quiKsilverItaly commentedon Mar 25, 2018
This thread was really helpful. I still have one little question.
What happens, if I already preloaded an image and give the exactly same url to Glide again? Does it get preloaded again or does Glide simply do nothing?
My goal is, that it simply does nothing, if the image from the url already got downloaded.
Glide 3:
sjudd commentedon Mar 27, 2018
@quiKsilverItaly if you just run the line you gave above it will not download the image a second time.