I am unclear how images are handled in Android in terms of caching.
Let's say, I have an app that is 3 levels deep (so, it has 3 Activities that are pushed on top of each other).
If each of my 3 activities shows 3 images in the ToolBar of all 3 Activities, my understanding is that the same 3 images are going to be loaded in memory 3 times. This is resulting in 9 images taking memory although it is really only 3 images, they are just loaded multiple times.
Also, let's say my main activity has a ListView showing information about documents retrieved over the network.
Each list view item is customized to show 3 pieces of information:
If the above ListView loads million items (each represents a document) and assume all are a word document, then all million images for .docx type are downloaded but it really is just one image that I need. This seem to be obvious waste of network bandwidth and memory, I'd like to download only one image and use it million times rather than download million images and use them each only once.
So, in scenario like this, where I dont know what kind of image I will need and therefore do not have it in image assets but rely on downloaded image to show document type, is there some kind of caching strategy that I could use to avoid having to download same image million times?
Is it provided by Android already?
UPDATE
In my above example of Toolbar images, these are Android Resource images (so, drawables). So, for these, I believe these are cached by default?
However, the ListView images are images that are downloaded from the web, so in my example above with custom ListViewItem consisting of 3 Views (one Image and 2 Labels), the Image would be downloaded at the same time ListView is being rendered. I am providing a simple ListView item with only 3 Views but it could be 10 views (say 4 images and 6 Labels). The main point is that the one image is downloaded from the web during the rendering process of the ListView. Since that image might be repeating, how to awoid downloading same image but to use cached one?
First about Lists of items
ListView
does not work in such an inefficient way you assume. But if you really care about efficiency and handling really large lists of data it is advised to use RecyclerView
. There is a good discussion and an answer with the comparison of both here.
If you plan to handle a list of thousands of docs with certain limited collection of document type icons the best practice would be to have a HashMap of Bitmaps, which you will link to the particular list item (regardless of what you will pick to use: RecyclerView
or ListView
).
Here is a brief example (it is not correct java, just to give an idea) with ListViews' adapter class:
public class DocumentsListAdapter extends ArrayAdapter<String> {
private final HashMap<String, Bitmap> docIcons = HashMap<String, Bitmap>()
private final Activity mContext;
private final List<String> mItems;
public DocumentsListAdapter(Activity context, List<String> items) {
docIcons.put("docx", loadDocBitmapFromDisk());
docIcons.put("xls", loadXlsBitmapFromDisk());
docIcons.put("pdf", loadPdfBitmapFromDisk());
// and so on...
mItems = items;
mContext = context;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView= inflater.inflate(R.layout.list_single, null, true);
TextView txtTitle = (TextView) rowView.findViewById(R.id.txt);
ImageView imageView = (ImageView) rowView.findViewById(R.id.img);
txtTitle.setText(mItems.get(position));
if(items.get(position).endsWith(".docx")) {
imageView.setImageBitmap(docIcons.get("docx"))
} // and so on for other types...
}
}
This example does not cover how you obtain your icons. You can download them from internet, store them on disk and when application needs those icons — you load them from disk as bitmaps and store in the memory only one time each bitmap and reuse them in the views. Implementing this logic along with RecyclerView you will get you rather efficient application which doesn't waste memory and is very responsive.
You don't have to store HashMap
with bitmaps in adapter but it is good option if you use it only in one place. If you need to use it in different activities, then consider to keep this HashMap
somewhere else, where it won't be reloaded every time user switches between different activities utilizing similar ListViews
.
Handling multiple Activities with the same images
Concerning the Activities it depends how do you store your images. If they are also get downloaded from the Internet and cached on disk you may do the same as described in the ListView section of this answer. Just keep somewhere list of images that you had loaded into the memory and use
imageView.setImageBitmap(someBitmap)
when you need to display it in the specific part of activity.
In the case you are working with application resources it is even easier, as you can do something like this:
Drawable dr = context.resources.getDrawable(R.drawable.my_favourite_image);
imageView.setImageDrawable(dr);
All work with memory efficient usage is done here under the hoods.
How to avoid downloading same image but to use cached one
It depends on how you are going to track the uniqueness of each image. E.g. if each image has unique URL the most basic solution is to use the HashMap with the URL of an image as the key.
More complicated but more flexible and correct decision would be to use something like Guava caching tools. This is not android specific and very broad topic to cover in this answer, but you can take a look here.
Although, if you plan to mess with complex layouts of images and care about performance, check Anko
and this benchmark. This is not related to content caching, but layout rendering performance can be improved by 3 times and more.