I am writing this article to share my knowledge which I have learned the hard way.
Knowledge comes to those who crave for it.
So, please read it carefully. We, developers, love coffee. Let’s take a cup of coffee and jump directly into it. Even our Android Gradle building takes more time than a cup of coffee.
In Android, working with images(bitmaps) is really difficult as the application goes out of memory(OOM) very frequently. OOM is the biggest nightmare for the Android developers.
We all Android developers are very fortunate that we live in the world of open-source. I too love open-source as much as you love. I have got everything from Android community, so I always want to give something from my side by sharing my knowledge with all of you.
So, Let’s see what are the problems that we face while loading an image into an android ImageView.
- Out of memory error.
- Slow loading of an image into the view.
- UI becomes unresponsive. Not smooth scrolling.
When I had these issues, I had many sleepless nights. But one day I came to know that there is a word library in Android, earlier even I was not knowing that we can also use a library in our Android project. I read about what is a library. Then, I found Glide library for loading image on Android.
Let’s see one by one how it solves these problems.
1. Out of memory error
This biggest nightmare is attached to the life of all the Android developers. To make us happy, Glide does downsampling. Downsampling means scaling the bitmap(image) to a smaller size which is actually required by the view. Let’s say we have an image of size 2000*2000, but the view size is 400*400. So why load an image of 2000*2000, Glide down-samples the bitmap to 400*400 and then shows it in the view.
Glide knows the dimension of the imageView as it takes the imageView as a parameter.
Glide down-samples the image without loading the whole image into the memory. This way, the bitmap takes less memory, and the out of memory error is solved. And we all are happy.
2. Slow loading
Slow loading is an another problem when it comes to loading a bitmap into the view. One of the major reason of slow loading is that we do not cancel the task like downloading, decoding a bitmap even when the view is out of window, hence there are many tasks which are being done even though we do not need, so it takes time for the actual image to load which just came in the window. Glide takes care of this, it cancels all the tasks properly and only loads the images which are visible to the user. This is one way to make loading fast.
Glide is aware of the activity, fragment lifecycle, this way it knows which image downloading tasks need to be canceled.
Another way is to create memory cache so that we do not have to decode the image again and again as decoding takes time. Glide creates a cache of some configurable size to catch the bitmaps.
It maintains two level of caching:
- Memory Cache
- Disk Cache
When we provide the url to the Glide, it does the following:
- It checks if the image with that url key is available in the memory cache or not.
- If present in the memory cache, it just shows the bitmap by taking it from the memory cache.
- If not present in the memory cache, it checks in the disk cache.
- If present in the disk cache, it loads the bitmap from the disk, also puts it in the memory cache and load the bitmap into the view.
- If not present in the disk cache, it downloads the image from the network, puts it in the disk cache, also puts it in the memory cache and load the bitmap into the view.
This way it makes loading fast as showing directly from the memory cache is always faster.
3. Unresponsive UI
The most important reason of unresponsive UI is that the application is doing too much task on the main thread. As we know that all the tasks related to rendering UI are done in the main thread. And the Android updates the UI in every 16ms. If you do any task that takes more than 16ms, then android have to skip that update and hence skip that frame. So, skipping frame leads to the less frame per second.
In college days, we used to fight for the movie clip which has more FPS(Frame Per Second). More the FPS, more the smoothness while playing.
If the FPS is lower, the user sees the UI laggy and that is unresponsive. While loading the bitmaps, even if we load those in the background, then also the UI lags. Why?
The reason is that the bitmaps are larger in size, it makes the Garbage Collector(GC) run very frequently.
The basic rule is that — The time for which GC is running, your application is not running.
GC takes the time to run and it forces the system to skip many frames. So, GC is the main culprit.
How Glide solves this?
Using Bitmap Pool.
Glide uses this bitmap pool concept to reduce GC calls as much as possible.
By using the Bitmap pool, it avoids continuous allocation and deallocation of memory in your application, reduces GC overhead, which results in a smooth-running application.
How to avoid continuous allocation and deallocation of memory in your application?
By using the inBitmap property of the bitmap.(which reuses bitmap memory).
Suppose we have to load few bitmaps in an Android application.
Let say we have to load two bitmaps (bitmapOne,bitmapTwo) one by one.When we load bitmapOne, it will allocate the memory for bitmapOne. Then if when we no longer need bitmapOne, do not recycle the bitmap (as recycling involves calling GC). Instead, use this bitmapOne as an inBitmap for bitmapTwo. This way, the same memory can be reused for bitmapTwo.
Let’s see the code, how it works. See the inBitmap property carefully.
Bitmap bitmapOne = BitmapFactory.decodeFile(filePathOne); imageView.setImageBitmap(bitmapOne); // lets say , we do not need image bitmapOne now and we have to set // another bitmap in imageView final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(filePathTwo, options); options.inMutable = true; options.inBitmap = bitmapOne; options.inJustDecodeBounds = false; Bitmap bitmapTwo = BitmapFactory.decodeFile(filePathTwo, options); imageView.setImageBitmap(bitmapTwo);
So, we are reusing the memory of bitmapOne while decoding the bitmapTwo.
This way we are not allowing the GC to be called again and again as we are not leaving off the reference of the bitmapOne, instead we are loading the bitmapTwo in the memory of bitmapOne.
One important thing is that the size of the bitmapOne should be equal or greater than the bitmapTwo so that bitmapOne’s memory can be re-used.
There are few things which are very specific to different Android versions which are to be considered while re-using the bitmap memory, I suggest you read this project.
So, Glide creates a bitmap pool of bitmaps.
You can say that the bitmap pool is a list of bitmaps which are no longer needed but are available for reuse to load the new bitmap in the same memory.
When any bitmap is available for recycle, Glide just pushes the bitmap in that bitmap pool.
When Glide has to load the new bitmap, it just gets a bitmap which can be reused to load the new one to reuse the same memory from that bitmap pool. Hence no recycling, no GC calls.
Fresco also does the same things like Glide. There are few different things but the concept is almost same.
I think we have received a good amount of knowledge today. Thank you so much for your time.
Happy Learning 🙂