I have a Custom AdapterView (sort of) in which I lazy load images. To do it, I use the awesome aquery library.
Short story: I would like to cache (memcache and filecache) the downsampled version of the file. It makes it quicker to add to my adapter - when the image is small I have no lags when scrolling my AdapterView. When the image is big, even if I use downsampling it lags a bit. I found out, that aquery stores the full version of image and downsamples it every time I call aq.image(...). How to cache the resized version, not the original one?
Long story:
My AdapterView relies heavily on images. These images are rather big, and when adapter items gets instantiated it takes some time to downsample it and then add to the list. So I thought it would be nice, to instantiate items with a lo-res photo when scrolling, and only load the hi-res version when the scrolling stops. It works like a charm, when I use two separate image urls (one for thumnbail and another for the original image). But the API I work with is quite limited, so I won't have the thumbnail images' urls. I have to async download the big version, downsample it, save both big and small version and then use whichever I need. And here the "short story" begins.
I just released an open source library called droidQuery which is a full port of jQuery to Android. It is much simpler to use than AQuery, and provides a lot more configurations. To download an image and set the output size, and cache the small image (on a per-session/every ten minute basis), you can use the following code:
final ImageView image = (ImageView) findViewById(R.id.myImage);
$.ajax(new AjaxOptions(url).type("GET")
.dataType("image")
.imageHeight(200)
.imageWidth(200)
.context(this)
.cache(true)
.cacheTimeout(600000)
.success(new Function() {
#Override
public void invoke($ droidQuery, Object... params) {
$.with(image).val((Bitmap) params[0]);
}
})
.error(new Function() {
#Override
public void invoke($ droidQuery, Object... params) {
droidQuery.toast("could not set image", Toast.LENGTH_SHORT);
}
}));
I also used AQuery library in the past, but after encountering some problems with limited configuration and weird progresbar visibility issue, I moved to Android-Universal-Image Loader
https://github.com/nostra13/Android-Universal-Image-Loader it gives you your needed feature as well as plenty other useful configuration options.
Just read this site from top to bottom - and you should be able to run it in a minute.
In your case most interesting lines are
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.memoryCacheExtraOptions(480, 800) // default = device screen dimensions
.discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75)
.discCache(new UnlimitedDiscCache(cacheDir)) // default
.discCacheSize(50 * 1024 * 1024)
.discCacheFileCount(100)
You can also change cached file names.
Related
I'm trying to display an animated GIF picture in Widget, and I have URL for the picture. I know I can use WebView in activity with webView.loadUrl("http://my_url_here.gif"), but WebView is not supported in Android Widget.
I've already had URL for the picture, and I'm finding some ways not using third party libraries.
Does anyone know how to display it in Widget?
Where is no way to show GIF animation on RemoteViews. You can use only widgets that describes in official documentation
A bit late to the party, but I will post this for future references who want to get a widget animated.
I think this is probably what you want to achieve:
https://media.giphy.com/media/Rb7FAFdrgHr51V6zjy/giphy.gif
Fundamentally, GIF files "allow images or frames to be combined, creating basic animations", any GIF is a collection of images with continuous updates/refreshes to achieve the animated effect.
Hence, even though widgets on Android are RemoteViews that only support basic Text and Image, but if you can downsample a GIF into a series of PNGs or Bitmaps, you can achieve the animation effect by creating an async background task that updates the image bitmap or src of the Widget's RemoteView.
There are great answers on SO for converting Gifs into a collection of PNGs:
Using Glide, how can I go over each frame of GifDrawable, as Bitmap?
public class GifPlayer extends AsyncTask<String, Void, List<Bitmap>> {
#Override
protected List<Bitmap> doInBackground(String... params) {
try {
// load Gif images
InputStream in = new java.net.URL(url).openStream();
//... use Glide to convert Gifs into PNGs and save it in your local file system.
List<Bitmap> gifs = ...
} catch (Exception e) {
}
return null;
}
#Override
protected void onPostExecute(List<Bitmap> pngArray) {
while(true) {
for(Bitmap png : pngArray){
views.setImageViewBitmap(R.id.new_widget_image, png);
WidgetManager.updateAppWidget(WidgetID, views);
Thread.sleep(500); // higher lower this number depends on how fast you want to animate
}
}
Not to mention, Android Widgets have a small memory limit, so the above code works, but it will run out of memory for larger GIFs.
In general, RemoteViews.setImageViewBitmap(viewId, bitmap) should only be used for Widgets that don't update frequently.
For frequent updates of Widget such as animating a GIF, always use setImageViewResource(viewId, resId) instead (save frames of a GIF as PNG files identified by consecutive IDs such as gif_1.png, gif_2.png, gif_3.png...)
and replace the above code by:
//...
#Override
protected void onPostExecute(List<Integer> pngArray) {
while(true) {
for(int id : pngArray){
views.setImageViewResource(R.id.new_widget_image, id);
WidgetManager.updateAppWidget(WidgetID, views);
Thread.sleep(500); // higher lower this number depends on how fast you want to animate
}
}
You can use the gifView library :https://github.com/koral--/android-gif-drawable
use this is your .xml:-
<pl.droidsonroids.gif.GifImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/src_anim"
/>
where src_anim is your loader gif file
You can use a load of different image loading libraries for this, but I recommend Fresco. They have great built-in GIF support detailed on this page which looks like the following in code:
Uri uri = Uri.parse("http://my_url_here.gif");
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setAutoPlayAnimations(true)
. // other setters
.build();
mSimpleDraweeView.setController(controller);
update
I never really understand where this need to not use libraries stems from, but you could just manually copy across all the code classes to your app then! The code in these libraries is battle tested and will work much more reliably than most code you decide to write alone.
That being said, there's a useful gist I've used in the past that may be what you're looking for.
I am trying to make a recyclerview with gifs. Everything shows perfectly but fresco do not cache gifs. After scroll recycler down and scroll up again, gifs are loading once again. I supposed they should be cached and loaded a bit quicker. Previously I used ION library. Loading was quicker and did not have cache problem. I had to change lib because, it has some problem with gif decoding, described here. Current solution looks like that:
//for default initial in application class
Fresco.initialize(this);
//I have also tried to change DiskCacheConfig and ImagePipelineConfig params.
//Without any positive result
//for recyclerview on onBindViewHolder
GenericDraweeHierarchy hierarchy = holder.draweeView.getHierarchy();
Uri uri = Uri.parse(path);
hierarchy.setPlaceholderImage(R.drawable.img_bg);
Logger.e(check(uri) + " " + uri.toString());
DraweeController controller = Fresco.newDraweeControllerBuilder().setUri(uri)
.setAutoPlayAnimations(true).build();
holder.draweeView.setController(controller);
//for method which show cached uri images in imagepipeline
public static boolean check(Uri uri) {
ImagePipeline imagePipeline = Fresco.getImagePipeline();
return imagePipeline.isInBitmapMemoryCache(uri);
}
//... all the time log shows "false + gif url"
I have not seen any information about not caching animated images. There is information about not supported image postprocessing for animations, but it's everything about that. How to correctly cache gifs?
edit:
It looks like fresco cache animations, because below method return true for reloaded gifs.
public static boolean isImageDownloaded(Uri loadUri) {
if (loadUri == null) {
return false;
}
CacheKey cacheKey = DefaultCacheKeyFactory.getInstance()
.getEncodedCacheKey(ImageRequest.fromUri(loadUri));
return ImagePipelineFactory.getInstance().getMainDiskStorageCache().hasKey(cacheKey)
|| ImagePipelineFactory.getInstance().getSmallImageDiskStorageCache()
.hasKey(cacheKey);
}
Just to make things a bit more clear, Fresco has 3 levels of cache:
DiskCache - Keeps image files in their original format. (To be
precise, if on an Android version that doesn't fully support webp
images, they may be transcoded to other format before storing.)
EncodedMemoryCache - In-memory cache of images in their original encoded format. (Image is kept as a byte-array of the original bytes
as they are stored on disk + some additional metadata.)
BitmapMemoryCache - In-memory cache consisting mostly of Android Bitmaps. Bitmaps are decoded images and each pixel occupies 32bits
which is significantly more than what it takes when encoded.
The trade-off is obviously space vs time. Available memory is limited and if the image is not in the bitmap cache, it will have to be decoded again. Furthermore, if it is not in the encoded memory cache either, it will have to be read from the disk which also can be slow.
Now back to the animated images. This is a known limitation. Animated images are not cached in their decoded form because that would exhaust the bitmap cache (just multiply the num_frames * width * height * 32bpp) and a single animated image can possibly evict every other image in the cache. Instead they are decoded on demand and only a couple of frames that are about to be displayed next are kept in a short-lived cache.
We have some plans to improve animations, although I cannot provide any time estimates.
I was facing the same issue, it appears that Fresco is correctly caching the gif images, but in fact it's taking time to decode and play the animation each time you scroll through the RecyclerView or any other view you're using.
However if you use a gif image without animation, the gif image doesn't "reload" when scrolling through the view.
Since I have control over the images displayed inside my app. I am creating 2 versions of the gif images on my server, the first without animation to display inside the RecyclerView/ListView and the other to display inside the "Media viewer" activity, when the user clicks on an item in the list.
Fresco, they mainly focused low size gifs. I tried with low size gifs in recycleview, caching working perfectly. If you use high resolution gifs, they need high memory consumption for decode and caching. may be surpass heap size. so they are decoded on demand, and only a couple of frames (those about to be displayed) get cached.
It is possible to configure fresco to display the first frame as soon as it is available (without decode whole frame) and cache the static first frame easily.
ImageDecodeOptionsBuilder b = new ImageDecodeOptionsBuilder();
b.setForceStaticImage(true);
ImageDecodeOptions imageDecodeOptions=new ImageDecodeOptions(b);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(animatedGifUri).setImageDecodeOptions(imageDecodeOptions).setLocalThumbnailPreviewsEnabled(true).build();
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.
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!!
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