Extending Adobe AIR

Prior to Adobe AIR 3, only Adobe could add core features to the runtime. Developers requiring new features could only wait. Now developers can extend the runtime themselves. This new capability allows developers to:

  • Achieve deeper integration with target devices
  • Incorporate legacy native code in their application
  • Achieve maximum performance for critical code

In this article, I'll discuss all of the essential aspects of this feature:

  • What you can achieve with native extensions for Adobe AIR
  • How to use an extension in an application
  • How to create your own extensions

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 overview

Extensions support three use cases: deep platform integration, legacy code reuse, and maximum performance.

Deep integration

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.

Legacy code

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.

Performance

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.

Multiplatform model

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.

Using extensions

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.

Discovering extensions

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.

Developing with extensions

Currently, not all development tools have direct support for extensions. Until they do, you can generally make use of them by working with them as ActionScript libraries. For example, in Flash Builder, you can add an extension to your project by following the same steps you use to add a SWC dependency. Just make sure you select the option to use external linkage as extensions are bundled with, not compiled into, the application.

When you add an extension to your application, the linkage from the application to the extension is recorded in the application's descriptor file. It's nothing more than a list of extension IDs:

<extensions> <extensionID>com.example.Extension1</extensionID> <extensionID>com.example.Extension2</extensionID> </extensions>

Note that an extension must be listed here to be used by the application.

Debugging with extensions

When you debug an application that uses one or more extensions on the desktop, the extension must support Windows or Mac OS X, according to the platform on which you're working. If you're building an application for the desktop, this should work as expected: the behavior of the extension during debugging will match that of the installed application.

If you're targeting a mobile or TV device, note that you'll still get the Windows or Mac OS X implementation of the extension when running in the simulator. This happens because the simulator is just that: a simulation of mobile or TV behavior, and not an emulation of the mobile or TV hardware.

The behavior of the extension when run in the simulator is up to the extension itself. It may provide a desktop implementation, or provide for a simulation of mobile and TV behavior when run on the desktop. Or, it may not run on the desktop at all. (If you're authoring an extension, see "Creating Extensions" below for details on how to implement these approaches.)

If your authoring tool doesn't directly support extensions, launch your application instead at the command line, using the adl tool that is packaged with the AIR SDK. Assuming you are launching with a debug SWF, adl will attempt to connect to any waiting debugger.

To launch with adl, simply place unpackaged copies of the extensions you are using in a single directory and pass that location to adl via the -extdir argument. (A .ane file is a ZIP file, so you can unpackage one using an unzip tool.)

Note that, while unconventional, the name of the extension directory must end in ".ane". For example, suppose you receive the file SampleExtension.ane. To use it with adl, you could unpackage it into a directory with the same name. The relevant portions of the adl command line are then:

adl -extdir extensions app.xml

where "extensions" is a directory containing the unpackaged extension, i.e.:

extensions/ SampleExtension.ane/ META-INF/ . . .

Using the unpackaged form of the extension with adl makes it easier to develop an extension and an app at the same time. You can set up your extension projects to build to the .ane directory, and then pass that same directory to adl. (If you are using the extension but not developing it, just unpackage it one time, and you're all set.)

Packaging your application

When it comes time to package your application, you can do so at the command line with adt. Like adl, adt accepts an -extdir argument. Unlike adl, adt requires that the extensions in that directory be complete, packaged versions. (The assumption here is that, at packaging time, your extensions are also complete and packaged.) For example:

adt -package -target apk . . . app.apk app.xml -extdir extensions

If you inspect the file set of the resulting application, it's easy to see that a portion of each extension is added, structure intact, in the new META-INF/AIR/extensions directory. For example:

META-INF/ AIR/ extensions/ com.example.Extension1/ META-INF/ ANE/ extension.xml

Other packaging transformations are more complex, and vary by platform. Typically you don't need to be concerned with these details, as they are handled by the AIR packaging technology.

Creating extensions

Developing an extension requires a number of skills, including API design, assessing implementation trade-offs, familiarity with the runtime extension API, and working with specific tools and properties of each targeted platform.

Development tools

Developing an extension requires writing both ActionScript code, as every extension has an ActionScript layer, and native code on at least one platform.

ActionScript can be written using the freely available Flex SDK, Flash Builder, or other tools. The ActionScript portion of the extension will need access to the ExtensionContext API, which is part of the AIR AS3 API surface. Its definition is found, as is the rest of the AIR AS3 API, in airglobal.swc in the Flex SDK.

The native portions of an extension are written with the corresponding native development tools, and the form of the native portion is specific to each platform as well:

Platform Form
Android Java Archive (.jar) or shared library (.so)
iOS Static library (.a)
OS X Framework (.framework)
Windows Dynamic Link Library (.dll)

In general it's possible to use platform-specific structures and conventions, such as resource bundles on Mac OS X. Further details are covered in "Packaging extensions" below.

Public API definition

Your extension will be consumed as an ActionScript API, and that's often a great place to start your design work. Ultimately, the functionality you need to expose via this API will dictate what your implementation must or cannot do.

When developing your API, think about how it will operate across multiple platforms. Sometimes this is straightforward; if you're integrating legacy code, for example, it likely runs the same on each platform. However, when adding deeper platform integration, you may find that there's variation in the underlying platform support that can or should be exposed.

Many built-in AIR APIs address similar situations and use API patterns that you can adopt in your extension APIs:

  • If your extension includes functionality that is operable on only some of its supported platforms, you can indicate this at runtime via an isSupported or supportsX properties. The existing NativeWindow API, for example, declares both NativeWindow.isSupported (are native windows supported at all) andNativeWindow.supportsTransparency (are transparent native windows supported).
  • If functionality varies between devices, you can indicate this at runtime via APIs that surface this variation. Applications can use this information to adapt their behavior. For example, the existingMultitouch.supportedGestures property reports to applications, at runtime, which gestures the underlying platform supports—something that varies between different mobile platforms.
  • Make inoperable APIs fail gracefully. For example, the NativeApplication.startsAtLogin property works only on the desktop, but is a no-op when used on a mobile or TV device. Thus applications can simply call it on all platforms, rather than adding the logic required to make the call conditional.

Extension APIs should also adopt ActionScript conventions where possible. For example, ActionScript APIs use events for notification; extension APIs should do the same. In some cases, this may require translating from different conventions used by the underlying platform APIs.

When you define your public API, compile it into a SWC using Flash Builder, compc, or an equivalent tool. You'll use this SWC again later when creating the extension package.

ActionScript/Native code boundary

Extensions contain both ActionScript and native code, and the boundary between the two can vary arbitrarily. The boundary can also be different on different platforms. This flexibility can be used to your advantage, as the extension author, by allowing you to choose the most suitable implementation language for each portion of the extension.

Typically, it's advantageous to write as much code as possible in ActionScript as development in ActionScript is relatively fast, safe, and portable. In some cases, it may even be possible to write the implementation for specific platforms entirely in ActionScript, and extensions allow this.

There are reasons to move logic into native code as well, even beyond legacy code integration. Using native threading facilities, native code can be executed on background threads, thus improving application responsiveness. (More on this later.) Native code may also have access to algorithms that, while implementable in ActionScript, may be provided in optimized form by the OS.

When developing your ActionScript layer, compile a separate SWF for each platform that has a unique implementation. (If two or more platforms share the same implementation, you can reuse the same SWF.) Since each SWF defines the public API of the extension for the platform on which its used, it's important to be sure that each platform is defining the exactly the same public API. You may want to use a common interface definition to leverage the compilers assistance in achieving parity.

Deployment models

The platform-specific portion of an extension implementation can arrive on a target device in two ways.

In the first and most common approach, the implementation is bundled with the application. In this application-bundled deployment model, each application that uses the extension gets its own copy of (the relevant portion of) the extension. This approach also creates a tight binding between the version of the application and the version of the extension that are shipped together, thus avoiding compatibility pitfalls that could occur if one were updated independently from the other.

The second approach is device-bundled deployment. As the name implies, a device-deployed extension implementation is bundled with the device itself, and not with the application. This deployment model is necessarily available only to manufacturers, as only manufacturers can arrange to preinstall software on these devices. Extensions that use this model are typically built by manufacturers in order to provide ActionScript APIs for features unique to their devices.

Note platform implementations, not complete extensions, are either application-deployed or device-deployed. A single extension may contain implementations in both categories. There are two common cases:

  • All application-bundled. This is the kind of extension created by a typical ActionScript developer, whether for their own or shared use. It contains platform-specific implementations for multiple platforms, and each implementation is bundled with each application that uses it.
  • Device-bundled for one platform, with application-bundled default and simulation behavior. This is the kind of extension typically created by a manufacturer. The implementation for their device is bundled with the device. In order to support a better developer workflow, the extension also includes application-deployed platform implementations for Windows and Mac OS X that operate when running on the desktop.
Extension descriptor

Borrowing from AIR application packages, extensions include an XML-based descriptor file that declares various properties of the extension. When developing an extension, you'll need to create one of these yourself, using your editor of choice.

The descriptor starts with a namespace declaration:

<extension xmlns="http://ns.adobe.com/air/extension/2.5">

It continues with a mandatory ID and version number:

<id>com.example.SampleExtension</id> <versionNumber>1.0.4</versionNumber>

It allows several optional attributes that are also helpful in identifying the extension:

<name>Sample Extension</name> <description> <text lang="en-US">This is a description.</text> <text lang="fr-FR">C'est une description.</text> </description> <copyright>Copyright 2011, Sample Inc.</copyright>

(The description need not be localized, and the name and copyright can also be localized if desired.)

This is followed by an enumeration of each platform supported by the extension and important information relevant to each platform.

A platform implementation that's application-bundled declares the name of the corresponding library, plus an initialization function. (These are covered in more detail later.)

<platforms> <platform name="Windows-x86"> <applicationDeployment> <nativeLibrary>sample.dll</nativeLibrary> <initializer>InitSample</initializer> </applicationDeployment> </platform>

The declaration for a device-bundled implementation is simpler, as the device already recognizes the relevant libraries and functions:

<platform name="Sample-ARM"> <deviceDeployment/> </platform>

Finally, an extension can contain an implementation for the "default" platform. This implementation must be ActionScript only, since the actual platform isn't known. And it must be deployed with the application, since there's no way to guarantee a default bundled with all devices. It's represented in the descriptor like this:

<platform name="default"> <applicationDeployment/> </platform> </platforms> </extension>
Framework dependencies

When an application that uses extensions is loaded, ActionScript code is loaded from various sources in a prescribed sequence:

  1. Definitions for all built-in APIs are loaded. These are all of the APIs in the flash.* packages. Since these are loaded first, both extension and application code can depend on these APIs.
  2. The code associated with each extension used by the application is loaded. Note that an extension must be used by the application, as specified by a reference to it in the application descriptor file, to be loaded. (Device-bundled extensions, despite always being present on the device, follow this same rule.) Since extensions are loaded before the application, they cannot depend on any code in the application.
  3. Application code is loaded. Applications can assume that both built-in APIs and any APIs defined by extensions are loaded before the application itself is loaded.

Note that, due to this sequencing, an extension cannot use a framework like Flex unless that framework is embedded in the extension itself. Furthermore, only one extension can load such a framework. Otherwise, conflicts will arise when other extensions or the application try to load the same framework, resulting in multiple definitions for each class.

To avoid these issues, extensions should typically avoid dependencies on frameworks. Instead, think about the relationship between the extension and the framework from the application's point of view: What interfaces should an extension define so that an application can easily bind it into whichever framework the application has selected?

Simulation support

An extension can define its behavior not only when running on the target platform but also when being run in the simulator during development.

The simulator runs on Windows or Mac OS, so an extension can implement simulator support by simply providing an implementation on Windows and Mac OS.

There are a couple of scenarios that may apply here, depending on which platforms your extension supports and what kind of simulator support you wish to implement.

If an extension supports only nondesktop platforms, then any Mac OS X or Windows implementation provided need only work for simulation purposes. This implementation can be written entirely in ActionScript. It can be as simple (no-ops) or as sophisticated (returning recorded values, canned data, or data from alternate inputs) as you'd like.

On the other hand, if an extension also supports Mac OS X and Windows, then you may want to provide alternate simulator behavior in the Mac OS X and Windows platform implementations. Consider having an explicit mode setting to toggle between the real and simulated behavior.

The "default" platform implementation can be used to provide simulation support, since it also runs on Windows and Mac OS X if no specific implementations are provided for those platforms. However, note that the default will also be executed on all other platforms that don't have a platform-specific implementation, whether they are desktop, TV, or mobile. Typically it's more effective to use the default platform to provide a fallback (for example, with isSupported returning false).

Runtime extension API

With a sense of the end-to-end picture of an extension in place, development can turn to coding. Extension coding leverages an ActionScript API, ExtensionContext, via which calls can be made from ActionScript to native code. It also leverages a native API, the runtime extension API, which is used at three distinct times: during extension initialization, during context initialization, and during execution of the extension functionality.

The native API is made available on all platforms in C. On Android, it is also available as a Java API.

Initialization

Each extension is given an opportunity to perform one-time initialization when it is first used for a given invocation of an application. Note that this may or may not be at the time the application is started, and may not happen at all if the extension is referenced by the application but never used.

The initialization code is provided either as a function (C) or implementation of an interface method (Java). The corresponding name (function or class) is specified in the extension descriptor:

<applicationDeployment> <initializer>InitSample</initializer> . . .

When using the C API, note that this call is taken to be an exported C symbol. On most platforms, you must explicitly mark it is as such in your compiled code via an extern "C" declaration and, possibly, an attribute such asdeclspec(export). If you try to export a C++ function, C++ name mangling conventions will generally cause the exported symbol to be different from the function name, resulting in a mismatch that AIR cannot resolve. (You may be able to address this by specifying the C++ mangled name, but usually it's easier to turn off the name mangling via the extern declaration.)

When using the Java API, the initializer element instead gives the fully qualified name (for example, com.example.MyExtension) of a class implementing the FREExtension interface. The extension is initialized by creating an instance of this class—it must have a constructor that takes no arguments—and then invoking theinitialize() method on it.

Note that the extension doesn't have a mechanism to report errors that occur during initialization. If the initialization code can fail, then that state should be recorded by the extension, checked during a later call, and finally reported back to the application.

Context

After initialization occurs, the ActionScript portion of an extension next creates one or more ExtensionContexts. An ExtensionContext is a binding between ActionScript and native code, consisting of three elements:

  • A set of named functions. These are registered by the native code, and can be invoked from ActionScript.
  • A reference to an ActionScript object that can be accessed from both ActionScript and native code.
  • A reference to a native code structure that can be accessed only from native code.

The ExtensionContext API is designed to scale across several different models for binding between ActionScript and native code. At its simplest, a single instance of a single context is sufficient for an extension that exposes a handful of functions. For example, an extension that exposes calls to retrieve a device identifier and OS version could simply contain two registered calls, each returning one of these elements.

An extension exposing a broader set of functions might use multiple contexts to group related functions. For example, one context could return device information; another, user information. This allows the two groups to have their own initialization, their own set of registered function names, and so on. In this case, there's still no need for more than one instance of each context.

At the other end of the spectrum, an extension can be used to create a one-to-one binding between ActionScript and native objects. For example, an extension for PDF support might allocate a new ExtensionContext for each PDF document in memory. Each context has the same set of functions; the set of registered functions is essentially the set of methods available on the document class. Each instance of the context uses the ActionScript and native reference storage to maintain the link between the two objects. In ActionScript code:

class PDFDocument { public function PDFDocument() { _context = new ExtensionContext( "com.example.PDFExtension", "PDFDocument" ); } public function load( url:String ):void { _context.call( "PDFDocumentLoad", url ); } }

And on the native code side, taking the usual liberties with error handling:

FREObject PDFDocumentLoad( FREContext ctx, void *functionData, uint32_t argc, FREObject argv[] ) { uint32_t length = 0; uint8_t *url = NULL; FREGetObjectAsUTF8( argv[0], &length, &url ); PDFDocument *pdf = LoadPDFFromURL( url ); FRESetContextNativeData( ctx, (void*)pdf ); . . .}

Of course, these patterns can be combined as well: A single extension could use one extension for a block of simple functions, and another on a per-object basis, and so on.

In C, each context is initialized (and finalized) via a function registered in the extension initialization call. The context initializer must return a set of functions registered by name, and can optionally perform additional work. It can determine which set of functions to return by looking at its second parameter (PDFDocument in the example above.).

In Java, a similar pattern is achieved via the FREContext abstract class. Extensions must create a derived class that implements the getFunctions() call, and then return an instance of that class fromFREExtension.createContext(). (Note, however, that the derived class can't use other FREContext helper methods until after createContext() returns and context initialization has been completed.)

ActionScript access from native code

With initialization and registration complete, ActionScript code can begin calling native code viaExtensionContext.call(). ActionScript objects of any type can be passed to such calls. Native code accesses these arguments using a native API exported by the runtime itself.

Access to ActionScript from native code is centered on the FREObject type. (This is an opaque handle in C, and a class in Java.) Given a FREObject, native code can:

  • Determine its type, via FREGetObjectType or dynamically casting to a derived class.
  • Convert to an integer, floating point, Boolean, or string value.

If the object is not a primitive type like int, then native code can:

  • Set and get properties of the object.
  • Call methods on the object.

In order to create values for setting properties, arguments for calling methods, and return values from native functions, native code can also:

  • Construct new objects from integer, floating point, Boolean, and string values.
  • Construct new objects by invoking class constructors.

Taken together, these capabilities allow fairly complete access to ActionScript from native code.

Nonetheless, accessing ActionScript from native code can be tedious due to the need to handle error conditions, explicitly construct argument arrays, and so on. If you have to access a significant amount of ActionScript from native code—accessing properties, invoking methods, and so on—consider creating a helper method in ActionScript, and invoking that from native code instead.

Finally, note that FREObjects have a limited lifetime: They are only valid while a call into the extension, viaExtensionContext.call(), remains on the stack. They cannot be held in native code across calls; you'll receive an error if you attempt to use them in this way. If you need to store an ActionScript reference across calls, save it in the ActionScriptData parameter on the relevant context. (If you need to store multiple references, put an Object in ActionScriptData, and hang references off of it as properties.)

Specialized native APIs

The runtime API made available to native code is augmented with special-case accessors for ByteArrays, Arrays, Vectors, and BitmapData objects. These specialized APIs are designed to provide the fastest possible performance for types frequently used to manipulate or transfer large amounts of data.

In C, these APIs take the form of functions that require FREObjects of corresponding types. In Java, they're accessed via classes derived from FREObject, which add additional, type-appropriate methods.

The special-case accessors for Arrays and Vectors are the most straightforward, adding four calls to get and set the length, and get and set individual items. These APIs avoid the inconvenience and overhead of, for example, converting each index from integers to FREObjects and back.

The ByteArray and BitmapData APIs are a bit more complex, and in turn allow direct access to the memory backing the array or image. This allows ActionScript code and native code to share access to memory segments and images without requiring intermediate copies.

ByteArray and BitmapData access involves a three-step process:

  1. Acquire the object. This locks the underlying memory buffer in place, thus permitting access from native code without the risk of the garbage collector releasing or moving the memory.
  2. Access the object, reading or modifying as desired. During this time, other ActionScript methods cannot be accessed; they're locked out while the memory buffer is locked.
  3. Release the object. This invalidates references to the memory obtained in Step 1, and re-enables access to the rest of the API.

For BitmapData only, note that you should also use the invalidate call to inform the runtime as to which portion of the image is updated; this in turn is used to correctly refresh that portion of the image when it's next rendered.

In the C API, the acquire methods return structs which contain a raw pointer to the underlying memory, plus associated necessary information, like the width and height of the image. In Java, this information is exposed as object properties, and access to the memory is provided using an instance of java.nio.ByteBuffer.

Note also that these APIs are subject to the same lifetime rules as any other FREObject. Even if a ByteArray or BitmapData object is locked, references to it cannot be successfully held after the outermost call to an extension returns.

Threading

Native code is permitted to use any available platform threading facilities. This means that extensions can be used, among other things, to improve application responsiveness by moving long-running tasks off of the ActionScript execution thread. However, access to the runtime extension API is allowed, with one exception, only from the same thread on which the runtime invokes the extension. (Note that the runtime doesn't make any guarantees about which thread it will use to invoke the extension.)

If you do use an extension to move work to a background thread, there's a straightforward pattern you can use to work with this restriction:

  1. Upon invocation from ActionScript, gather all the data that's needed and save copies in native data structures.
  2. Spin up or acquire a thread to perform the work, and hand it the copied data.
  3. Upon completion, store the results to a native data structure, and notify ActionScript (see below).
  4. When ActionScript receives the completion notification, call into the extension a second time to retrieve the results, which are copied out of the result data structure.

The signaling mentioned in Step 3 is accomplished by calling FREDispatchStatusEventAsync (C) or FREContext.dispatchStatusEventAsync (Java). This call is the one exception mentioned above; it can be called from any thread at any time after the corresponding context is fully initialized.

When this signaling API is called, it causes a StatusEvent, with the specified code and level, to be queued for dispatch in ActionScript at the next opportunity. Typically, this means the event will be dispatched within one frame. The event is dispatched by the corresponding ExtensionContext object. At this point, since execution is by definition back on the ActionScript thread, it's safe (although optional) to call back into native code.

Resource management

Extensions that allocate resources (memory, threads, file handles, and so on) in native code should take care to release those resources when they're no longer required. The runtime provides a pair of notification APIs that help manage resources associated with the lifetime of the context or the extension itself.

When a context is released from ActionScript, native code is notified by a call to the registered context finalizerfunction (C) or the FREContext.dispose method (Java). In ActionScript, this can be triggered either explicitly from ExtensionContext.dispose() or implicitly when the ExtensionContext object is garbage-collected. Use of the dispose() method is recommended, as there can be substantial delays between when an object is available to the garbage collector and when it is ultimately collected.

When an extension itself is shut down, native code is notified by a call to the registered extension finalizerfunction (C) or the FREExtension.dispose method (Java). Note that the runtime doesn't guarantee that these methods are called. On most platforms, the extension remains loaded as along as the application is running, and so the extension itself is never shut down.

Packaging extensions

The packaging step connects extension development with extension consumption. Everything that makes up an extension—the descriptor, ActionScript code, native code and resources for each platform—is all assembled into a single, ZIP-based archive. That package is then an input to the application packaging step.

Extension packages

Extension packages are created using the adt command line tool available in the Flex and AIR SDKs.

ADT is the same tool used to package AIR applications for deployment, and the options used to package extensions are largely the same. It begins by telling adt which kind of package you want to create, and the name of the output file:

adt -package -target ane MyExtension.ane \

Next comes the path to the extension descriptor, and to the SWC that defines the ActionScript API:

extension-descriptor.xml -swc ExtensionAPI.swc \

After this comes one or more sequences specifying the individual sets of files for each platform. For example, a simple extension supporting iOS and Mac OS might use:

-platform iPhone-ARM library.swf libextension.a \ -platform MacOS-x86 library.swf Extension.framework

Here, the library.swf file is the ActionScript layer for the platform; it must use this reserved name. In this example the same implementation is used on both platforms but, as discussed above, different SWF files could be used if desired.

This is a simple example containing only the minimally necessary library.swf and library or framework for the target platforms. (For the "default" platform, only library.swf is required.) Extensions can contain arbitrary additional files for each platform: images, sound, resource bundles, and so on.

The adt command above results in an extension with the following structure. It's straightforward to see how additional platforms are added, and where additional files are placed:

mimetype META-INF/ ANE/ extension.xml iPhone-ARM/ library.swf libextension.a MacOS-x86/ library.swf Extension.framework/ . . . catalog.xml library.swf

The root catalog.xml and library.swf are those taken from the specified SWC. If the SWC contains any additional files, they are also added to the package.

Application packages

When an extension is consumed by an application, portions of the extension package are transformed and embedded in the application package. This all happens automatically during the application packaging step, and generally one needn't be concerned with the details. However, it can be useful to know where files land in the final package should you need to reference them, and there are some platform-specific details you may find informative.

Ignoring those platform-specific details for a moment, the default transformation for the contents of an extension package are simply to copy it, file by file, into the extensions portion of the application package. This section appears inside the META-INF/AIR directory, in order to avoid conflicts with any of the files included by the application itself.

For example, packaging the extension above for iOS would result in the following structure within the application package:

META-INF/ AIR/ extensions/ com.example.Extension1/ catalog.xml library.swf META-INF/ ANE/ extension.xml iPhone-ARM/ library.swf

Note that all files outside the selected platform directory are embedded, so that they can be referenced by the extension if necessary. The extension can find this location, both during debugging and for a deployed application, using ExtensionContext.getExtensionDirectory().

Not surprisingly, only the platform directory matching the target is included; all other platform directories are, if present in the extension, ignored. The platform-specific portions of an extension should therefore not contain any references to the portions of the extension specific to other platforms.

Finally, note that in this example, libextension.a does not appear in the final package. It's subject to one of the platform-specific transformations described below, and therefore is not copied over.

Android details

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 details

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.

Desktop details

On Windows, the DLL and any associated files are simply placed, intact, in the Windows-x86 directory.

On Mac OS X, extensions take the form of a Framework bundle. This bundle is placed, intact, in the Frameworks subdirectory of the Application bundle. In contrast to the mobile platforms, this mechanism does support composition, and so features like resource bundles work without further changes or the risk of additional name collisions.

When compiling Mac OS X frameworks for use as an extension, developers should set the following options to support correct resolution of the dependency on the AIR framework in all scenarios:

  1. Add @executable_path/../runtimes/air/mac@executable_path/../Frameworks, and/Library/Frameworks to LD_RUNPATH_SEARCH_PATHS.
  2. Use weak framework linking and the flat namespace option.

Together, these options settings allow the application to load the correct copy of the AIR framework first, and then for the extension to rely on the already loaded copy.

Limitations

Extensions enable a wide variety of new use cases, but like any technology it has its limits. When determining whether or not extensions are appropriate for your application, keep in mind:

  • Extensions cannot directly alter the behavior of built-in ActionScript classes. For example, an extension can't modify how the Socket API works, or add support for a new codec for the Video object.
  • Extensions cannot directly integrate interactive objects, such as native UI controls, with the display list. (Other items, such as native dialog boxes, may work, depending on the platform.)

If you run into limitations you'd like to see addressed in a future release, let us know on the web at ideas.adobe.com

Where to go from here

AIR 3 release candidate provides a comprehensive extension mechanism. With it, developers can build two-tier applications. They can use ActionScript for core UI and logic. They can use extensions and native code for deep platform integration, legacy code reuse, and to achieve maximum performance.

Using an extension is similar to using any ActionScript library. Application developers can easily leverage extensions developed by others. Developing an extension requires knowledge of the targeted platforms and their associated development tools. The extension mechanism provides a flexible model, allowing the application developer to find an optimal balance and binding between ActionScript and native code. Extensions can make use of native facilities, including bundled resources, and a comprehensive extension API provided by the runtime for access to ActionScript.

Prior to extensions, developers had no choice but to wait for Adobe to introduce whatever capability they required of the runtime. Now, these limits have been removed.

Also be sure to check out the Native extensions for Adobe AIR page in the Adobe AIR Developer Center.


原文链接:http://www.adobe.com/devnet/air/articles/extending-air.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值