DLG Software

Application development

  with JavaScript, Flex, and AIR

This post describes a memory issue I had with AIR on Android when processing multiple large images — was having app crashes/restarts.

AIR, Droids, and Memory

I'm a bit leery about writing this post, since it recommends forcing execution of the Flash Player's garbage collection, something you generally don't want to do. However, it may be acceptable when you're running on a Droid and processing large images. Let me make my case, and I'd be happy to get feedback if you think I'm out of line on this.

One of the nice features of AIR on Android is having access to the phone's camera and cameraRoll (the latter provides access to saved images). However, default camera settings create multi-MB images (Droid X has a 8MP camera) that can use a lot of memory when you read them in. As a result, you need to keep a close eye on memory usage as you work with these images.

To be clear, when I talk about memory issues here I'm not talking about memory leaks. Rather, I'm talking about memory that is available for garbage collection but garbage collection just isn't running frequently enough — at least not frequently enough for the Droid, which of course has limited memory (Droid X = 512MB).

Memory use that wouldn't cause problems on a PC can cause stability issues on a Droid — the code I provide below runs without problems on a PC but consistently causes problems on a Droid. Use more than your share of memory on a Droid and the OS will simply whack your app.

The problem

The problem addressed here is caused by an ever-increasing memory footprint while waiting for garbage collection to run.

To illustrate this I've written a little diagnostic app (source code is later in the post). Below are 2 screencaps. The image to the left shows the memory usage after having selected just 6 images from CameraRoll. Note the msgline, which reports the memory footprint (from System.privateMemory). Yes, you read that right, that's 196MB of memory on a phone with a total of 512MB. And all we've done is read 6 images in, consecutively assigning them to an Image control's source property. The image to the right is taken a moment later, after a forced execution of garbage collection — quite a drop in the app's memory footprint.

IMAGE LEFT: after selecting 6 images from cameraRoll: 196MB
IMAGE RIGHT: immediately after forcing garbage collection: 93MB

My Solution

So, my solution wasn't rocket science, I just force garbage collection after every image read using System.gc(). Note that this requires Flash Player 10, which shouldn't be an issue for Android apps since they already require FP 10.

To better illustrate the before/after picture I've included some Flex profiler screen grabs below (these profiler execs were done on my PC, and I'm assuming the FP's behavior is similar in both PC and Droid environments, though they certainly aren't the same, since I've noted generally higher memory usage when running on the phone).

In this execution I select an image at the 5 second mark and then select another every 5 seconds. You can see that garbage collection is running at times — where the blue line dips from the red — but it isn't sufficient to keep us from clinbing to near 150MB. At the end (the 50 second mark) I manually execute garbage collection.

So, here's the memory profile with gc running on its own schedule...

Fig 1. Using normal GC — peak memory 143 MB

...and below is the same app with a force of gc after every image selection/read. Now memory peaks at only 60MB and generally settles down to around 32 MB after gc runs.

Fig 2. Using forced GC — peak memory 60 MB


As noted above, this wasn't a memory leak problem — once GC executes memory is reclaimed. Using this approach — forcing gc after every image selection — changed my app from flaky to stable.

I admit I don't fully understand what's going on here (yes, in both scenarios memory is eventually reclaimed, but why is overall post-gc memory use lower when the System.gc call is introduced?). In any case, I wanted to post this in case others are having similar problems. As noted above, if you have insight into this or want to take issue with my fix feel free to use the Contact me link at the bottom of the page.

BTW, none of this is a criticism of how gc is implemented in the FP, you certainly don't want it running every time you perform a task in your app, since it can add significant processing to a frame. On the other hand, it appears that garbage collection isn't quite aggressive enough for phone apps working with large images. And in this case I think it's acceptable to help it along by using System.gc(). Keep in mind that this is an exceptional case — in general you shouldn't be manually running garbage collection. For more on this topic see the references section below.

Below is the source code for this test — not my original code, obviously, just a diagnostic program to illustrate the problem for this post.

Note: I realize that this diagnostic program doesn't represent a real-world situation — i.e., reading 6 images consecutively like this isn't something you'd often do, at least not without performing other tasks that might trigger garbage collection. Still, it does reflect the memory profile I was seeing in my app.

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
   3:          xmlns:s="library://ns.adobe.com/flex/spark"
   4:          creationComplete="onCreationComplete()">
   6:  <s:titleContent>
   7:      <s:Label id="msgText" width="100%"/>
   8:  </s:titleContent>
  10:  <fx:Script>
  11:      <![CDATA[        
  12:      //------------------------------------------------------------------------
  13:      //  This illustrates an issue with loading several large images on a phone 
  14:      //  causing a too-large memory footprint which can cause stability problems. 
  15:      //  Issue appears to be gc not frequent enough; solution is System.gc call.
  16:      //  But do NOT use System.gc() lightly - see blog post for details.
  17:      //
  18:      //  When running on PC this loads images from disk via  Load Image button.
  19:      //  When running on the phone this loads images through CameraRoll button.
  20:      //------------------------------------------------------------------------
  22:      private var cameraRoll:CameraRoll;
  23:      private var lastLocalImagePosition:Number = 0 ; 
  24:      private var localImages:Array = [ 
  25:          "file:///C:/tempImages/camDemoInput2.JPG" 
  26:          , "file:///C:/tempImages/camDemoInput1.JPG"      
  27:          , "file:///C:/tempImages/camDemoInput3.JPG"  
  28:          , "file:///C:/tempImages/camDemoInput5.JPG"        
  29:          ] ;
  32:      private function onCreationComplete():void 
  33:      {
  34:          reportMemory() ; 
  35:          if (CameraRoll.supportsBrowseForImage)
  36:          {
  37:              cameraRoll = new CameraRoll();
  38:              cameraRoll.addEventListener(MediaEvent.SELECT,onRollSelect,false,0,true);
  39:              loadImageButton.enabled = false ;
  40:          }
  41:          else 
  42:              cameraRollButton.enabled = false ;                            
  43:      }            
  45:      // for non-phone exec we just load images from disk    
  46:      private function loadImageButtonHandler():void 
  47:      {
  48:          img.source = localImages[lastLocalImagePosition] ;    
  49:          msgText.text = "" ;
  50:          if (lastLocalImagePosition == localImages.length-1)
  51:              lastLocalImagePosition=0 ; 
  52:          else
  53:              lastLocalImagePosition++ ; 
  55:          // next is for PC exec w/Profiler: comment in and you get a nice flatline
  56:          // peak memory, comment it out and you hit much higher peak memory
  57:          callLater(forceGC) ;             
  58:      }
  60:      // for phone execw we load images via CameraRoll     
  61:      private function cameraRollButtonHandler():void 
  62:      {
  63:          cameraRoll.browseForImage();
  64:      }
  66:      private function onRollSelect(event:MediaEvent):void 
  67:      {                
  68:          var media:MediaPromise = event.data;                
  69:          img.source = media.file.url ; 
  70:          //msgText.text = media.file.url ; // + "(" + media.file.size + "kb)" ;
  71:          msgText.text = "" ;        
  72:      }            
  74:      // This has dramatic effect on memory usage when using many large images, 
  75:      // memory footprint is much lower if you GC whenever you change the image 
  76:      private function forceGC():void
  77:      {
  78:          System.gc() ; 
  79:      }    
  81:      private function reportMemory():void
  82:      {            
  83:          msgText.text = "App memory (MB):" 
  84:              +(System.privateMemory/1000000).toFixed(2);   
  85:      }
  86:      ]]>
  87:  </fx:Script>
  89:  <s:layout>
  90:      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle" gap="10"/>            
  91:  </s:layout>        
  93:  <s:Image id="img" maxWidth="380" maxHeight="380"/>  
  95:  <s:Button id="loadImageButton"  width="330" label="Load disk image"   
  96:            click="loadImageButtonHandler()"/>
  97:  <s:Button id="cameraRollButton" width="330" label="Load cameraRoll image" 
  98:            click="cameraRollButtonHandler()"/>
  99:  <s:Button width="330" label="Show memory used" click="reportMemory()"/>        
 100:  <s:Button width="330" label="run system.gc()" click="forceGC()"/>
 102:  </s:View>


Here are some links on memory usage and garbage collection you might find useful. Note the cautions on use of System.gc().

Flex/AIR certification logo


JavaScript appdev primers

General JavaScript

SPA Demo Application

Backbone Demo Application

Recommended sites


Flex/AIR primers

Flex demo apps

all require Flash Player!

AIR mobile dev


SAS Introduction