Posts tagged cache
Truly Cached Flex Modules
Jan 31st
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…
Fixing that annoying feature of List controls
Nov 15th
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.