The AIR HTMLLoader class and its control wrapper, the <mx:HTML> component, are a pretty straightforward way to display web content within an AIR application.
While the combination of HTMLLoader and ActionScript is pretty powerful, HTMLLoader and <mx:HTML> do pose some limitations if you are trying to develop a serious web application in AIR. But one of these limitations can be worked around with a little code.
Out of the box, HTMLLoader is not able to handle links within HTML documents which open in a new window (by setting target="_blank" in your link tag, for instance). If you click on the link, and the navigateInSystemBrowser property in the HTMLLoader object is set to false, then absolutely nothing happens.
Sönke Rohde offers an innovative solution based on modifying the document after it has been loaded, trapping every instance of target="_blank" and rewriting the node to call a custom JavaScript function that then opens the link in the system browser. The net effect is that only links that open external windows appear in the system browser; all other links continue to open in-place. (Changing the navigateInSystemBrowser property affects all links).
My application, and others I would guess, does not find opening new documents in the default system browser desirable, but yet we want to handle these “open in new window” clicks. Since my application is single-window, I’d simply like to redirect these links to open in the current browser.
Doing so is relatively straightforward, but not spelled out in detail, so I thought I’d post the solution here for the benefit of all you copy/paste coders in the crowd (of which I consider myself a proud, card-carrying member).
First, you need to define an subclass of the AIR HTMLHost class. This class provides a series hook functions to allow you to customize various browser events invoked by JavaScript code, including Window.open() among them.
package com.mycorp
{
import flash.html.HTMLHost;
import flash.html.HTMLWindowCreateOptions;
import flash.html.HTMLLoader;
public class MyHTMLHost extends HTMLHost
{
public function MyHTMLHost(defaultBehaviors:Boolean=true)
{
super(defaultBehaviors);
}
override public function createWindow(windowCreateOptions:HTMLWindowCreateOptions):HTMLLoader
{
// all JS calls and HREFs to open a new window should use the existing window
return htmlLoader;
}
}
}
The createWindow function is the magic bit here. The createWindow method is tasked with returning an HTMLLoader responsible for opening the requested link. Normally, following Adobe’s docs, you’d create a new HTMLLoader, attach it to a newly created NativeWindow, and return the HTMLLoader instance.
In our case, we want to load links in the existing HTMLLoader, we just override createWindow and return the existing HTMLLoader. (Don’t worry about where the htmlLoader property is set; that’s handled for you automatically by the next bit of code).
After you’ve defined this class, you then need to instantiate it and assign it to the htmlHost property on the HTMLLoader object. The HTMLLoader will then use the instance of your custom HTMLHost, and as a bonus will assign its htmlLoader property for you.
private function onCreationComplete():void
{
// ... other stuff ...
htmlLoader.htmlHost = new MyHTMLHost();
}
Next, run your application and visit a document with a target="blank" link:
<p>Click <a href="http://www.google.com" target="_blank">here</a> to open a document in a new window.</p>
And the link should create a new document in the current HTML browser component. Success!








Shipping your Android application: a checklist
I recently completed my first Android application. I learned a lot from the project, which was a ton of fun.
Out of that project popped a checklist of things to do in order to make sure your application is “industrial strength” before it heads out into the Android Market. This isn’t a complete list, but it did grow out of our project backlog as we polished the app and drove towards ship date.
Where possible, I point to a few resources for more information, and other points are good subjects for future blog posts.
Manifest
<uses-sdk>element. Example:android:targetSdkVersionattribute to indicate which SDK you built against, and for which the app is intended to run onandroid:minSdkVersionto indicate the lowest revision of the SDK that the app will run against. Apps built against newer versions of the SDK will generally run on older devices, as long as you are careful not to invoke API calls that do not exist in the older SDK.<supports-screens>element in our manifest, withandroid:SmallScreens="false".<uses-configuration>elements appropriately. You can specify more than one for an “or”-style grouping of supported configurations. This example says the application is compatible with both stylus-driven (resistive) and finger-driven (capacitance) touchscreens, but one or the other is required:android:themeattribute of the<application>element. Here’s an example manifest entry:<application android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.Light" android:debuggable="false" android:name="com.myorg.myapp.MyAppSubclass"> <!-- blah blah blah --> </application>Packaging
resfolder hierarchy. It’s amazing how assets can get orphaned as your application’s interface moves through iterations of design. Leaving them in the app only increases the size of the APK and takes up more space on your user’s devices.LogAPI will antagonize the garbage collector if you do string concatenation in your log messages (and we all do that).debuggable=trueitem in yourapplicationelement tofalse(as in the above example).Resources
dpunits in your layout files) wherever possible, and more likely than not things will “just work” when your app runs on different screen resolutions.Application
onPause/onStop/onDestroyhandler methods within your activities.Applicationobject:android:nameattribute of the<application>element in your manifest.onLowMemoryhandler, and be a nice citizen.onCreate,onTerminate. Create global items to share between activities inonCreate, and clean up resources as best you can inonTerminate.configChangesin your Manifest andonConfigurationChangedin yourActivityclasses, to intercept default behavior for system configuration changes. If you don’t do this, Android will kill and restart your activities to handle them:onConfigurationChangedin your activity class when the user rotates the device, rather than Android stopping and restarting your activity:<activity android:name=".activity.MyAwesomeActivity" android:label="@string/app_name" android:configChanges="orientation" > <!-- remainder of activity declaration goes here --> </activity>And here is how you would respond to it within your Activity:
@Override public void onConfigurationChanged(Configuration newConfig) { // TODO do something here super.onConfigurationChanged(newConfig); }onConfigurationChangedwhen the user changes the keyboard configuration, again without killing your activity:<activity android:name=".activity.MyAwesomeActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden" > <!-- remainder of activity declaration goes here --> </activity>onConfigurationChanged. But you can also not bother to overrideonConfigurationChanged, which effectively disables Android’s activity kill/restart mechanism. You should do this only if you are certain your activity will not be affected by the change in question.onLowMemoryhandlers.External Storage
Context#getExternalFilesDirmethod to get a directory for your temp files, which will automatically be cleaned up by the Android OS if the user uninstalls your application.getExternalFilesDirwould provide. When the user updates to Froyo, Android will still auto-remove the contents on app uninstall.Networking
Battery Life
Most of these suggestions came from the Google I/O presentation Coding for Life — Battery Life, That Is:
nullwhen you are done with them, to release them to the garbage collector earlier rather than later.Matcherobjects:Matcher#reset(newString)to reuse the matcher object.StringBuilderobject instances:StringBuilder#setLength(0)– good as new!StringBuilders) in classes such asListAdapter, where they get called often.android:keepScreenOn="true"attribute in your XML layouts for controls that must keep the screen alive.