Skip to content

Difference between downloadOnly() and preload()  #1391

Closed
@BobOtike

Description

@BobOtike

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

TWiStErRob commented on Aug 5, 2016

@TWiStErRob
Collaborator

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

BobOtike commented on Aug 5, 2016

@BobOtike
Author

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

TWiStErRob commented on Aug 5, 2016

@TWiStErRob
Collaborator

would do what I want it to do?

Yes, it'll simply populate the SOURCE cache.

this is only a fancier way [...] is there some other advantage to it?

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.

will view every item that is in the RecyclerView eventually.

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

BobOtike commented on Aug 5, 2016

@BobOtike
Author

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 with preload, or am I missing something?

Yes you get automatic caching, request merging and cancellation for free.

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

TWiStErRob commented on Aug 5, 2016

@TWiStErRob
Collaborator

or am I missing something?

I think downloadOnly was designed to be used as fire-and-forget, or in the background, hence it returns FutureTarget<File>. In this case (background) you wouldn't really have async requests, just block on Future.get(). This is where a background service would come in handy. What do you want to do in the listener?

[... long description of your impl ...]

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 ;)

loading what is visible first and not only starting at 0 and then stepping through one by one.

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 the SOURCE cache by downloading the data, and preload fills the RESULT and memory cache, by decoding and transforming the input and then saving it. Using preload to fill just the SOURCE cache is wasteful as it decodes the image.

quiKsilverItaly

quiKsilverItaly commented on Mar 25, 2018

@quiKsilverItaly

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:

                Glide.with(this)
                        .load("url")
                        .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
sjudd

sjudd commented on Mar 27, 2018

@sjudd
Collaborator

@quiKsilverItaly if you just run the line you gave above it will not download the image a second time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sjudd@TWiStErRob@quiKsilverItaly@BobOtike

        Issue actions

          Difference between downloadOnly() and preload() · Issue #1391 · bumptech/glide