Posts tagged cache

Truly Cached Flex Modules

I was working on a project recently and I discovered (or possibly misunderstood – see disclaimer) that Flex does not cache modules correctly, or – at the very least – it does not do it effectively according to my tests.

My test was conducted using Flex SDK 3.4.0.9271, Firefox 3.5.7 and Flex Builder Professional 3.0.214193

According to this page (under the Preloading modules heading), Adobe asserts the following:

When a module is loaded by the Flex application for the first time, the module’s SWF file is transferred across the network and stored in the browser’s cache. If the Flex application unloads that module, but then later reloads it, there should be less wait time because Flash Player loads the module from the cache rather than across the network.

Module SWF files, like all SWF files, reside in the browser’s cache unless and until a user clears them. As a result, modules can be loaded by the main application across several sessions, reducing load time; but this depends on how frequently the browser’s cache is flushed.

I found the above claims to be demonstrably false, or at least just plain inefficient…

Let me qualify this:
According to my tests (conducted with my Firefox browser cache turned both on and off), i found that the application’s memory usage keeps growing exponentially when switching between two loaded modules. There was a variance (as you’d imagine) when the browser cache was left on but nonetheless, the memory usage keeps growing steadily.

In the code sample below, I have extended the functionality of the mx.modules.ModuleLoader class, and added a Dictionary (with a little logic) to manage the (ostensibly) effective caching of previously loaded modules. I have not tested this class extensively, but all the tests that I conducted seemed to produce a significant memory and speed improvement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package
{
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
 
	import mx.events.FlexEvent;
	import mx.events.ModuleEvent;
	import mx.modules.IModuleInfo;
	import mx.modules.ModuleLoader;
	import mx.modules.ModuleManager;
 
	/**
	 * This class manages the loading, unloading and caching of Flex Modules
	 * This is a modified version of the mx.modules.ModuleLoader class
	 *
	 * @author Danny Kopping - danny@ria-coder.com
	 */
	public class CachedModuleLoader extends ModuleLoader
	{
		private var map:Dictionary = new Dictionary();
 
		private var _url:String = null;
		private var module:IModuleInfo;
		private var loadRequested:Boolean = false;
 
		public function CachedModuleLoader()
		{
			super();
		}
 
		override public function set url(value:String):void
		{
			if (value == _url)
				return;
 
			if (module)
			{
				module.removeEventListener(ModuleEvent.PROGRESS, moduleProgressHandler);
				module.removeEventListener(ModuleEvent.SETUP, moduleSetupHandler);
				module.removeEventListener(ModuleEvent.READY, moduleReadyHandler);
				module.removeEventListener(ModuleEvent.ERROR, moduleErrorHandler);
				module.removeEventListener(ModuleEvent.UNLOAD, moduleUnloadHandler);
 
				//module.release();
				module = null;
 
				if (child)
				{
					removeChild(child);
					//child = null;
				}
			}
 
			_url = value;
 
			dispatchEvent(new FlexEvent(FlexEvent.URL_CHANGED));
			removeAllChildren();
 
			if (_url != null && loadRequested)
			{
				if(!map[_url])
					loadModule();
				else
				{
 
					child = map[_url];
					addChild(child);
				}
			}
		}
 
		override public function get url():String
		{
			return _url;
		}
 
		override public function createComponentsFromDescriptors(recurse:Boolean = true):void
		{
			super.createComponentsFromDescriptors(recurse);
 
			loadRequested = true;
			loadModule();
		}
 
		override public function loadModule(url:String = null, bytes:ByteArray = null):void
		{
			if (url != null)
				_url = url;
 
			if (_url == null)
			{
				//trace("loadModule() - null url");
				return;
			}
 
			if (map[_url])
			{
				//trace("loadModule() - already created the child");
				return;
			}
 
			if (module)
			{
				//trace("loadModule() - load already initiated");
				return;
			}
 
			dispatchEvent(new FlexEvent(FlexEvent.LOADING));
 
			module = ModuleManager.getModule(_url);
 
			module.addEventListener(ModuleEvent.PROGRESS, moduleProgressHandler);
			module.addEventListener(ModuleEvent.SETUP, moduleSetupHandler);
			module.addEventListener(ModuleEvent.READY, moduleReadyHandler);
			module.addEventListener(ModuleEvent.ERROR, moduleErrorHandler);
			module.addEventListener(ModuleEvent.UNLOAD, moduleUnloadHandler);
 
			module.load(applicationDomain, null, bytes);
		}
 
		override public function unloadModule():void
		{
			if (child && contains(child))
			{
				removeChild(child);
				child = null;
			}
 
			if (module)
			{
				module.removeEventListener(ModuleEvent.PROGRESS, moduleProgressHandler);
				module.removeEventListener(ModuleEvent.SETUP, moduleSetupHandler);
				module.removeEventListener(ModuleEvent.READY, moduleReadyHandler);
				module.removeEventListener(ModuleEvent.ERROR, moduleErrorHandler);
 
				module.unload();
				module.removeEventListener(ModuleEvent.UNLOAD, moduleUnloadHandler);
				module = null;
			}
 
			if(map[_url])
			{
				delete map[_url];
			}
		}
 
		private function moduleProgressHandler(event:ModuleEvent):void
		{
			dispatchEvent(event);
		}
 
		private function moduleSetupHandler(event:ModuleEvent):void
		{
			// Not ready for creation yet, but can call factory.info().
 
			dispatchEvent(event);
		}
 
		private function moduleReadyHandler(event:ModuleEvent):void
		{
			child = module.factory.create() as DisplayObject;
			dispatchEvent(event);
 
			if (child)
			{
				var p:DisplayObjectContainer = parent;
				// p.removeChild(this);
				addChild(child);
 
				map[url] = child;//ModuleManager.getModule(_url);
				//trace(map + ":" + url + ":" + map[url]);
			}
		}
 
		private function moduleErrorHandler(event:ModuleEvent):void
		{
			unloadModule();
			dispatchEvent(event);
		}
 
		private function moduleUnloadHandler(event:ModuleEvent):void
		{
			dispatchEvent(event);
		}
	}
}

The usage of this class is exactly the same as the regular mx.modules.ModuleLoader class. I hope this helps!

Download this file: Download

**DISCLAIMER**
I know a bit about Flex modules from hours of obsessing over them, but i do not know everything. From my tests of the efficacy of the above code & explanation, i found that it reduces memory usage and increases the general usability of my Flex project; i could be very wrong on this topic, and if i am – please tell me. Maybe i’m just an idiot, but it seems to work…
Vote in HexoSearch

Fixing that annoying feature of List controls

Don’t you just hate it… Doesn’t it just piss you off when you want to display a gallery of images in Flex and Flex decides that it’s actually going to set it so that when you scroll, it’ll only then load the images displayed offscreen? I find this particularly “ball-ache-ish” with the TileList component.

I was browsing through the documentation like i had many times before, but this time i noticed the offscreenExtraRowsOrColumns property. At first you think “wtf” but then i got curious…

Say you have 25 images in a grid that displays one row of 5 images at a time:

The row in green will be the only row visible at this point in time. When you scroll, Flex will chuck out the first row and create the second row:

…and so on. Isn’t that just damn irritating? (If you’re thinking “nah, what’s the big deal?” then you obviously haven’t tried this before).

Anyway, the way to fix it is to tell Flex how many rows there are offscreen so that it can create them in the interim. This seems like a bit of a weird method because in the navigator classes (ViewStack, et al) there is a creationPolicy property which – when set to “all” – will create all the non-visible components before they’re navigated to, thus slowing down the processing a bit (depending on your app) but also speeding up the switch between views. Alas, no such property to be found in the TileList component. Can anybody tell me why? I think i should code a modified version of the TileList component to accept this property… +1 to TODO list… *sigh*

Hopefully this will help those of you experiencing the ball-ache that i did.Vote in HexoSearch