-
Notifications
You must be signed in to change notification settings - Fork 6.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preload memory cache from disk cache #978
Comments
Definitely available: by the name of For RecyclerView there's an adapter in v4: https://github.com/bumptech/glide/tree/master/integration/recyclerview/src/main/java/com/bumptech/glide/integration/recyclerview (#240) |
You're doing a great job with this beautiful library. Thanks for the links. |
Credit goes to @sjudd! Feel free to ask if you're stuck. |
Would you provide me a simpler example? Imagine I have a list of strings that path of images are in this list and on adapter of ListView i'm using glide to load images. (No thumbnails) |
Here's a fragment that has a list. The adapter is filled with data by generating some random-colored images. It gets a public class TestFragment extends android.support.v4.app.ListFragment {
private static final String URL_TEMPLATE = "http://placehold.it/300x200/%06x/000000&text=%d";
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
List<String> images = new ArrayList<>();
for (int i = 1; i < 100; ++i) {
images.add(String.format(Locale.ROOT, URL_TEMPLATE, i * 0x5f3759df % 0x1000000, i));
}
DrawableRequestBuilder<String> request = Glide
.with(this)
.fromString()
.fitCenter() // must be explicit, otherwise there's a conflict between
// into(ImageView) and into(Target) which may lead to cache misses
.listener(new LoggingListener<String, GlideDrawable>())
;
PreloadingAdapter adapter = new PreloadingAdapter(request, images);
setListAdapter(adapter);
getListView().setOnScrollListener(adapter.preload(3));
}
} The The following is a fully functioning adapter, you can see that the only a few extra things are needed to upgrade a normal adapter to a preloading one. class PreloadingAdapter extends BaseAdapter implements PreloadModelProvider<String> {
private final GenericRequestBuilder<String, ?, ?, ?> request;
private final ViewPreloadSizeProvider<String> sizeProvider = new ViewPreloadSizeProvider<>();
private final List<String> images;
public PreloadingAdapter(GenericRequestBuilder<String, ?, ?, ?> request, List<String> images) {
this.request = request;
this.images = images;
}
/// adapter methods
@Override public int getCount() {
return images.size();
}
@Override public String getItem(int position) {
return images.get(position);
}
@Override public long getItemId(int position) {
return images.get(position).hashCode();
}
@Override public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView = (ImageView)convertView;
if (imageView == null) {
imageView = new ImageView(parent.getContext());
imageView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 400));
sizeProvider.setView(imageView); // safe to call multiple times
}
request.load(getItem(position)).into(imageView);
return imageView;
}
/// preload methods
@Override public List<String> getPreloadItems(int position) {
return Collections.singletonList(getItem(position));
}
@Override public GenericRequestBuilder getPreloadRequestBuilder(String item) {
return request.load(item);
}
public OnScrollListener preload(int maxPreload) {
return new ListPreloader<>(this, sizeProvider, maxPreload);
}
} To adapt this to a RecyclerView you should be able to just wrap it in |
Thanks a lot. Your links were great. parentObject --> row of list Normaly I load images with Glide like this >> Glide.with(context).load(parentObject.childObject1.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView1);
Glide.with(context).load(parentObject.childObject2.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView2);
Glide.with(context).load(parentObject.childObject3.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView3); but now with your guidance I must load images with DrawableRequestBuilder like this: drawableRequestBuilder.load(parentObject.childObject1.imagePath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView1);
drawableRequestBuilder.load(parentObject.childObject2.imagePath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView2);
drawableRequestBuilder.load(parentObject.childObject3.imagePath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView3); problem is : now load gets ParentObject as type and i'm sending string. I don't know how should I approach this to load path string from object list with drawableRequestBuilder load. |
I don't exactly see any problem with the code you pointed out. I suggest you move all those repeated arguments to the initialization of To make the preloading work you just need to return the list of those 3 images from ParentObject parent = getItem(position));
return Arrays.asList(parent.child1.imagePath, parent.child2.imagePath, parent.child3.imagePath); The preloader and the adapter don't have to have the same generic types. If you register a model loader for If this doesn't help, please rephase your question. |
This is my adapter inside my MainActivity before implementing PreloadModelProvider public class BaseAdapterGallery3Item extends BaseAdapter {
Context context;
LayoutInflater layoutInflater;
BaseAdapterGallery3Item(Context context) {
this.context = context;
}
@Override
public int getItemViewType(int position) {
return mediaStoreFileRow3List.get(position).mediaStoreFile1.intMediaStoreFileViewType;
}
@Override
public int getViewTypeCount() {
return 10;
}
@Override
public int getCount() {
return mediaStoreFileRow3List.size();
}
@Override
public Object getItem(int position) {
return mediaStoreFileRow3List.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MediaStoreFileRow3 mediaStoreFileRow3 = mediaStoreFileRow3List.get(position);
ViewHolderListViewGallery holder;
if (convertView == null) {
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (getItemViewType(position) == INT_VIEW_TYPE_PHOTO) {
convertView = layoutInflater.inflate(R.layout.item_gallery_3, parent, false);
} else {
convertView = layoutInflater.inflate(R.layout.item_gallery_3_horizontal, parent, false);
}
holder = new ViewHolderListViewGallery(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolderListViewGallery) convertView.getTag();
}
if (mediaStoreFileRow3.mediaStoreFile1.intMediaStoreFileViewType == INT_VIEW_TYPE_PHOTO) {
holder.imageView1.setVisibility(View.VISIBLE);
holder.autoResizeTextView1.setVisibility(View.GONE);
Glide.with(context).load(mediaStoreFileRow3.mediaStoreFile1.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(holder.imageView1);
} else {
holder.imageView1.setVisibility(View.GONE);
holder.autoResizeTextView1.setVisibility(mediaStoreFileRow3.mediaStoreFile1.intVisibility);
holder.autoResizeTextView1.setBackgroundColor(mediaStoreFileRow3.mediaStoreFile1.intBackgroundColor);
holder.autoResizeTextView1.setText(mediaStoreFileRow3.mediaStoreFile1.stringDayMonthYearDateTaken);
}
if (mediaStoreFileRow3.mediaStoreFile2.intMediaStoreFileViewType == INT_VIEW_TYPE_PHOTO) {
holder.imageView2.setVisibility(View.VISIBLE);
holder.autoResizeTextView2.setVisibility(View.GONE);
Glide.with(context).load(mediaStoreFileRow3.mediaStoreFile2.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(holder.imageView2);
} else {
holder.imageView2.setVisibility(View.GONE);
holder.autoResizeTextView2.setVisibility(mediaStoreFileRow3.mediaStoreFile2.intVisibility);
holder.autoResizeTextView2.setBackgroundColor(mediaStoreFileRow3.mediaStoreFile2.intBackgroundColor);
holder.autoResizeTextView2.setText(mediaStoreFileRow3.mediaStoreFile2.stringDayMonthYearDateTaken);
}
if (mediaStoreFileRow3.mediaStoreFile3.intMediaStoreFileViewType == INT_VIEW_TYPE_PHOTO) {
holder.imageView3.setVisibility(View.VISIBLE);
holder.autoResizeTextView3.setVisibility(View.GONE);
Glide.with(context).load(mediaStoreFileRow3.mediaStoreFile3.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(holder.imageView3);
} else {
holder.imageView3.setVisibility(View.GONE);
holder.autoResizeTextView3.setVisibility(mediaStoreFileRow3.mediaStoreFile3.intVisibility);
holder.autoResizeTextView3.setBackgroundColor(mediaStoreFileRow3.mediaStoreFile3.intBackgroundColor);
holder.autoResizeTextView3.setText(mediaStoreFileRow3.mediaStoreFile3.stringDayMonthYearDateTaken);
}
return convertView;
}
} Now I'm trying to add preloader inside fields: DrawableRequestBuilder<String> drawableRequestBuilder; inside drawableRequestBuilder = Glide
.with(this)
.fromString()
.fitCenter();
listViewGallery.setAdapter(baseAdapterGallery3Item);
listViewGallery.setOnScrollListener(baseAdapterGallery3Item.preload(10)); and this is my new adapter: public class BaseAdapterGallery3Item extends BaseAdapter implements ListPreloader.PreloadModelProvider<String> {
Context context;
LayoutInflater layoutInflater;
private final ViewPreloadSizeProvider<String> viewPreloadSizeProvider = new ViewPreloadSizeProvider<>();
BaseAdapterGallery3Item(Context context) {
this.context = context;
}
@Override
public int getItemViewType(int position) {
return mediaStoreFileRow3List.get(position).mediaStoreFile1.intMediaStoreFileViewType;
}
@Override
public int getViewTypeCount() {
return 10;
}
@Override
public int getCount() {
return mediaStoreFileRow3List.size();
}
@Override
public Object getItem(int position) {
return mediaStoreFileRow3List.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final MediaStoreFileRow3 mediaStoreFileRow3 = mediaStoreFileRow3List.get(position);
ViewHolderListViewGallery viewHolderListViewGallery;
if (convertView == null) {
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (getItemViewType(position) == INT_VIEW_TYPE_PHOTO) {
convertView = layoutInflater.inflate(R.layout.item_gallery_3, parent, false);
} else {
convertView = layoutInflater.inflate(R.layout.item_gallery_3_horizontal, parent, false);
}
// convertView.getLayoutParams().height = convertDpToPx(context, 30);
viewHolderListViewGallery = new ViewHolderListViewGallery(convertView);
convertView.setTag(viewHolderListViewGallery);
viewPreloadSizeProvider.setView(convertView.findViewById(R.id.image_view_1));
} else {
viewHolderListViewGallery = (ViewHolderListViewGallery) convertView.getTag();
}
if (mediaStoreFileRow3.mediaStoreFile1.intMediaStoreFileViewType == INT_VIEW_TYPE_PHOTO) {
viewHolderListViewGallery.imageView1.setVisibility(View.VISIBLE);
viewHolderListViewGallery.autoResizeTextView1.setVisibility(View.GONE);
// Glide.with(context).load(mediaStoreFileRow3.mediaStoreFile1.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView1);
drawableRequestBuilder.load(mediaStoreFileRow3.mediaStoreFile1.stringMediaDataPath).dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView1);
} else {
viewHolderListViewGallery.imageView1.setVisibility(View.GONE);
viewHolderListViewGallery.autoResizeTextView1.setVisibility(mediaStoreFileRow3.mediaStoreFile1.intVisibility);
viewHolderListViewGallery.autoResizeTextView1.setBackgroundColor(mediaStoreFileRow3.mediaStoreFile1.intBackgroundColor);
viewHolderListViewGallery.autoResizeTextView1.setText(mediaStoreFileRow3.mediaStoreFile1.stringDayMonthYearDateTaken);
}
if (mediaStoreFileRow3.mediaStoreFile2.intMediaStoreFileViewType == INT_VIEW_TYPE_PHOTO) {
viewHolderListViewGallery.imageView2.setVisibility(View.VISIBLE);
viewHolderListViewGallery.autoResizeTextView2.setVisibility(View.GONE);
// Glide.with(context).load(mediaStoreFileRow3.mediaStoreFile2.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView2);
drawableRequestBuilder.load(mediaStoreFileRow3.mediaStoreFile2.stringMediaDataPath).dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView2);
} else {
viewHolderListViewGallery.imageView2.setVisibility(View.GONE);
viewHolderListViewGallery.autoResizeTextView2.setVisibility(mediaStoreFileRow3.mediaStoreFile2.intVisibility);
viewHolderListViewGallery.autoResizeTextView2.setBackgroundColor(mediaStoreFileRow3.mediaStoreFile2.intBackgroundColor);
viewHolderListViewGallery.autoResizeTextView2.setText(mediaStoreFileRow3.mediaStoreFile2.stringDayMonthYearDateTaken);
}
if (mediaStoreFileRow3.mediaStoreFile3.intMediaStoreFileViewType == INT_VIEW_TYPE_PHOTO) {
viewHolderListViewGallery.imageView3.setVisibility(View.VISIBLE);
viewHolderListViewGallery.autoResizeTextView3.setVisibility(View.GONE);
// Glide.with(context).load(mediaStoreFileRow3.mediaStoreFile3.stringMediaDataPath).asBitmap().dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView3);
drawableRequestBuilder.load(mediaStoreFileRow3.mediaStoreFile3.stringMediaDataPath).dontAnimate().centerCrop().into(viewHolderListViewGallery.imageView3);
} else {
viewHolderListViewGallery.imageView3.setVisibility(View.GONE);
viewHolderListViewGallery.autoResizeTextView3.setVisibility(mediaStoreFileRow3.mediaStoreFile3.intVisibility);
viewHolderListViewGallery.autoResizeTextView3.setBackgroundColor(mediaStoreFileRow3.mediaStoreFile3.intBackgroundColor);
viewHolderListViewGallery.autoResizeTextView3.setText(mediaStoreFileRow3.mediaStoreFile3.stringDayMonthYearDateTaken);
}
return convertView;
}
@Override
public List<String> getPreloadItems(int position) {
return Arrays.asList(mediaStoreFileRow3List.get(position).mediaStoreFile1.stringMediaDataPath, mediaStoreFileRow3List.get(position).mediaStoreFile2.stringMediaDataPath, mediaStoreFileRow3List.get(position).mediaStoreFile3.stringMediaDataPath);
}
@Override
public GenericRequestBuilder getPreloadRequestBuilder(String item) {
return drawableRequestBuilder.load(item);
}
public AbsListView.OnScrollListener preload(int maxPreload) {
return new ListPreloader<>(this, viewPreloadSizeProvider, maxPreload);
}
} Now you can see in provided video only some of the images are preloaded into cache memory when I scroll fast. I don't know where is my mistake on implementing preloader. I've set preload number to 50 or 100 and no result out of it and app crashed by number around 200. On number 10 at least I saw some of images preloaded. lower numbers not any significant result. I'm using "AZ screen recorder" and while recording this video number of preloaded images differs only in two or three but it explains some expensive behaviour is behind this latency while mentioned softwares are very smooth and fast even on super fast scrolling. |
You seem to be emulating a GridView with this adapter, why not just use one? :) There are a few potential issues above:
@sjudd do you have anything that I missed? I think you may have more experience with (pre-)loading image-heavy lists/grids. |
WoW. Now I see why this library is so much powerful, you and sjudd are behind it. |
drawableRequestBuilder = Glide
.with(this)
.fromString()
.override(200, 200) // Example for testing the result and it worked OK
.dontAnimate() // Items still animate but is not my consideration for now
.centerCrop() // OK until now no change in result
I don't know how to but this is what I found and created this class but how should I use it?>> public class FixedPreloadSizeProvider<T> implements ListPreloader.PreloadSizeProvider<T> {
private final int[] size;
/**
* Create a new PreloadSizeProvider with a fixed size.
* @param width The width of the preload size
* @param height The height of the preload size
*/
public FixedPreloadSizeProvider(int width, int height) {
this.size = new int[]{width, height};
}
@Override
public int[] getPreloadSize(T item, int adapterPosition, int perItemPosition) {
return Arrays.copyOf(this.size, this.size.length);
}
} |
Assign an instance of it to |
Would you provide me the code for it, otherwise I couldn't get it. |
Diff for "my new adapter" above: -private final ViewPreloadSizeProvider<String> viewPreloadSizeProvider = new ViewPreloadSizeProvider<>();
+private final ListPreloader.PreloadSizeProvider<String> preloadSizeProvider = new FixedPreloadSizeProvider<>(200, 200);
-viewPreloadSizeProvider.setView(convertView.findViewById(R.id.image_view_1));
-return new ListPreloader<>(this, viewPreloadSizeProvider, maxPreload);
+return new ListPreloader<>(this, preloadSizeProvider, maxPreload); |
Now it works perfectly and performance is smooth and good but needs some more changes as you suggested.
Did this and there was not any noticeable changes in ListView's behaviour. But it was very nice that you mentioned this behaviour of Glide.
At first I was on RecyclerView with GridLayoutManager and after some performance problems tried LinearLayout manager and loading each item with many views(to prevent expensive layout in layout) like what I do now with ListView and then saw some fast and smooth image gallery apps that are using ListView instead of RecyclerVIew and that ignited my curiosity because RecyclerView is newer and more suggested by professionals. So I decided to at least try once and result was very convincing that something is wrong whether with RecyclerView behaviour or how I implemented it in my project and there was no change in result no matter how I tried different approaches, no success. But I have another question about the way you made use of the class private final ListPreloader.PreloadSizeProvider<String> preloadSizeProvider = new FixedPreloadSizeProvider<>(200, 200); What your approach is called to know more about and Why can't we just call >> private final FixedPreloadSizeProvider<String> preloadSizeProvider = new FixedPreloadSizeProvider<>(200, 200); |
Glad to hear you got to a point of "good enough". Good luck with future optimizations. Thanks for the details about Grid. I didn't notice any particular performance benefit between List and Recycler, the only thing that's different is that Recycler supports much more stuff: viewholder, animation, range updates; it's just a better API. The core of them should be very similar though. I use Recycler throughout my apps and not found any issues. Feel free to mail me if you want me to look at some code (this conversation is fairly out of scope for Glide issues already).
You could do that, but it's much more flexible that way. I actually don't know what it's called, but one of the first things that I learned for OO programming is to use the least specific interface that satisfies your needs. Maybe try looking for "polymorphism" or "Liskov Substitution Principle". The implementation doesn't matter as long as it provides the behavior defined by the interface (or base class). For example: |
Yes and now I'm trying to prevent Glide to play gif animations and the only solution I found is
What is TranscodeType I couldn't find any example about. Thanks |
https://docs.google.com/drawings/d/1KyOJkNd5Dlm8_awZpftzW7KtqgNR6GURvuF6RfB210g/edit?usp=sharing TranscodeType is the last R accepted by Targets. In a DrawableRequestBuilder for most loads Bitmap is the 3rd type and then it's wrapped in a drawable. BitmapRequestBuilder is a little degenerate because it has fixed Bitmap representation, but it's transcoded into a Bitmap as well. |
So I should set a drawble as a second parameter, but as always without example code it's hard to get what should I do. |
In my case So I changet to this
But I've got error on
compiler asks me to change Drawable to Bitmap. |
Try to use the IDE's "Extract variable" refactoring to figure out a type of an expression, just select the expression and execute the refactoring. Nomen est omen:
The first generic argument to both is the type of the model, that is the type of the object passed to |
|
Ok. Now I have my own listener that should be added to the ListView while preload listener is assigned to ListView. Problem is when I use a list for MultiScrollListener and add two listener inside that list, performance of ListView (laggy scrolling) decreases drastically. |
This is my multi scroll listener
} |
Two things you should look into:
private AbsListView.OnScrollListener[] mListeners = new AbsListView.OnScrollListener[0];
public void addScrollListener(AbsListView.OnScrollListener listener) {
AbsListView.OnScrollListener[] newListeners = Arrays.copyOf(mListeners, mListeners.length + 1);
newListeners[newListeners.length - 1] = listener; // add last (that place is null in copy)
mListeners = newListeners; // commit new list
} (you can also probably get away with not implementing remove; even if not it should be easy to do) You may get away with |
Thank you.
Yes. I checked it out and already did some optimizations on my listener, and if I set preload or my listener alone they are are fine. |
It depends... not necessarily worth it, just look at that |
You could also specialize your Multi-listener to Dual with two fields (a,b) and implement methods like: void f(...) {
a.f(...);
b.f(...);
} it's hard to beat that performance-wise :) Though this only works if you don't need the dynamic add/remove. |
Thanks a lot. I'll try it. |
Hey @mtst99 it was great reading i hope you must have finished this things! but i came across similar thing and i am stucked in Recyclerview so can you please update here with your final code? Thanks! |
Hi @ndeokar . I ended up manage memory on my own and not using third libraries. Much simpler and faster as these libraries meant to support variety of behaviours while I only needed some parts of this library. I didn't probe library with details but I got what I think is logical and of course more smooth. :) |
@mtst99 it would be interesting to see how you got better performance, maybe we could incorporate it into Glide. Do you think requests and async loads so big overhead? |
@TWiStErRob sure. I'll share my code as soon as I finish all the levels of preloading and caching for different amount of images in each row 3, 5 and 10. |
@mtst99 That would be great! bdw i must say @TWiStErRob Glide is really powerful ! Thank you for such big help! |
It is interesting to see how some applications like A+Gallery or QuickPic show images loaded into the disk cache very fast and smooth.
Imagine I have 1000 images.
They are shown inside a ListView or RecyclerView.
About 20 to 50 images gonna appear on the screen.
On scroll I get to the images that are not inside memory cache anymore because memory cache is now filled with scrolled images.
So memory cache is limited and it could be customized but carefully which is not my concern now.
What I noticed about A+Gallery and QuickPic is that they show images like they are available inside memory cache which is impossible by the size of provided memory cache that these days' devices provide for each application.
So It left me with this idea that these apps loading memory cache from disk cache as soon as ListView or RecyclerView or GridView gets close to the end of the available bitmaps inside memory cache.
Glide loads new images from disk cache if they are not in memory cache, which will happen if users scroll on many images in one direction.
If explained feature is already implemented into the Glide, please help me to know how to load Glide's memory cache from its disk cache to prevent fast scrolling show empty items or placeholders.
And if Glide doesn't have such a feature yet I would like to know your idea about requested feature is it doable or I'm wrong about mentioned apps. Thank you
The text was updated successfully, but these errors were encountered: