It has been a rather unfruitful couple of weeks. I have been battling with Android’s infamous out of memory errors for most of the past two weeks. It has been seemingly impossible to find a solution for my app which satisfies all of its necessary requirements. I would like to have an independent class for handling the execution of images on a background thread. I also want that class to create Bitmaps, which fit the ImageView so as to not waste heap space. Then I want to cache those images, to increase load times while avoiding the creation of duplicate bitmaps, again saving heap space. So what are the problems?
Well for starters, I need some way of grabbing the width and height of the ImageView; which is more difficult than you would expect. You cannot simply create an ImageView in the activities onCreate method and call its getWidth and getHeight methods because the screen has not been laid out yet. One way you can get around this is to use the ViewTreeObserver class to set up an OnPreDrawListener or a GlobalLayoutListener, then grab the image width and height from within them. For example, you can override the onPreDraw method within the OnPreDrawListener and fire any code you want from within there. The Android developers page says onPreDraw is the “Callback method to be invoked when the view tree is about to be drawn”. The method is constantly being called; perhaps every time the screen refreshes. That means to use it I needed some kind of catch so my code only fires when a new bitmap is loaded. It was simple enough to handle with a boolean value, but it seemed wasteful to be in a constant cycle when I only need the value once. Another attempt I made was to override the onWindowFocusChanged method in the Activity classes, which holds my fragments and then call a custom method within the fragments to populate the imageView with the properly sized bitmap. At first I thought it worked, but the method is only called when the activity sets up, not when each fragment is loaded. That, and it was not a very modular approach requiring a method override in every activity where I wanted to use images.
In the end, I settled on a different solution. I had seen a blog, before this out of memory issue ever began; and I was able to implement the solution. I searched for the blog again but I could not find it, I think because it was a link within one of the hundreds of Stack Overflow posts I have been reading lately. That said, if I come across the blog again I will post the link.
Anyway, the idea was to add a CustomView class to a FrameLayout and within the CustomView class override the onSizeChanged method. Then within that method, you can set the newX and newY parameter values as your width and height for the bitmap you want to create. Originally, I did not want to use this approach because wrapping my imageView in a FrameLayout seemed unnecessary. OnSizeChanged is not called if you don’t add the CustomView to the FrameLayout. It also turns out that Layouts in general provide some basic animations for developers. That was a bonus, unfortunately the animations I made felt a bit clunky and created unnecessary animation delays. I decided to avoid animation for now, at least until I understand a bit more about it. Also, I don’t want to waste any of my semester on unnecessary improvements until the basic functions are complete. In any event, I had overcome the first unexpected hurdle of the week and am now able to generate properly sized bitmaps. I used the suggested code from the Android Developers page for decoding large bitmaps.
Unfortunately, loading properly sized bitmaps did not fix the out of memory issue, but it was happening less frequently. I decided I would continue following the advice on the Android developers sight regarding Displaying Bitmaps Efficiently at https://developer.android.com/training/displaying-bitmaps/index.html as best I could. Personally, I find there to be a huge lack of details on the Android developers page, which can make implementing their code difficult at times.
I eventually was able to use the AsyncTask class to handle my images on an independent thread, build a cache for the images, and create custom sized bitmaps all in one class that extends View in order to grab the ImageView dimensions. Along with some clean up in the onDestroy methods of my fragments; I managed to greatly reduce the memory leak to the point where a user would need to button mash through the pictures for a long time before it would crash. However, this was obviously still unacceptable.
The leak left was much to small to crash the app when used normally, but I knew it was there and it irritated me to no end. I eventually realized I was creating a new custom view each time I was loading an image, instead of overwriting the same view. I also discovered that some of what I thought was a memory leak is not, in the case of loading multiple images in and out of the same ImageView the heap space will appear to be growing by a few kilobytes each time the image changes. However, that memory is eventually reclaimed, perhaps it is simply the heap space becoming fragmented which is eventually cleaned up, but I am really not sure. To be sure there was no longer a leak though, I set up an onPreDrawListener which changed the image every time it was called, many times each second. Then I left the app to run for a couple of hours loading images on my Samsung Galaxy S3 and it no longer crashed due to an out of memory error. Success!
In the end, I actually decided to remove the cache logic. My reasons are the cache requires more heap space for storing multiple images, and when a fragment loses focus the entire cache is stored on the heap so the user can go back. You can recycle the bitmaps in the onPause method, but then they need to be loaded into the cache again. It sort of defeats the purpose of having the cache. Furthermore, users will not typically be cycling through images when using the app and it did not make sense because you can only cache only 3 or 4 large bitmaps before one must be evicted. When I tested the app on actual devices, the image load times seemed instantaneous too. I only needed to be sure that if the user cycled through all the images repeatedly the app would not crash and the gain in speed for memory the cache offers does not seem worth it.
On top of all that, I made some UI changes to my app this week and I think they will make using the app much clearer. I broke what was three roofs into five, allowing me to remove valley and hip sets from the instruction section and allow them to be standard when needed. Along with that, templates have their own create button now. I also provided buttons for cycling through multiple roof rafters. Now the instructions tab can house nothing but actual instruction texts and the app as a whole seems more intuitive. I was hoping to add more roofs and content these past two weeks, but it just did not work out. This week I will tackle gambrel and mansard roofs. Next, it will be onto adding textual content, drawing diagrams, publishing the Android version; and beginning on the iOS version. As time becomes to much of a constraint I may add the mansard roof in the next version of the app.
Till next time.
From the blog jasonhintlian » cs-wsu by jasonhintlian and used with permission of the author. All other rights reserved by the author.