Flex – Event Handlers and Weak References

Did you know defining an event handler on an object in Flex can prevent it or other objects from being garbage collected even after all explicit references to the objects have been removed? If not, then keep reading.

Introduction

Previously, I discussed techniques for freeing up memory on video and images in Flex, but I neglected to include a discussion of event handlers. With the release of ActionScript 3, Adobe added the notion of strong references and weak references. Strong references are those references that, if present, will prevent the object from being garbage collected. The presence of only weak references, on the other hand, will allow an object to be garbage collected. When most developers consider garbage collection, they often consider only strong references, those references they explicitly know about. The age-old wisdom, that if you remove all active (strong) references to an object, it will become eligible for garbage collection, overlooks the fact that the object may have strong references from other objects, created via event handler registration.

Event Handlers are Strong References (by default)

Somewhat surprisingly, and perhaps in a move to preserve older source code, the commonly used method addEventListener() creates a strong reference to the event handler by default. That means even after removing all explicit references to an object, it may never be eligible for garbage collection because other objects have subscribed as listeners to the object. If you have a process that repeatedly creates such objects, you have an uncontrolled memory leak that will eventually crash your program. As an example, consider a screen saver application that displays a new Image or Video on the screen every 30 seconds by creating new display objects and discarding the existing ones. Without considering the event handlers on this application, the memory usage could grow without bound, even if you take proper steps to discard the display objects.

From the Adobe Flex 3 documentation:

One important consequence of this parameter involves working with display objects’ events. Normally, you might expect a display object to be removed from memory when it is removed from the display list. However, if other objects have subscribed as listeners to that display object, with the useWeakReference parameter set to false (the default), the display object will continue to exist in Flash Player’s or AIR’s memory even though it no longer appears on the screen.

Solution #1: Proper deconstructor

The most obvious solution to prevent event handlers from holding objects in memory is to call removeEventListener() on the original object for each event handler on it. The problem is Flex does not provide a listAllEventListeners() method nor a removeAllEventListeners() method in the Event Dispatcher API [Feature Request!], so unless you remembered to save a reference to the event handler when you created it, you’re not going to have much luck removing it. This solution requires you to manage a list of event handlers and their associated types, so each can be discarded at a later time. Also, remember to remove the reference to the event handler in the list, such that the act of maintaining a list of references does not prevent garbage collection.

Solution #2: Declare a weak reference

Maintaining a list of event handlers for every object is a bit cumbersome, so luckily Adobe has added a better solution: declare the reference weak when the event is added to the object. In ActionScript 3, Adobe added an overloaded version of addEventListener() that takes 5 parameters. By setting the 5th parameter (useWeakReference) to be true, the event handler and its associated objects will become eligible for garbage collection if the weak reference event handlers are the only ones left.

For example, let’s say you defined a timer to run for 10 seconds and then stop:

var myTimer:Timer = new Timer(10,0);
myTimer.addEventListener(TimerEvent.TIMER,myTimerHandler);

As the code is written, objects referenced by myTimerHandler cannot be garbage collected, even if the timer was never started. Alternatively, if you add the overloaded parameters to the method ,false,0,true, objects referenced by myTimerHandler can be garbage collected:

var myTimer:Timer = new Timer(10,0);
myTimer.addEventListener(TimerEvent.TIMER,myTimerHandler,false,0,true);

Keep in mind, there are situations when it makes sense to define event handlers using strong references even on temporary objects. Just remember to explicitly remove the event handler if you want to reclaim the allocated memory.

Some developers, though, have made cases that Solution #1 is the only correct solution, and that using weak references is not sufficient for proper memory management.

Conclusion: Refactor your code with overloaded parameters

My suggestion to the reader is search through their code for all occurrences of addEventListener. For each occurrence, decide whether the event handler should live for the life of the application and never be garbage collected, or can be removed before the application completes. If it is the former, and the event handler should live indefinitely for the application, then do nothing. If it is the latter, and the event handler may be discarded at some point, add the parameters ,false,0,true to the event handler creation. This will preserve the existing functionality but allow it to be garbage collected. Keep in mind, though, that the event handler will not be garbage collected if you maintain strong or explicit references to it elsewhere. In the previous example, if you defined myTimerHandler to be a class-level member function, then the event handler would not be eligible for garbage collection with or without the change to the event handler registration.

Installing the Adobe AIR 2 SDK in Eclipse

While Adobe makes adding a new Flex SDK easy – just drop a new version folder alongside another version and point Eclipse to the new directory – they make installing a new Air SDK quite frustrating. The Air SDK is installed inside the Flex SDK, so to truly install a new version, you must hijack your existing Flex SDK and replace a few dozen files within the folder structure. It is my sincere hope that Adobe moves away from this coupling and allows Air SDKs to be more easily upgraded in the future without the need to piecemeal copy them onto an existing Flex SDK installation.

Below is short story of how I managed to get my application to build Adobe Air 2.0 applications with Eclipse and the steps I took to resolve the numerous issues that cropped up.

1. Download and Install the Air 2 Runtime and Air 2 SDK

The first step is to download the Air 2 runtime which installs itself within the operating system. The second step is to download the Air 2 SDK, which downloads as a zip file. Assuming you have Flex 2, 3, or 4 installed, there should be a plugin sdks directory that contains an AIR runtime folder such as:

Flex SDK root: C:\Program Files (x86)\Adobe\Flex Builder 3 Plug-in\sdks\3.2.0
Air Runtime root: C:\Program Files (x86)\Adobe\Flex Builder 3 Plug-in\sdks\3.2.0\runtimes

Backup (for safety) the runtimes folder and replace it with the one in the Air 2 SDK zip file. Congratulations, you have just installed the Air 2 SDK!

Note: You may have multiple Flex SDK folders on your computer with multiple versions. You should use whichever one your version of Eclipse is pointing to, or create a new version (“3.2.0-air2” for example), and point Eclipse to this new version.

2. Turning on Air 2 within your application

Next, try building and running your Air project within Eclipse using the Flash Builder plugin. Upon launching the compiled application, you will likely see the message “IIMEClient error”:

IIME Client Error

A little bit of digging, shows the error is caused by a combination of the following:

Flash Builder 4 + AIR 2.0 SDK + Application with AIR 1.5 app-descriptor

In short, the app-descriptor for my application (such myApplication.xml) requires that the namespace reference xmlns be changed from

http://ns.adobe.com/air/application/1.5
to http://ns.adobe.com/air/application/2.0

Once you change the version number, the application will compile and run without issue.

3. Full installation of Air 2 SDK required for Release Builds

While you can now build and run Air 2 applications within Eclipse, you will receive an error if you try to Export them as Release Builds. To resolve this issue you go back to step 1 and this time merge all of the files from your Air 2 SDK zip file onto your Flex SDK directory. This is especially risky, since you are replacing dozens of files throughout the SDK, so this time make sure to backup your entire Flex SDK folder. You should expand the zip file and replace over all files and folders.

For example, in the sub-directory bin replace two files from the Flex SDK: adl.exe and adt.bat, but leave the existing files in that folder in place. The rest of the files contained in the zip file should be used to replace the existing SDK files in a similar manner. Many operating systems offer a merge functionality that will only replace the files that have changed and keep the existing files in place.

After you are done, you can open a command window, navigate to the SDK bin folder and type “adt -version” to determine which version of Air is installed. My thanks to Michael Christoff for this part of solution that works on any OS.

The Result

Now you should be able to build, run, and release an Adobe Air 2 application. You can test this by exporting your Air Application to a .air release file, then installing your application using the Air 2 graphical runtime which was installed at the beginning of this process.