Efficient way to load large number of same image in ReyclerView - android

I developed a chat app. It uses RecyclerView to show chat info. It's exactly like facebook messenger.
My problem is, loading the image of user I'm chatting to takes time, and make the app slow when there's large number of chat messages.
I'm using picasso to load image.
Picasso.get()
.load("https://domain/images/profile_picture/" + otherImage)
.networkPolicy(NetworkPolicy.OFFLINE)
.transform(new CircleTransform())
.into(imageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError(Exception e) {
int placeHo;
if(otherGender.equals("female"))
placeHo = R.drawable.ic_female_color;
else
placeHo = R.drawable.ic_male_color;
Picasso.get()
.load("https://domain/images/profile_picture/" + otherImage)
.error(placeHo)
.transform(new CircleTransform())
.centerCrop(Gravity.TOP)
.into(imageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError(Exception e) {
Log.v("Picasso","Could not fetch image");
}
});
}
});
Is there any efficient way to show the image? as it's showing the same image(user profile picture).

I can think only one reason that your RecyclerView is lagging. Because your image size is large.
See, if your chat image is about 50-100dp then you should use the same resolution image. Perhaps you are loading original image.
AFAIK I use Glide over Picasso because Glide optimize the downloaded image, as ImageView size.
See Glide doc
Glide's primary focus is on making scrolling any kind of a list of
images as smooth and fast as possible, but Glide is also effective for
almost any case where you need to fetch, resize, and display a remote
image.
You don't need to worry about cache in Picasso and Glide both. Cache is enabled by default in Picasso, so if same image is queried again, then it will be picked from cache.
Solution 1 (using Piccaso)
Resize image as much you need.
Picasso
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.resize(100, 100) // resizes the image to these dimensions (in pixel). does not respect aspect ratio
.into(imageViewResize);
Solution 2 (Using Glide)
You need not to worry about anything, you are good to go if you use Glide.

keep it Static
Or You can set it at the time of initialisation
not changing it every time on viewholder
sorry for my english and format of writing

If you are using the same image in all the Views of the same viewType then load the image in onCreateViewHolder method of the adapter and not in onBindViewHolder.
The same View with the image already loaded will be recycled (reused) and not loaded again and again, the result will be for sure faster.

You have to postpone the first image retrieve procedure at when the RecyclerView.OnChildAttachStateChangeListener() event signals that a child is added. Obliviously the Event is triggered for each of firsts visible items. The most correct way to postpone something like this is to create an Handler and then send/enqueue a Handler.sendMessage(Message.obtain(..)) one for each visible objects) items. Inside the Handler you will receive that Message and there you can execute the Picasso action.
Then you have to decide if load other (the not yet visible items) images all at once or just prepare the picture only when it is required by the Recycler.

Related

Why doesn't Picasso display full images?

I have an android app that uses Picasso to load images. Problem is that it doesn't load the image the first time. It just gives me an empty fragment. It loads it fine after I return back to my thumbnail activity and tap the same image again.
Picasso
Picasso.with(
context)
.load('url')
.into(mImage);
It is also worth mentioning that it has no problem loading the full image (frame) of a video file that is part of my album collection. That one I tap on it and it loads the image no problem. I don't know why.
Now, I did some research and looked around a few forums where people seemed to be having my same problem and they recommend Glide. Promptly I set it up in my project and give it a try and my images work, I get full size images the first time I select a thumbnail.
Glide:
Glide.with(context)
.asBitmap()
.load('url')
.into(new BitmapImageViewTarget(mImage)
{
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition)
{
super.onResourceReady(resource, transition);
mImage.setImageBitmap(resource);
mImage.setZoom(1);
}
});
My question is: why did Glide provided me with a better result than picasso did? What was I missing to make Picasso work?
well , i guess nothing wrong with Picasso , i think it's just some delay caused by internet connection .
Glide and Picasso are technically the same , and you should use the last version of them next time
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
and also , since we are talking about images , you have to know that both Picasso and Glide have issues with loading some https url and for that i advice you to use Universal Image Loader

Display multiple image with Glide

I want to display one image into two different image views using glide.I know that i can simply use the code below to do it.
ImageView imageView = findViewById(R.id.header_image);
Glide.with(this).load(R.drawable.header_image).into(imageView);
ImageView imageView2 = findViewById(R.id.header_image);
Glide.with(this).load(R.drawable.header_image).into(imageView2);
But it requires to load the same image twice into memory and i don't want that due to memory issues.I want to load the image once and diaplay it into two image views.What can i do to achieve it?
You no need to worry about memory issue while using glide because glide has own caching system to optimize memory pls read this doc
Glide provides a number of options that allow you to choose how loads will interact with Glide’s caches on a per request basis.
Disk Cache Strategies
DiskCacheStrategy can be applied with the diskCacheStrategy method to an individual request. The available strategies allow you to prevent your load from using or writing to the disk cache or choose to cache only the unmodified original data backing your load, only the transformed thumbnail produced by your load, or both.
The default strategy, AUTOMATIC, tries to use the optimal strategy for local and remote images. AUTOMATIC will store only the unmodified data backing your load when you’re loading remote data (like from URLs) because downloading remote data is expensive compared to resizing data already on disk. For local data AUTOMATIC will store the transformed thumbnail only because retrieving the original data is cheap if you need to generate a second thumbnail size or type.
To apply a DiskCacheStrategy:
GlideApp.with(fragment)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
Loading only from cache
In some circumstances you may want a load to fail if an image is not already in cache. To do so, you can use the onlyRetrieveFromCache method on a per request basis:
GlideApp.with(fragment)
.load(url)
.onlyRetrieveFromCache(true)
.into(imageView);
If the image is found in the memory cache or in the disk cache, it will be loaded. Otherwise, if this option is set to true, the load will fail.
You can use following code to load an image once and display it in multiple imageviews.
Glide.with(this)
.asBitmap()
.load(R.drawable.header_image)
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
imageview.setImageBitmap(resource);
imageview2.setImageBitmap(resource);
}
});
If you are using latest version of Glide then create RequestOptions like
private RequestOptions simpleOptions = new RequestOptions()
.centerCrop()
.placeholder(R.color.color_gray)
.error(R.color.color_gray)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
Use RequestOptions to load image with set properties
Glide.with(context)
.load(url)
.apply(simpleOptions)
.into(imageView);
Or maybe this way…
RequestBuilder<Drawable> builder = Glide.with(this).load(drawableResId);
builder.into(imageView0)
builder.into(imageView1)
try this way ...
setImage(R.drawable.header_image,imageView ,imageView2 );
void setImage(int image, ImageView... imageView) {
for (ImageView imageView : imageView) {
if (imageView != null) {
Glide.with(this).load(image).into(imageView);
}
}
}

Long display time for pictures using Glide

I am doing an API request to Imgur using Retrofit in order to retrieve about 40 URLs and then display them in a RecyclerView using Glide like such:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Glide
.with(context)
.load(list.get(position).getLink()) // Gets the URL in my list
.centerCrop()
.placeholder(R.mipmap.placeholder)
.crossFade()
.into(holder.imageView);
}
The request gets answered quickly, but most of my images stay as placeholders as some of them appear one by one later on.
What is causing this delay in the display of my images? Is it perhaps linked to the speed of my Internet connection?
Additionally is my approach a correct one when it comes to "large" amounts of pictures?
Please note that most ImageViews do not load, even the ones that are visible to my user.
This might be depending on your internet connection. If so, you can use imgur smaller sizes, you can add one of those letter at the end of your filename:
s = Small Square (90×90)
b = Big Square (160×160)
t = Small Thumbnail (160×160)
m = Medium Thumbnail (320×320)
l = Large Thumbnail (640×640)
h = Huge Thumbnail (1024×1024)
i.e. this:
http://i.imgur.com/bF8zPs4.jpg
becomes this (for the small square):
http://i.imgur.com/bF8zPs4s.jpg
Once you've set the url for the image in Glide, the image itself still needs to be downloaded and shown in the ImageView, which is causing the delay. If the images are not loading, can you check if the image url loads the image in your browser for example?
A better approach to load images in an adapter with Glide is to use a RequestManager which you pass in the constructor of your adapter. Glide will then subscribe to the lifecycle of your activity of fragment. This will prevent images from being downloaded when your activity of fragment has been destroyed, which is especially useful when Glide has to download large images.
public MyAdapter(List<Object> items, RequestManager requestManager) {
this.items = items;
this.requestManager = requestManager;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
requestManager
.load(list.get(position).getLink()) // Gets the URL in my list
.centerCrop()
.placeholder(R.mipmap.placeholder)
.crossFade()
.into(holder.imageView);
}
And then you can call the constructor of your adapter in an acitivty or a fragment as such:
MyAdapter myAdapter = new MyAdapter(items, Glide.with(this));
I've been using this approach after I've found this answer from TWiStErRob.
it is depend on your internet connection speed and the size of images.
if the size of images it to large,it may cause of of Memory exception.
In Recyclerview, Glide only loads images in visible imageViews. ImageViews that are not visible will be loaded once you scrolldown. This is due to recycler property of RecyclerView.
Add following line in you onBindViewHolder and see the logs, you will understand it:
Log.w("Link", list.get(position).getLink());

Android: Downloading 1k+ images using Glide

I'm currently trying to create an image "player" to show a person's drive. The drives are recorded and stored as images, split as 1 image per second.
I'm pulling the images using Glide. However, as you can imagine, there are a lot of images - thousands of them. My first idea was to download the images using the downloadOnly option of Glide, and use a Discrete Seekbar, and as the images are getting downloaded, then increase the max of the Seekbar. The problem with the approach, is that it takes a long time.
This is what I currently have to download the images:
private void downloadOnlyImages(List<String> URLs){
totalImages = 0;
for(final String url: URLs){
Glide.with(this)
.load(url)
.downloadOnly(new SimpleTarget<File>() {
#Override
public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
Log.i("glide", "image downloaded" + url);
mySeekBar.setMax(totalImages++);
}
});
}
}
and this is what I'm currently using with the seekbar:
mySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Glide.with(getActivity())
.load(URLs.get(progress))
.fitCenter()
.dontAnimate()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
}
The idea was, download the images, and then use the seekbar to move through them, always replacing the same ImageView with the next image. Using Glide's cache, I'm trying to check if it's in cache then pull that image if not then fetch using the URL. But, this hold up the UI Thread, which is causing the delay.
TLDR: I want to be able to be able to use a seekbar as a "player" for thousand of images that I'm pulling using Glide. However, it's very laggy using my naive approach. Trying to think of a more effective and efficient way to pull the images, in a more "lazy loading" way.

Picasso Caching Doesn't Appear to be working

I am using Picasso to handle image loading and caching in my Android Udacity project and I am noticing the caching is not working as I'd expect:
As you can see on the left fragment, the image has already loaded in an earlier thread. Now, with the same URL link, I am asking Picasso to place that image in the fragment on the right.
Here is the code which generates the grid view on the left fragment (and occurs first):
https://github.com/esend7881/udacity-android-popmovie/blob/a9a1b9a19a37594bb5edd736b7ec59229fb5905a/app/src/main/java/com/ericsender/android_nanodegree/popmovie/adapters/GridViewAdapter.java#L71
String load = String.format(sImgUrl, sImgSize, movie.poster_path);
Picasso.with(mContext.getApplicationContext())
.load(load)
.placeholder(R.drawable.abc_btn_rating_star_on_mtrl_alpha)
.error(R.drawable.abc_btn_rating_star_off_mtrl_alpha)
.resize(550, 775)
.into(viewHolder.imageView);
And then here is the code which runs in the right fragment:
https://github.com/esend7881/udacity-android-popmovie/blob/a9a1b9a19a37594bb5edd736b7ec59229fb5905a/app/src/main/java/com/ericsender/android_nanodegree/popmovie/fragments/MovieDetailsFragment.java#L308
Picasso.with(getActivity().getApplicationContext())
.load(String.format(sImgUrl, sImgSize, mMovieObj.poster_path))
.error(R.drawable.blank)
.fit()// .resize(366, 516)
.into(mMovieThumb, new com.squareup.picasso.Callback() {
#Override
public void onSuccess() {
Utils.log(sw.toString());
Utils.hideViewSafe(mMovieThumbProgress);
}
#Override
public void onError() {
Utils.log(sw.toString());
Utils.hideViewSafe(mMovieThumbProgress);
}
});
I am using the same application context in each as well as the load text:
String.format(sImgUrl, sImgSize, mMovieObj.poster_path))
and
getActivity().getApplicationContext()
So, I would think Picasso ought to detect when the exact same URL load link appears in the same context within a short period of time from each other and Picasso would then load the exact same image back into the app.
If this is not how Picasso caching works, then how does it?
As a comment mentioned, I'd guess this is affected by the size of the image being different in both fragments.
I'd recommend using https://github.com/facebook/fresco instead of picasso. It's more efficient, especially with different sizes. You can also directly access cached files if required https://github.com/facebook/fresco/issues/80
It's probably related to the HTTP headers received when getting the image that do not allow caching, as Picasso relies on an HTTP component to do the caching.
Try uploading your image on imgur, try hardcoding that path and see if it works. If that's the case, you'll have to find a workaround on how to get the image from the movie database.

Categories

Resources