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



Conclusion

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()">
   5:          
   6:  <s:titleContent>
   7:      <s:Label id="msgText" width="100%"/>
   8:  </s:titleContent>
   9:   
  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:      //------------------------------------------------------------------------
  21:   
  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:          ] ;
  30:   
  31:          
  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:      }            
  44:                      
  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++ ; 
  54:          
  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:      }
  59:   
  60:      // for phone execw we load images via CameraRoll     
  61:      private function cameraRollButtonHandler():void 
  62:      {
  63:          cameraRoll.browseForImage();
  64:      }
  65:              
  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:      }            
  73:                                  
  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:      }    
  80:          
  81:      private function reportMemory():void
  82:      {            
  83:          msgText.text = "App memory (MB):" 
  84:              +(System.privateMemory/1000000).toFixed(2);   
  85:      }
  86:      ]]>
  87:  </fx:Script>
  88:      
  89:  <s:layout>
  90:      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle" gap="10"/>            
  91:  </s:layout>        
  92:          
  93:  <s:Image id="img" maxWidth="380" maxHeight="380"/>  
  94:    
  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()"/>
 101:                  
 102:  </s:View>

References

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


About

Just playing here, exploring basic tweening in JavaScript, comparing to Flex capabilities (Verdict: not as good as Flex, but plenty good enough for application development).

Using Greensock I created
nine simple effects which are run randomly. I'll put the code up on github shortly.

JavaScript appdev primers

JS Data Explorer (JSDE)

General JavaScript

Flex/AIR primers

Flex demo apps

(all require Flash Player)

AIR mobile dev

SAS Introduction

Some recommended sites

About

Archives

black infrared 6s jordan retro 6 beats by dre cheap black toe 14s legend blue 11s coach outlet online coach outlet online sac louis vuitton Legend Blue 11s legend blue 11s legend blue 11s legend blue 11s michael kors outlet jordan retro 6 jordan 6 black infrared 6s sac louis vuitton coach outlet online jordan 11 legend blue michael kors outlet legend blue 11s lebron 11 Nike kd vii Easy Money jordan 11 legend blue black infrared 6s louis vuitton outlet legend blue 11s black infrared 6s Nike kd vii Easy Money michael kors outlet legend blue 11s Legend Blue 11s jordan 11 legend blue lebron 12 jordan 11 legend blue lebron 11 retro jordans coach outlet legend blue 11s coach outlet online jordan retro 11 black infrared 6s legend blue 11s michael kors outlet michael kors outlet coach outlet online beats by dre cheap jordan retro 6 legend blue 11s retro jordans black infrared 6s louis vuitton outlet Louis Vuitton Outlet michael kors outlet legend blue 11s black infrared 6s coach factory outlet jordan retro 6 jordan 11 jordan 11 legend blue nike kd vii easy money louis vuitton outlet louis vuitton outlet jordan 11 legend blue jordan retro 6 black infrared 6s black infrared 6s cheap jordans legend blue 11s black infrared 6s lebron 12 Legend Blue 11s Nike kd vii Easy Money cheap jordans michael kors outlet Legend Blue 11s michael kors outlet jordan 11 legend blue jordan 11 legend blue michael kors outlet michael kors outlet legend blue 11s black infrared 6s black infrared 6s jordan retro 6 black infrared 6s michael kors outlet louis vuitton outlet jordan 6 black infrared beats by dre cheap jordan retro 6 coach outlet online kate spade outlet legend blue 11s black infrared 6s jordan 11 Legend Blue lebron 11 legend blue 11s lebron 12 louis vuitton outlet jordan 11 legend blue black toe 14s legend blue 11s michael kors outlet legend blue 11s jordan retro 11 lebron 12 coach outlet online lebron 12 jordan 6 black infrared legend blue 11s coach outlet online black infrared 6s michael kors outlet legend blue 11s legend blue 11s coach outlet online louis vuitton outlet jordan 6 jordan 11 legend blue black infrared 6s jordan 11 legend blue louis vuitton outlet black infrared 6s Nike kd vii louis vuitton outlet black toe 14s jordan retro 11 jordan retro 11 legend blue 11s ferrari 14s Louis Vuitton Outlet beats by dre cheap jordan 6 lebron 11 jordan 11 legend blue michael kors outlet legend blue 11s Legend Blue 11s beats by dre cheap michael kors outlet Nike kd vii legend blue 11s cheap oakley sunglasses sac louis vuitton jordan 6 black infrared coach outlet online black infrared 6s jordan retro 6 legend blue 11s michael kors outlet beats by dre cheap kate spade outlet jordan 11 jordan 6 black infrared coach outlet online jordan 11 legend blue michael kors outlet jordan retro 6 retro jordans coach outlet online legend blue 11s black infrared 6s coach outlet online retro jordans black infrared 6s jordan 6 michael kors outlet sac louis vuitton coach outlet beats by dre cheap cheap oakley sunglasses coach factory outlet michael kors uk jordan retro 6 black infrared 6s nike kd vii easy money jordan 6 black infrared black infrared 6s Nike kd vii Easy Money jordan 6 jordan 6 jordan 11 legend blue legend blue 11s Legend Blue 11s jordan 11 legend blue black infrared 6s beats by dre cheap black infrared 6s black infrared 6s coach outlet online michael kors outlet michael kors outlet jordan 11 legend blue Nike kd vii legend blue 11s lebron 11 beats by dre cheap legend blue 11s sac louis vuitton jordan 11 legend blue michael kors outlet sac louis vuitton michael kors outlet Legend Blue 11s jordan 11 Legend Blue jordan 6 jordan 11 Legend Blue michael kors outlet Louis Vuitton Outlet beats by dre cheap lebron 11 sac louis vuitton michael kors outlet jordan 11 legend blue black infrared 6s jordan retro 6 jordan 11 legend blue Nike kd vii retro jordans jordan 11 legend blue 11s legend blue 11s louis vuitton outlet Nike kd vii black infrared 6s legend blue 11s michael kors outlet legend blue 11s black infrared 6s Legend Blue 11s jordan 11 legend blue jordan retro 11 jordan retro 6 legend blue 11s jordan retro 6 black infrared 6s black infrared 6s louis vuitton outlet beats by dre cheap legend blue 11s legend blue 11s jordan 11 Legend Blue legend blue 11s jordan 6 black infrared coach outlet online jordan retro 6 black infrared 6s legend blue 11s michael kors outlet black infrared 6s michael kors outlet jordan 11 legend blue lebron 11 legend blue 11s michael kors outlet beats by dre cheap sac louis vuitton beats by dre cheap legend blue 11s beats by dre cheap jordan 6 black infrared jordan 11 michael kors outlet legend blue 11s black toe 14s Legend Blue 11s sac louis vuitton black toe 14s lebron 12 legend blue 11s coach outlet online Nike kd vii jordan retro 6 lebron 11 jordan 6 black infrared jordan 11 legend blue jordan 11 legend blue black infrared 6s louis vuitton outlet black toe 14s jordan 11 legend blue black infrared 6s michael kors uk jordan 11 legend blue jordan 6 black infrared ferrari 14s michael kors outlet jordan 11 legend blue Legend Blue 11s legend blue 11s lebron 12 black infrared 6s jordan retro 11 beats by dre cheap coach outlet online legend blue 11s coach outlet online legend blue 11s legend blue 11s michael kors uk legend blue 11s coach outlet online black infrared 6s black toe 14s legend blue 11s jordan 11 legend blue lebron 12 michael kors outlet michael kors outlet lebron 12 jordan 11 legend blue black infrared 6s lebron 12 michael kors outlet jordan retro 11 cheap oakley sunglasses jordan 11 legend blue jordan retro 11 cheap jordans