In my app, i repeat an AysnceTask that downloads images from my AWS bucket but they take a little bit to download (1/2 seconds usually) and when I am downloading 10 images it adds up and makes the user experience worse.
My question is: is there a faster way to download images from AWS S3?
Android code:
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
String PhotoURL = "https://s3.amazonaws.com/bucket/Images/" + productForImages;
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(PhotoURL).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
productColumn = 5;
productImages[productRow][productColumn] = result;
}
}
Using different services (like AWS), a GitHub, or another library to speed up downloading is possible!
Try the Glide library recommended by google.
It have more features compared to Picasso library.
Add this dependency in your gradle
compile 'com.github.bumptech.glide:glide:3.5.2'
Load your image using following code
Glide.with(context)
.load("//inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
.into(yourImageView);
Reference - https://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en
Use Picasso - Its a easy to use library by
Step 1: Add dependency in gradle
compile 'com.squareup.picasso:picasso:2.5.2'
Step 2: Use in activity
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Picasso handles all the heavy lifting in the background. It also caches the images for faster image retrieval in the future. Also, Picasso does parallel downloads by default.
If you want to download images faster and more efficiently, then there are methods like multiple connections to download a file (Too much overhead for small files though).
However, the main bottleneck is the users' internet bandwidth. I guess your current implementation is downloading those images every time open your app. This is not efficient. What you can do is that caching the images and checking for update.
For example, when you open your app:
Load the cached images` (If it is fresh install, then there should also be cached images in your app package)
check for image update at the background
If there is an update, download them
When you finish downloading, update your cache and replace the loaded images if needed.
Use multi threaded downloads: You can make Asynctask's run in parallel with myTask.execute(AsyncTask.THREAD_POOL_EXECUTOR). This allows more downloads in same time.
Make server content delivery faster: Add Amazon cloudfront (a CDN) in front of S3. This speeds up downloads for users.
First stream downloading files to a disk cache, then decode into bitmaps of only required resolution. This saves a lot of RAM if you need not show images at full size. Since images are cached, repeated requests for same image will not use network resources.
For #1 and #3, simply use Fresco
Related
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)
Updated
So as suggested earlier, i used Universal image loader. But i am getting some error.
**This is the first time i am playing around with this kind of stuff.
Below are my codes :
Here is the code to save bitmap to internal storage using async:
Uri uri = Crop.getOutput(result);
try {
bmp = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
} catch (IOException e) {
e.printStackTrace();
}
finally {
new saveDpToDisk().execute(bmp);
}
class saveDpToDisk extends AsyncTask{
#Override
protected Object doInBackground(Object[] params) {
try {
fos = openFileOutput("ProPic", Context.MODE_PRIVATE);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
bmp.compress(Bitmap.CompressFormat.JPEG, 60, fos);
return bmp;
}
}
and here is code to load image from storage using UIL:
#Override
protected Object doInBackground(Object[] params) {
FileInputStream fis;
try {
fis = openFileInput("ProPic");
String uri = String.valueOf(fis);
DisplayImageOptions options = new DisplayImageOptions.Builder().cacheOnDisk(true).build();
ImageLoader loader = ImageLoader.getInstance();
loader.displayImage(uri, pro_pic, options);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return pro_pic;
}
}
And this is the error I am getting (app doesnt force close, error just appears in logcat
UIL doesn't support scheme(protocol) by default [java.io.FileInputStream#42987180]. You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))
java.lang.UnsupportedOperationException: UIL doesn't support scheme(protocol) by default [java.io.FileInputStream#42987180]. You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))
at com.nostra13.universalimageloader.core.download.BaseImageDownloader.getStreamFromOtherSource(BaseImageDownloader.java:235)
at com.nostra13.universalimageloader.core.download.BaseImageDownloader.getStream(BaseImageDownloader.java:97)
at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.downloadImage(LoadAndDisplayImageTask.java:290)
at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryCacheImageOnDisk(LoadAndDisplayImageTask.java:273)
at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryLoadBitmap(LoadAndDisplayImageTask.java:229)
at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.run(LoadAndDisplayImageTask.java:135)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
________x_____________________x___________________________x_______________________
Original Question
My app has to load only one image (which user can select from gallery to set as his dp for that app).
So, What I did is I saved the selected pic to storage using FileOutputStream and then load the pic using FileInputStream in activity's onResume method.
But, what happens is that when the selected pic is too large, the app starts up too slowly (takes time to inflate view) and logcat shows memory heap of 30-60 MB.
So, i thought of storing the image in cache and load but dont exactly find a way to do so.
Shall i use picasso? If yes, how to use it for saving and laoding from cache.
Or are there any other ways to achieve what i need?
If you read this post on G+ by Koush you will get clear solutions for your confusions, I have put the summery of that, in that Android-Universal-Image-Loader is the winner for your requirement!
Picasso has the nicest image API if you are using network!
UrlImageViewHelper + AndroidAsync is the fastest. Playing with these
other two great libraries have really highlighted that the image API
is quite dated, however.
Volley is slick; I really enjoy their pluggable backend transports,
and may end up dropping AndroidAsync in there. The request priority
and cancellation management is great(if you are using network)
Android-Universal-Image-Loader is the most popular one out there
currently. Highly customizable.
This project aims to provide a reusable instrument for asynchronous
image loading, caching and displaying. It is originally based on Fedor
Vlasov's project and has been vastly refactored and improved since
then.
Considering all this Android-Universal-Image-Loader suites your requirement (Loading the images are on disk locally)!
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
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.