This article focuses on the specifics of developing for Android, iOS, OS X, and Windows. Extensions are also supported on other platforms, including BlackBerry Tablet OS.
Extensions support three use cases: deep platform integration, legacy code reuse, and maximum performance.
Mobile devices offer an array of unique capabilities. Some are software, such as the sophisticated Android notification mechanism. Others are hardware, like the dual screens in the Sony S2 tablet.
While Adobe does not take a lowest common denominator approach when adding APIs to AIR, we do focus on creating APIs that can be used across multiple devices. Thus, it is unlikely that AIR will support a dual-screen API any time soon, since the API would be inoperable on nearly all devices.
Extensions can be used to add these capabilities to the runtime as needed. Extension authors can provide API mappings for platform-specific features, and can do so with as little, or as much, sophistication as they need.
Development shops with longer histories often have code from past projects that they would like to bring forward into new applications. In many cases, such code is even relatively portable, written in C or C++ and expecting POSIX APIs.
Regardless of the legacy code’s origin, the extensions mechanism allows the code to be wrapped up with an ActionScript API, and thus be used within an AIR application.
Extensions can be used to implement computationally intensive code that requires the highest possible level of optimization. Applications that leverage these compute kernels can implement them in extensions, and then invoke them from ActionScript.
Using native code enables several important optimizations. Firstly, and perhaps most obviously, the computationcan be coded in C or assembly for maximum performance. Second, native code can take advantage of multiple cores, which can provide a significant performance boost for parallelizable algorithms. Finally, native code may be able to take direct advantage of the GPU for off-loading certain calculations.
Two-tier application architecture
The introduction of extensions brings with it not just a new feature but a new way of writing applications.
Prior to extensions, applications have been written entirely in ActionScript. ActionScript is a capable language, well suited to user interface and core application logic. However, no language is well suited to all tasks, and ActionScript is no exception.
With extensions, applications can be written in two tiers: ActionScript on top, coordinating application and presentation logic, and native code below, providing deep integration, legacy code reuse, and performance.
This architecture is nothing new; applications have been written using this approach for decades, and with any number of languages used in combination. By enabling it in AIR, we allow its many benefits to be brought to bear on AIR application development.
Extensions as ActionScript libraries
An extension mechanism needs a model, that is, a methodology by which new APIs are bound into the runtime. AIR extensions are modeled as ActionScript libraries that contain native code.
This model allows the creation of a native extension to be separated from its consumption. Thus, extensions can be developed independently, published separately, licensed, sold, and so on as can any ActionScript library. Alternatively, they can be created and consumed by the same developer, possibly even for use with a single application.
While an extension necessarily includes native platform code, each extension is nonetheless intended to be useable across multiple platforms. That is, one need not build a separate extension to leverage a legacy library across each targeted platform. Rather, a single extension contains a platform-specific version of that library for each targeted platform.
Developing extensions in this model is a bit more work for the extension developer but enormously easier for the application developer. It allows an application developer to use a single, cross-platform ActionScript API throughout an application, and rely on the extension to provide the necessary platform-specific implementation. This, of course, is how the built-in ActionScript APIs work, and it’s this aspect of the extension model that makes them true extensions to the runtime.
Extensions separate creation and consumption of the extension. This separation makes it possible for developers to creative an extension not only for their own use, but also for others to use. This allows a developer to extend the runtime facilities for all other AIR developers. OEMs can also use this capability to extend the runtime in ways specific to their devices.
Before writing your own extension, it’s worth checking to see if the functionality you need is already available; perhaps someone else has done some of the work for you. Extensions created by other developers can be published through any of the normal means: code-sharing sites, blogs, and so on.
Note that access to device-specific capabilities implemented by device manufacturers will typically require obtaining the extension from the manufacturer itself. The manufacturer’s SDK or other developer information should contain the necessary information.
When acquiring an extension from external sources, think carefully about whether or not you trust that provider. As with externally sourced ActionScript code, the code in the extension becomes part of your application and runs with full application privileges. By incorporating an extension into your application, you take responsibility for any security issues that extension might contain.
On Android, two extension models are supported: Java archives for extensions developed with the Android SDK, and shared libraries for those developed with the Native Development Kit (NDK). Both are appropriate for some set of extensions; simply pick the one that best suits your use case. (One can, of course, use JNI to bridge between Java and native code on Android, should that also be necessary.)
On Android, resources such as images are compiled into each application and accessed via constants in the generated class R. This mechanism doesn’t support composition; there is only one instance of R available. Android extensions can include resources but, to work around this limitation, they must be accessed via an API provided by the runtime.
To include resources in your Android extension, first place them in the “res” subdirectory of the Android platform directory, using the same naming conventions and file structure as for a regular Android application. At packaging time, resources from all extensions are merged into the resources directory of the main application. In order to avoid name conflicts, extensions should use a unique prefix for their resources.
These resources will be merged, as will the code in the Java archive, into the compiled dex and resources files of the main application. They can then be accessed via the
FREContext.getResourceId() method, which takes the desired resource ID as an argument. In other words, instead of accessing an image as, for example, R.drawable.background_image, use
getResourceId( "drawable.background_image" ).
Note that this API is available only in Java, as the Android resource mechanism is a Java construct, not expected to be accessed by extensions using native Android development.
The Java FREContext class also provides, on Android only, a
getActivity() method that returns the main application Activity, which in turn is required by a variety of Android APIs.
iOS is a unique platform in that it does not permit applications to be composed of shared libraries. On iOS, extensions instead take the form of static libraries that are linked into the main executable at packaging time. (This has relatively little cost at packaging time, as AIR applications packaged for iOS are already compiled to ARM code and run through the linker.) This is why the static library (.a) that is in the extension package does not appear separately in the resulting application.
On the other hand, it’s conventional on iOS to keep resources, such as images, as external files that are separately loaded at runtime. Such resources can be bundled with the extension, at packaging time, will be copied into the standard application resources directory. This permits the regular NSBundle APIs to be used to load these resources, even from within the extension.
Note that the iOS implementation effectively flattens the namespaces used in both the executable (symbols in the static library) and in the file system (filenames of resources). As a result, extensions must take extra care to choose unique names in order to avoid conflicts. A prefix unique to the extension is typically sufficient. In particular, developers should take care not to use the default name Localizable.strings, as it would certainly conflict with the default Localizable.strings file used by the main application.