Monkey patching is replacing parts of the flex framework with your own classes. For example, you can copy mx.core.UIComponent to src folder of your project, modify it, and flex will use your modified class instead of framework’s one.
Genereally it’s considered to be evil, and I actually agree with this point of view. But sometimes you do actually need to use monkey patching – to fix bug in a flex framework or to extend it somehow in case simple inheritance doesn’t work.
Everything goes fine unless you decide to use flex framework as an RSL (Runtime Shared Library). After that your monkey patches are no longer applied.
We had this exact problem in our project, CommuniGate Pronto!, and I haven’t found a solution in the internet. But I’ve come up with an inelegant solution which nevertheless does the job quite well.
A bit of theory
Flash player handles class loading in the following way – in any application domain only one class implementation can exist. And if class N already exists in application domain, then flash player will never replace it with another implementation.
RSL (runtime shared library) – is just a .swf-file, which contains classes definitions and implementation. Framework RSL – is a .swf-file with definitions and implementations of all the classes of Flex Framework.
As you know, I suppose, flash application consists of frames, as in movie clip. Some of you may remember calling flash applications flash movies. Each frame can contain almost any data, including class definitions and implementations.
Flash player loads application frame by frame. As soon as it downloads first frame, it starts to play it (execute it, if you like). It binds class definitions to implementations, executes code, plays sound, draws sprites and so on.
Flex compiler (mxmlc) creates application which consist of two frames. In first frame it puts minimal amount of data – just preloader and initialization classes in order for preloader to appear quickly. In second frame it puts all the classes and assets used in the application. Including framework.
The interesting fact that actually creates the problem is that RSL are downloaded by preloader in the first frame. And all the framework classes definitions are loaded into application domain in first frame. But monkey patched classes are stored and defined in second frame and therefore don’t override already loaded RSL implementations.
Solution
One of the possible solutions of this problem is creating a custom preloader (which is a must for a professional-looking application) and creating references to monkey patched classes there. That way these classes (and all their dependencies) are included in the first frame of the application. But this increases size of the preloader significantly and actually defeats it’s purpose, so I don’t think that solution is appropriate. This solution is described in detail by James Ward in his blog.
The better solution becomes quite obvious after understanding the way flash player works. Generally, you should create your own RSL containing monkey-patched classes and make sure it is loaded before flex framework RSL.
Creating a monkey-patched library
You can create RSL using Flex Builder built-in features. Create a flex library project, link your project to this library and make it link as RSL (project properties -> Flex Build Path -> Library path -> Link type under library folder). This way leads to rather fat RSLs because Flex includes most of the flex framework there. But it may be appropriate for you if you are patching some tiny class with a few dependencies. But this was inappropriate for me, so I’ve chosen a hardcore way.
It’s known that mxmlc allows to control which classes should be included in resulting .swf. It’s essential both for modules and RSLs and can be controlled by -externs, -external-library-path, -includes, -load-externs and other options. You can take a look at the list of included classes and their dependencies by using -link-report option. More info on compiler options can be found in documentation.
The ideas is to create simple application with no dependencies, force mxmlc to include monkey patched classes, create it’s link report and exclude all the unnecessary classes from it.
Make sure you don’t inlude references to the patched classes, because it can break everything up – in frame one flex framework is not yet loaded and all the dependencies won’t be satisfied.
So, the class can look like this:
package
{
import flash.display.Sprite;
public class monkeylib extends Sprite
{
public function monkeylib()
{
super();
}
}
}
After that you compile it with mxmlc and generate a link report:
"c:\Program Files\Adobe\Flex Builder 3\sdks\3.2.0\bin\mxmlc" -includes+="mx.controls.Button" -link-report=monkey_report.xml monkeylib.as
The next step is to exclude the classes you want to be patched from the link report and use it as a source for -load-externs. You should also exclude main application class (monkeylib in our case).
Let’s use Button
class as an example. You use Ctrl+F in your favorite editor and look for :Button
:
<script name="C:\Users\Nikita\Documents\Flex Builder 3\monkeylib\src\mx\controls\Button.as" mod="1254311407324" size="18581" optimizedsize="15328">
<def id="mx.controls:Button" />
<pre id="mx.core:IButton" />
<pre id="mx.managers:IFocusManagerComponent" />
<pre id="mx.core:IDataRenderer" />
<pre id="mx.core:UIComponent" />
<pre id="mx.controls.listClasses:IDropInListItemRenderer" />
<pre id="mx.core:IFontContextComponent" />
<pre id="mx.controls.listClasses:IListItemRenderer" />
<dep id="mx.styles:ISimpleStyleClient" />
<dep id="flash.events:Event" />
<!-- ..... -->
</script>
You remove this block completely or comment it out. Note the series of <pre/>
tags. I believe, ‘pre’ stands for ‘prerequisite’. It’s actually the list of the class which this class extends and interfaces, which it implements. You must also include all these classes inside your library to make it work. Otherwise you’ll get VerifyError when loading your library.
Apart from these <script/>
blocks, you must also remove (or comment out) all the <pre/>
which reference the class you want to be included in your library, otherwise it won’t be included.
So, for Button
class we should also include IButton, IFocusManagerComponent, IDataRenderer, IDropInListItemRenderer, IFontContextComponent, IListItemRenderer, UIComponent
. Note, that UIComponent
is present in this list. That means you should also include all the classes required for UIComponent
: IPropertyChangeNotifier, ISimpleStyleClient, IStyleClient, IDeferredInstantiationUIComponent, IRepeaterClient, IValidatorListener, IInvalidating, ILayoutManagerClient, IUIComponent, FlexSprite, IFlexDisplayObject, IFlexModule, IConstraintClient, IToolTipManagerClient, IChildList, IAutomationObject, IStateClient, IUID
.
You can check out the resulting report: monkey_report.xml
After that you simply need to compile your library using the report you’ve created as a source for -load-externs
:
"c:\Program Files\Adobe\Flex Builder 3\sdks\3.2.0\bin\mxmlc" -includes+="mx.controls.Button" -load-externs=monkey_report.xml -link-report=libreport.xml monkeylib.as
Using -link-report
again is a good idea – it let’s you control which classes have been included in your application, and which haven’t.
If everything went OK you’ll get a .swf file with a file size of 30kb (instead of 110kb without this sophisticated link management).
Loading patched library before framework RSL
Ok, I was unable to find a proper way to load patched library before RSL – mxmlc always placed framework RSL before mine in the loading list. So I had to involve a bit of black monkey patch magic again. I’ve modified <code>mx.preloaders.Preloader</code> class to place my library to the begging of rsl list. You can get test project here Lines 179-184 of Preloader
class:
// monkey patch
var monkeyRSLNode:RSLItem = new RSLItem("monkeylib.swf");
if(rslList.length > 0)
monkeyRSLNode.rootURL = RSLItem(rslList[0]).rootURL;
rslList.unshift(monkeyRSLNode);
// ------------
That’s all. Feel free to ask any questions in comments.
Filed under:
flex by Hrundik