This Post describes ten techniques I've used to improve performance, usability, and security of AIR applications, and to make the development process faster and easier:
Another technique is to create a variable that holds an event listener function so that the event listener can easily remove itself, like this:
Below is a simplified version of some code that parses the XML feed generated by Gmail:
A good example is an MP3 player application. If you were to store data about all the user's tracks in an XML file, but the user only wanted to look at tracks from a specific artist or of a particular genre, you would probably have all the tracks loaded into memory at once, but only show users a subset of that data. With a SQL database, you can select exactly what the user wants to see extremely quickly and keep your memory usage to a minimum.
The Flex framework has frame rate throttling built in. The WindowedApplication class's
As I learned when writing MailBrew, sometimes frame rate throttling can be slightly more complicated, however. MailBrew has a notification system that brings up Growl-like notifications when new messages come in (see Figure 2), easing them in with a gradual alpha tween. Of course, these notifications appear even when the application isn't active, and require a decent frame rate in order to fade in and out smoothly. Therefore, I had to turn off the Flex frame rate throttling mechanism and write one of my own.
- Keeping memory usage low
- Reducing CPU usage
- Storing sensitive data
- Writing a "headless" application
- Updating the dock and system tray icons
- Handling network connectivity changes
- Creating "debug" and "test" modes
- Detecting when the user is idle
- Managing secondary windows
- Programming for different operating systems
1. Keeping memory usage low
I recently wrote an email notification application called MailBrew. MailBrew monitors Gmail and IMAP accounts, then shows Growl-like notifications and plays alerts when new messages come in. Since it's supposed to keep you informed of new email, it obviously has to run all the time, and since it's always running, it has to be very conservative in terms of memory usage (see Figure 1). Figure 1. MailBrew consumes some memory while initializing, and uses a little every time it checks mail, but it always goes back down.
Since the runtime does automatic garbage collection, as an AIR developer you don't have to manage memory explicitly, but that doesn't mean you are exempt from having to worry about it. In fact, AIR developers should still think very carefully about creating new objects, and especially about keeping references around so that they can't be cleaned up. The following tips will help you keep the memory usage of your AIR applications both low and stable:
Here's some (simplified) code from an application I wrote called PluggableSearchCentral that shows adding and removing event listeners properly:
- Always remove event listeners
- Remember to dispose of your XML
- Write your own
dispose()
functions - Use SQL databases
- Profile your applications
Always remove event listeners
You've probably heard this before, but it's worth repeating: when you're done with an object that throws events, remove all your event listeners so that it can be garbage collected.Here's some (simplified) code from an application I wrote called PluggableSearchCentral that shows adding and removing event listeners properly:
private function onDownloadPlugin():void { var req:URLRequest = new URLRequest(someUrl); var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onRemotePluginLoaded); loader.addEventListener(IOErrorEvent.IO_ERROR, onRemotePluginIOError); loader.load(req); } private function onRemotePluginIOError(e:IOErrorEvent):void { var loader:URLLoader = e.target as URLLoader; loader.removeEventListener(Event.COMPLETE, onRemotePluginLoaded); loader.removeEventListener(IOErrorEvent.IO_ERROR, onRemotePluginIOError); this.showError("Load Error", "Unable to load plugin: " + e.target, "Unable to load plugin"); } private function onRemotePluginLoaded(e:Event):void { var loader:URLLoader = e.target as URLLoader; loader.removeEventListener(Event.COMPLETE, onRemotePluginLoaded); loader.removeEventListener(IOErrorEvent.IO_ERROR, onRemotePluginIOError); this.parseZipFile(loader.data); }
Another technique is to create a variable that holds an event listener function so that the event listener can easily remove itself, like this:
public function initialize(responder:DatabaseResponder):void { this.aConn = new SQLConnection(); var listener:Function = function(e:SQLEvent):void { aConn.removeEventListener(SQLEvent.OPEN, listener); aConn.removeEventListener(SQLErrorEvent.ERROR, errorListener); var dbe:DatabaseEvent = new DatabaseEvent(DatabaseEvent.RESULT_EVENT); responder.dispatchEvent(dbe); }; var errorListener:Function = function(ee:SQLErrorEvent):void { aConn.removeEventListener(SQLEvent.OPEN, listener); aConn.removeEventListener(SQLErrorEvent.ERROR, errorListener); dbFile.deleteFile(); initialize(responder); }; this.aConn.addEventListener(SQLEvent.OPEN, listener); this.aConn.addEventListener(SQLErrorEvent.ERROR, errorListener); this.aConn.openAsync(dbFile, SQLMode.CREATE, null, false, 1024, this.encryptionKey); }
Remember to dispose of your XML
In Flash Player 10.1 and AIR 1.5.2, we added a static function to the System class calleddisposeXML()
which makes sure all the nodes in an XML object are dereferenced and immediately available for garbage collection. If your application parses XML, make sure to call this function when you're finished with an XML object. If you don't use System.disposeXML()
, it's possible that your XML object will have circular references which will prevent it from ever being garbage collected.Below is a simplified version of some code that parses the XML feed generated by Gmail:
var ul:URLLoader = e.target as URLLoader; var response:XML = new XML(ul.data); var unseenEmails:Vector.<EmailHeader> = new Vector.<EmailHeader>(); for each (var email:XML in response.PURL::entry) { var emailHeader:EmailHeader = new EmailHeader(); emailHeader.from = email.PURL::author.PURL::name; emailHeader.subject = email.PURL::title; emailHeader.url = email.PURL::link.@href; unseenEmails.push(emailHeader); } var unseenEvent:EmailEvent = new EmailEvent(EmailEvent.UNSEEN_EMAILS); unseenEvent.data = unseenEmails; this.dispatchEvent(unseenEvent); System.disposeXML(response);
Write your own dispose() functions
If you are writing a medium to large application with a lot of classes, it's a good idea to get into the habit of adding "dispose" functions. In fact, you will probably want to create an interface calledIDisposable
to enforce this practice. The purpose of a dispose()
function is to make sure an object isn't holding on to any references that might keep it from being garbage collected. At a minimum, dispose()
should set all the class-level variables to null
. Wherever there is a piece of code using an IDisposable
, it should call its dispose()
function when it's finished with it. In most cases, this isn't strictly necessary since these references will usually get garbage collected anyway (assuming there are no bugs in your code), but explicitly setting references to null
and explicitly calling the dispose()
function has two very important advantages:- It forces you to think about how you're allocating memory. If you write a
dispose()
function for all your classes, you are less likely to inadvertently retain references to instances which could prevent objects from getting cleaned up (which might cause a memory leak). - It makes the garbage collector's life easier. If all instances are explicitly nulled out, it's easier and more efficient for the garbage collector to reclaim memory. If your application grows in size at predictable intervals (like MailBrew when it checks for new messages from several different accounts), you might even want to call
System.gc()
when you're finished with the operation.
private function finishCheckingAccount():void { this.disposeEmailService(); this.accountData = null; this.currentAccount = null; this.newUnseenEmails = null; this.oldUnseenEmails = null; System.gc(); } private function disposeEmailService():void { this.emailService.removeEventListener(EmailEvent.AUTHENTICATION_FAILED, onAuthenticationFailed); this.emailService.removeEventListener(EmailEvent.CONNECTION_FAILED, onConnectionFailed); this.emailService.removeEventListener(EmailEvent.UNSEEN_EMAILS, onUnseenEmails); this.emailService.removeEventListener(EmailEvent.PROTOCOL_ERROR, onProtocolError); this.emailService.dispose(); this.emailService = null; }
Use SQL databases
There are several different methods for persisting data in AIR applications:- Flat files
- Local shared objects
- EncryptedLocalStore
- Object serialization
- SQL database
A good example is an MP3 player application. If you were to store data about all the user's tracks in an XML file, but the user only wanted to look at tracks from a specific artist or of a particular genre, you would probably have all the tracks loaded into memory at once, but only show users a subset of that data. With a SQL database, you can select exactly what the user wants to see extremely quickly and keep your memory usage to a minimum.
Profile your applications
No matter how good you are at memory management or how simple your application is, it's a very good idea to profile it before you release it. An explanation of the Flash Builder profiler is beyond the scope of this article (using profilers is as much an art as a science), but if you're serious about building a well-behaved AIR application, you also have to be serious about profiling it.2. Reducing CPU usage
It's very difficult to provide general tips about CPU usage in AIR applications since the amount of CPU an application uses is extremely specific to the application's functionality, but there is one universal way to reduce CPU usage across all AIR applications: lower your application's frame rate when it's not active.The Flex framework has frame rate throttling built in. The WindowedApplication class's
backgroundFrameRate
property indicates the frame rate to use when the application isn't active, so if you're using Flex, simply set this property to something appropriately low like 1
.As I learned when writing MailBrew, sometimes frame rate throttling can be slightly more complicated, however. MailBrew has a notification system that brings up Growl-like notifications when new messages come in (see Figure 2), easing them in with a gradual alpha tween. Of course, these notifications appear even when the application isn't active, and require a decent frame rate in order to fade in and out smoothly. Therefore, I had to turn off the Flex frame rate throttling mechanism and write one of my own.
No comments:
Post a Comment