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.
Related
I'm using Fresco library to display images in my Android app. I'd like to display some images (jpg or png) that I have set with public grants.
When I was doing quick tests, I just took any image from internet to set a URL, but when using the real ones that I need to use, I have the following url https://drive.google.com/uc?export=view&id=<>, but as it is a redirect and, once redirected, new url is not the image itself, Fresco is unable to display it.
I have tried Picasso as an alternative library, but with out any success.
I have also tried the download url for both libraries (https://drive.google.com/uc?export=download&id=<>). But no result.
Anybody knows how could it be possible to get this images? Or the only solution is to download it (using the second url) processing the object received store a bitmap of it and displaying it?
For downloading it, what should i use and how? retrofit?
Thanks in advance.
Fresco supports different network stacks. For example, you can use OkHttp with Fresco, which should follow redirects or modify the default one to allow redirects - or write your own based on them.
Guide for OkHttp: http://frescolib.org/docs/using-other-network-layers.html
Related GitHub issue: https://github.com/facebook/fresco/issues/61
I found a solution for this problem (but could be only applicable if you use Google Cloud or Google Script).
It consists on creating a doGet() service with the following code inside:
var file = DriveApp.getFileById(fileId)
return Utilities.base64Encode(file.getBlob().getBytes());
and use that base64 value in your app. With this format, Fresco can do the magic
It is not an immediate solution, and requires to do somework in other platform that is not your Android app, but it works perfectly.
Are you sure that there is no problems with your URLs?
Picasso works with direct URLs like: https://kudago.com/media/images/place/06/66/06662fda6309ce1ee9116d13bd1c66d5.jpg
Then you can download your image like:
Picasso.with(this)
.load(url)
.noFade()
.placeholder(R.drawable.placeholder_grey) //if you want to use a stub
.into(imageView, new com.squareup.picasso.Callback() {
#Override
public void onSuccess() {
//here you can operate with image after it is downloaded
}
#Override
public void onError() {
}
});
Hope it will help you.
I've been reading quite a lot on the topic, still not quite clear though. At the moment I'm creating an app, loading an image and a text on one screen. For loading the images I opted for Glide, but where is the most appropriate place to read them from? All of the tutorials I passed pass the image's URL. Isn't it slower when loaded from the net? Thanks a lot!
If you care for apk size then do not put these images static. Instead you can keep these images on server(your or free server) and easily load those images using libraries like Glide or Picasso.
Isn't it slower when loaded from the net?
No. It will download image once and then cache it for future use. So it's very fast.
If you think apk size will doesn't matter for you and user should not face problem due to unavailability of internet then you can keep those images static inside app iteself.
If you want to build an app that uses dynamic images or you want to update your images without updating your application, getting them from the server is better. And in my opition picasso is easy to use and straightforward. Also uses it's own framework caching. But if you think that your images wont change, put them in an asset folder so that they are in app's internal memory. Getting them from the server has it's downsides like you need to use a placeholder images because they won't be retrieved immediately.
You must use caching mechanisms if you want the images always from network. The system I follow is like this: (PS. I use Picasso, fast and reliable):
Picasso.with(this).load(URL).networkPolicy(NetworkPolicy.OFFLINE). //load from cache first time
into(imageView, new Callback() { //Picasso Callback
#Override
public void onSuccess() {
if(isNetworkAvailable()) { // if network available then update the cache for this URL
Picasso.with(MyActivity.this).invalidate(URL);
}
progress.setVisibility(View.GONE); // Progressbar
}
#Override
public void onError() { // Image not loaded, try again one last time
Picasso.with(MyActivity.this).load(URL).into(imageView, new Callback() {
#Override
public void onSuccess() {
progress.setVisibility(View.GONE);
}
#Override
public void onError() {
progress.setVisibility(View.GONE);
}
});
}
});
Scenario:
I have a large GIF image which I want to cache the first time user opens the app using Glide - Image Loading and Caching library. After that whenever user opens the app, I want to show the cached version if present. This GIF URL will expire after a given interval. When it expires, I fetch the new GIF URL and display/cache that for future use.
What I tried:
I went through Caching and Cache Invalidation on Glide's github page. I also went though the Google Group thread Ensuring That Images Loaded Only Come From Disk Cache, which shows how to get the image form cache. I also went through How to invalidate Glide cache for some specific images question.
From the links above I see the following code sniplet which shows how to load the image from cache. However this only tries to get the image from cache. If its not present in cache, it doesn't try to get from the network and fails:
Glide.with(TheActivity.this)
.using(new StreamModelLoader<String>() {
#Override
public DataFetcher<InputStream> getResourceFetcher(final String model, int i, int i1) {
return new DataFetcher<InputStream>() {
#Override
public InputStream loadData(Priority priority) throws Exception {
throw new IOException();
}
#Override
public void cleanup() {
}
#Override
public String getId() {
return model;
}
#Override
public void cancel() {
}
};
}
})
.load("http://sampleurl.com/sample.gif")
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(theImageView);
Questions:
Is there a cleaner way to achieve the following: Show the GIF image from the cache if present else download the GIF, cache it for later use and show it in the ImageView.
The caching article above mentions the following:
In practice, the best way to invalidate a cache file is to change
your identifier when the content changes (url, uri, file path etc)
The server sends a different URL to the app when the previous one expires. In this case, I believe the old image will eventually be Garbage Collected? Is there a way to force remove the image from the cache?
On a similar note, is there a way to prevent the Garbage Collection of an image with specific key (to prevent downloading the large file again) and then later instruct to delete the old image from cache when the URL changes?
You don't need a custom ModelLoader to show the GIF from cache if present and fetch it otherwise, that's actually Glide's default behavior. Just using a standard load line should work fine:
Glide.with(TheActivity.this)
.load("http://sampleurl.com/sample.gif")
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(theImageView);
Your code will prevent Glide from downloading the GIF and will only show the GIF if it is already cached, which it sounds like you don't want.
Yes, the old image will eventually be removed. By default Glide uses an LRU cache, so when the cache is full, the least recently used image will be removed. You can easily customize the size of the cache to help this along if you want. See the Configuration wiki page for how to change the cache size.
Unfortunately there isn't any way to influence the contents of the cache directly. You cannot either remove an item explicitly, or force one to be kept. In practice with an appropriate disk cache size you usually don't need to worry about doing either. If you display your image often enough, it won't be evicted. If you try to cache additional items and run out of space in the cache, older items will be evicted automatically to make space.
Glide.with(context)
.load("http://sampleurl.com/sample.gif")
.skipMemoryCache(true)
.into(imageView);
You already noticed that we called .skipMemoryCache(true) to specifically tell Glide to skip the memory cache. This means that Glide will not put the image in the memory cache. It's important to understand, that this only affects the memory cache! Glide will still utilize the disk cache to avoid another network request for the next request to the same image URL.for more read this
Glide Cache & request optimization.
Happy coding!!
So, I was starting my project and wants to use Picasso in my project because its popular and used by many projects out there.
I included picasso using gradle and tried loading facebook profile url with this. http://graph.facebook.com/rohitiskul/picture.
It worked very well. It loaded image from network without any issues. I restarted the app.(Without actually killing the process). It showed me the same image instantly cached in Memory.
But then, I killed the app (force stop) and restarted. It took almost 10+ seconds to load the image. And that image was loading from the disk when I checked in the debug logs.
My code looks like this -
In MainActivity-
Picasso.with(context)
.load("http://graph.facebook.com/rohitiskul/picture")
.into(imageView);
In application class-
Picasso picasso = new Picasso.Builder(this)
.indicatorsEnabled(true).loggingEnabled(true).build()
Picasso.setSingletonInstance(picasso);
Anyone with the similar problem? Any solution would be helpful.
I tried loading same Url with UniversalImageLoader and it was fast when fetching cached image from disk.
Edit
Earlier while playing with my app, I found out that Picasso wasn't loading the disk cached image when device was offline.
I encounter the same problem,
but find only slow for the first image, later images will be fast.
Probably it needs a warm-up (loading index cache) ?
Okay i got your problem. I have fixed it by doing this
Picasso.with(context)
.load("http://graph.facebook.com/rohitiskul/picture")
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView, new Callback() {
#Override
public void onSuccess() { }
#Override
public void onError() {
// Try again online if cache failed
Picasso.with(context)
.load("http://graph.facebook.com/rohitiskul/picture")
.into(imageView);
}
});
Explanation:
Picasso will look for images in cache.
If it failed only then image will be downloaded over network. In your case from facebook.
This issue I had also faced earlier, with this what I understood that Picasso refer the cached image based on image name mentioned in the URL.
In your case you don't have image name in URL like 'image1.jpg'. Due to which Picasso is finding it difficult to read from cache and it downloads the image everytime
You can give a try to image containing the image name in URL and that will work
Picasso doesn't offer disk cache out of the box. Instead, it relies on an Http Cache.
Make sure you add OkHttp to your dependency list.
Add a string identifier with the stableKey method when making the request so Picasso can identify your requests and quickly load it from the cache.
Example:
Picasso.Builder(context).loggingEnabled(true).build()
.load(imageUrl)
.stableKey("myImage")
.into(imageView)
Care: No code here, only text and some questions about bitmap caching
I'm currently developing an App which is almost finished. The only thing left, that I would like to do is caching images. Because, at the moment, when the user opens the app the app downloads images from a server. Those images are not static, that means they can change every minute/hour/day. I don't know when they change, because it's a list of images gathered by the amount of twitter shares, facebook likes etc. That means, when a picture has 100 likes and 100 tweets it is place 1. But when another picture gets more likes and tweets it gets rank 1 and the other one will be placed as rank 2. This isn't exactly my app, but just so you understand the principle.
Now I looked into Bitmap caching so the user doesn't have to download the same images over and over. The question I do have is how do I do it? I mean, i Understand HOW to cache bitmaps.
I looked into this documentation article: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
But, the problem is, how do I know if the Bitmap already got downloaded and has been cached or if I have to download it again? Don't I have to download the image first to check if I have this particular image already in my system?
I thought about getting the URL of the image, then convert it into a hash. And then, save the files to the cache with the hash as filename. Then, when the image URL comes it will be checked wether the image is available in the cache or not. If it is it will be loaded if not it will be downloaded. Would that the way to go be?
Or am I misunderstanding bitmap caching and it does it from its own already?
my best advice on those cases is: Do not try to re-invent the wheel.
Image loading/caching is a very complex task in Android and a lot of good developers already did that. Just re-use their work.
My personal preference is Picasso http://square.github.io/picasso/
to load stuff with it is one very simple line of code:
Picasso.with(context).load(url).into(imgView);
it's that simple!
It does both RAM and disk cache, handles all threading issues and use the excellent network layer okHttp.
edit:
and to get access directly to the Bitmap you can:
Picasso.with(context).load(url).into(new Target() {
void onBitmapLoaded(Bitmap bitmap, LoadedFrom from){
// this will be called on the UI thread after load finishes
}
void onBitmapFailed(Drawable errorDrawable){
}
void onPrepareLoad(Drawable placeHolderDrawable){
}
});
Check this library:
http://code.google.com/p/android-query/wiki/ImageLoading
It does caching automagically
example
//fetch a remote resource in raw bitmap
String url = "http://www.vikispot.com/z/images/vikispot/android-w.png";
aq.ajax(url, Bitmap.class, new AjaxCallback<Bitmap>() {
#Override
public void callback(String url, Bitmap object, AjaxStatus status) {
}
});.
http://code.google.com/p/android-query/wiki/AsyncAPI
You can try https://github.com/thest1/LazyList
the project code was designed for listviews, but still, its purpose is to download images from URLs in the backgroud so the user doesn't have to hold on the whole downloading time.
you take these JAVA classes : FileCache, ImageLoader, MemoryCache, import them into your project,
for downloading an image you just call imageLoader.DisplayImage(URL,ImageView);
the best part is that it takes care of the cache itself so you don't have to worry about that
hope this helps