11 June 2012
An intermediate or advanced understanding of Adobe AIR and ActionScript 3, and familiarity with Flash Builder and Java.
Intermediate
Note: Adobe recommends using the next version of Flash Builder for developing native extensions for Adobe AIR. Download Flash Builder 4.6 trial.
This tutorial introduces you to creating your own native extensions for Adobe AIR using simple code. In addition to showing you code and skills that are common across all native extensions (AIR, ActionScript 3, and Flash Builder), this tutorial covers the compilation of native code on the Android platform. Although you may be interested in targeting Objective-C, C#, C++, C, or some other language, the Java code should give you a solid understanding of native interaction with native extensions.
To make the most of this tutorial, be sure you have the following software installed:
You also need the following:
You may choose to use another platform, but the steps in the native-code portion of this guide will require that you maintain your own native build environment.
Additional resources
Adobe AIR has allowed application developers to extend the features of the runtime with a set of tools known as Native Extensions for Adobe AIR. This feature was enabled for AIR for TV starting with version 2.5, but it's now been expanded to work on mobile and desktop platforms. By using native extensions, your applications can access all of the features of your target platform, even if the runtime itself doesn't have built-in support.
To illustrate this point, imagine you're creating an application on an Android device, and want to vibrate the phone when your application completes a download. Without native extension support, you'd either have to code the entire program in Java, or use AIR and accept that this task would be impossible. With native extensions, however, you can create a bridge that spans between native code and your own application logic, allowing you to pass instructions back and forth, making it possible for your app to control the vibration motor. You could then leverage the multiplatform support of AIR to deploy this same app to iOS, and expand your native extension by including Objective-C code. You could even change the native code to be platform-aware, allowing you to change the vibration duration and pattern whether the app is running on Android or iOS.
Native extensions allow you to harness unique and platform-specific capabilities of your devices; they also allow you to use native code in ActionScript apps; reuse existing platform code; perform operations in threads to increase your apps' processing power; and give you access to native platform libraries. Native extensions are packaged and distributed like all other ActionScript libraries: you can distribute your own libraries, as well as use native extensions published by other developers, allowing you to plug in functionality to your own applications.
Adobe also provides several fully documented native extension examples that will help developers get started with the above features.
This tutorial will get you started creating a native extension of your own. It'll walk you through the steps requried to create native Java code for Android, ActionScript 3 code and a native extension file; you'll also learn how to create a Flex mobile application that works with your native extension, and finally you'll test it on your device. Even though this is a "Hello, World!" tutorial, we'll eschew printing the usual message via native code, and instead opt to control the vibration motor of an Android smartphone. If you're feeling adventerous (or wish to target a different platform) you may choose to adapt the native code section of this guide to to fit a non-Android platform.
Here are the high-level steps that you'll undertake in the following pages:
Creating native code
Creating ActionScript 3 code
Creating the .ANE file, tying native and ActionScript 3 together
Testing your native extension
Prove that your native extension works!
At the heart of a native extension is the native code, which will act as the first layer between your application and the feature you want to control. In this example, the native code will control the vibration motor and send and receive information with ActionScript 3. You'll write the functions to control the vibration motor yourself, and you'll use the Java APIs provided by Adobe AIR to send data back and forth to ActionScript 3.
Before you can start coding and using libraries, however, you have to set up your build environment.
Since you'll be writing native Android code, you need a development environment that can operate with the Android SDK. If you haven't already, install and configure the Android SDK by following the links included at the beginning of this guide. Once this is complete, you'll modify Flash Builder to create and compile Android projects:
Though it's tempting to jump into the specifics of writing code, it'll save hassle later if you now verify that your phone can connect, and is recognized by the Android SDK.
We now have to create a new Android project in Flash Builder, and instruct the linker to look for the Java API JAR file provided with the AIR SDK.
You have one more project configuration task: set the application to have permission to use the vibration controls of the device.
You've performed this step so that your native code will have the requisite permission to run should you decide to create native test cases. Although this tutorial doesn't cover it, testing your native code before moving on to ActionScript 3 can be helpful—especially for more advanced native extensions.
Now that your Android project is properly configured, you have to start adding in the structures that establish a bridge between ActionScript and your native Java code. The first of these structures is an extension context. An extension context is responsible for containing up to three native extension-related items (see also Oliver Goldman's article, Extending Adobe AIR):
Note that your native extension may have multiple Extension Contexts, and you should separate them based on function. In this example, you only need one, which will provide a map to access the Android vibration feature.
Next you'll create a new VibrationExtensionContext class.
You'll see that two functions have been created for you: public void dispose() and public Map<String, FREFunction> getFunctions() . As you may have guessed, getFunctions() must return a key-value pair mapping between Strings (which are referenced in your ActionScript 3 code), and any FREFunction classes, which you'll define next. The APIs provided by Adobe give you classes and functions that begin with the abbreviation FRE, which stands for Flash Runtime Extension.
The first step to defining the functions in your native extension is to create a new Map to return. In the getFunctions() class, add:
@Override
public Map<String,FREFunction> getFunctions()
{
Map<String, FREFunction> functionMap = new HashMap<String, FREFunction>();
return functionMap;
}
This creates an empty HashMap, but it clearly isn't very useful if it's empty. You're going to map three key value pairs, each of which will define a class that implements the FREFunction interface:
isSupported to a VibrationSupportedFunction. This will run some logic and pass an FREObject to ActionScript 3 containing either true or false , depending on whether the platform supports vibration. Note: It's best practice to always allow the ActionScript 3 code to do a compatibility check before using other features of your native extension.
vibrateMe to a VibrationVibrateFunction. This FREFunction accepts a parameter from ActionScript 3 controlling the duration of the vibration, and then performs the actual vibration on your device.initMe to a VibrationInitFunction. This function allows the native code to perform and initialization tasks before the other functions are ready to be used. In this example, you'll create a reference to Android's VIBRATOR_SERVICE , which will be used in the VibrationVibrateFunction (AKA "vibrateMe" in ActionScript 3).getFunctions() Class, call the put() function on your functionMap object. The first parameter will be a String representing the above function names, and the second parameter a new instance of the (not yet created) function:functionMap.put("initMe", new VibrationInitFunction());
functionMap.put("isSupported", new VibrationSupportedFunction());
functionMap.put("vibrateMe", new VibrationVibrateFunction());
You've defined three functions. Next, you'll write them as classes that implement the FREFunction interface. You'll start with the VibrationInitFunction, which, when called, will set a class in your VibrationExtensionContext that will later be used to vibrate the device.
Note that you can simplify the above steps by using a code-generation feature of Eclipse: click on the class you wish to create (VibrationInitFunction, in this example), hit Ctrl-1 to bring up the code hint window, and select "Create class VibrationInitFunction." This will automatically create the class for you.
As you'll see, a function inside of your VibrationInitFunction has already been defined for you: call(), which takes two arguments: an FREContext, and an FREObject[] array. By default, these are defined as arg0 and arg1, but you can give them more descriptive names. Change the call() function definition to look like this:
public FREObject call(FREContext context, FREObject[] passedArgs)
When this function is called, the first argument will be a reference to your VibrationExtensionContext, and the second argument will be an array of all arguments (if any) passed down by the ActionScript 3 code. This will be important for your VibrationVibrateFunction, which will set the duration based on the first argument in that array.
For now, your init function is going to use the passed-in FREContext object to get the VibrationExtensionContext, and then the Android Activity it belongs to. Using this activity reference, it will then be able to retrieve the global Android system service known as the Context.VIBRATOR_SERVICE, which will allow you to control the vibrator motor. You'll store this system service in a new variable contained in your VibrationExtensionContext, which you'll create shortly:
call() function of VibrationInitFunction , add the following line to grab the VibrationExtensionContext from the FREContext that was passed in:VibrationExtensionContext vbc = (VibrationExtensionContext)context;
getActivity() function. The inclusion of this function in the FREContext class is designed to support common tasks, such as your need to grab the context's Activity, and thus have a path to the SystemService that we require.Activity a = vbc.getActivity();
a.getSystemService(), and pass in a reference to the global Context.VIBRATOR_SERVICE . This will return an object of type Vibrator. You need a place to store this that is available to the entire Extension Context, so put it in a new variable, vb, placed inside the VibrationExtensionContext:vbc.vb = (Vibrator)a.getSystemService(Context.VIBRATOR_SERVICE);
vb to the class:public Vibrator vb = null;
Thus, you've created a reference to a native code structure, the vb Vibrator class, which is accessible to any class that can reference your VibrationExtensionContext.
Your completed VibrationInitFunction should look like this:
public class VibrationInitFunction implements FREFunction
{
@Override
public FREObject call(FREContext context, FREObject[] passedArgs)
{
VibrationExtensionContext vbc = (VibrationExtensionContext)context;
Activity a = vbc.getActivity();
vbc.vb = (Vibrator)a.getSystemService(Context.VIBRATOR_SERVICE);
return null;
}
}
You've learned how to: create a class that implements FREFunction; understand the arguments passed in from ActionSscript 3; access your Extension Context via the FREContext argument; and you've seen a common initialization task for extensions.
Next you have to implement the other two FREFunctions that you defined in the Map<String, FREFunction> getFunctions() function.
The second function that you defined earlier is the VibrationSupportedFunction. As you indicated in the HashMap returned by getFunctions(), this function can be called using the ActionScript 3 String isSupported. The creation of this function is quite similar to the VibrationInitFunction—however, it will show you how to return a Boolean inside of a FREObject.
arg0 and arg1 to context and passedArgs, respectively.call() function; create and return that now. You'll also want a reference to your VibrationExtensionContext, so create that by casting the context parameter:FREObject result = null;
VibrationExtensionContext vbc = (VibrationExtensionContext)context;
// ...
return result;
vbc.vc should be set.vbc.vc non-null? If so, result should be true.vbc.vc is null, we can reasonably conclude that initialization failed, and that this platform doesn't support Vibration.result should be set to false. Create the following if statement:
if (vbc.vb == null)
{
result = FREObject.newObject(false);
}
else
{
result = FREObject.newObject(true);
}
newObject() on FREObject can result in an FREWrongThreadException exception being thrown. You'll surround your if statement in a try catch block, to handle this eventuality.Your completed call() function should now look like this:
@Override
public FREObject call(FREContext context, FREObject[] passedArgs)
{
FREObject result = null;
VibrationExtensionContext vbc = (VibrationExtensionContext)context;
try
{
if (vbc.vb == null)
{
// Not supported
result = FREObject.newObject(false);
}
else
{
// Supported
result = FREObject.newObject(true);
}
}
catch (FREWrongThreadException fwte)
{
fwte.printStackTrace();
}
return result;
}
You now have the second of your three native extension functions: VibrationSupportedFunction. When called by the ActionScript 3 String isSupported , this function will check to see if the variable vb in the VibrationExtensionContext "context" is non-null. It'll return a FREObject that's either true or false based on this condition, and will catch a FREWrongThreadException that's potentially thrown by the static newObject() function of FREObject.
The last native extension function you have to implement performs the core duty of your native extension: it allows the AIR application to vibrate the device's motor for a specified duration.
arg0 to context and arg1 to passedArgs.result.vbc. You'll use this to access vbc.vb, the Vibrator object.We're now ready to access the first passed argument as a FREObject, and try to set it as an integer. If the data is malformed, an exception may be thrown, which you'll catch. Your call() function should currently look like this:
@Override
public FREObject call(FREContext context, FREObject[] passedArgs)
{
FREObject result = null;
VibrationExtensionContext vbc = (VibrationExtensionContext)context;
try
{
// Vibrate the device
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
try { // ... } block, we're going to try and grab the first element in the passedArgs array as a FREObject:FREObject fro = passedArgs[0];
getAsInt(); on this FREObject:int duration = fro.getAsInt();
vb Vibrator variable, passing in the duration:vbc.vb.vibrate(duration);
You've now successfully created three native functions, mapped them to strings in the HashMap provided by getFunctions(), and created the native logic necessary to perform all the actions that are required of your native extension. This completes the creation of the VibrationExtensionContext, which is the only extension context that your native extension requires.
You've created the one and only extension context that your native extension requires, but you still haven't created the main class of our extension. Fortunately, adding this class is simple; all we have to do is create a class called VibrationExtension which implements the FREExtension interface.
The FREExtesion interface defines the initialize, dispose, and createContext functions, which allow hooks into the lifecycle of a native extension. Despite providing us with three functions, we only have to customize one: the createContext function. This function has to return a FREContext. Fortunately, you've already created your own VibrationExtensionContext, and you can simply return an instance of this class.
createContext() function will read arg0 . This argument is actually an ID defining the type of context to create (which is only useful if you have multiple context types). Change arg0 to contextType.createContext() function, we only need to return a new instance of our VibrationExtensionContext. Replace the return null; code with the following:return new VibrationExtensionContext();
This will cause the initialization and creation of your Extension Context, and allow you to use the native code you put in your native extension.
In the following sections of this tutorial, we'll see how to code the ActionScript 3 side of our native extension, as well as package and test the completed native extension file and sample application. These steps will involve referencing your native code as a JAR file. Creating a JAR file in Flash Builder is simple:
You've done the majority of the coding necessary in creating a native extension, having created Java code that you can extend with additional functions, logic, and (if necessary) even additional extension contexts to expand the scope of your native extension.
In contrast, creating the ActionScript 3 code necessary to complete this platform bridge is simple. Your tasks include:
Your ActionScript 3 library code will consist of one class, which will import the flash.external.ExtensionContext API, and provide the following functions:
initMe native function.isSupported, which will call our isSupported native function, and which will return true or false depending on the response from our native code.vibrate, which will accept a Number duration, and call your native vibrateMe function with this number as a parameter.Your ActionScript 3 code will exist inside of a Flex Library project:
You now have to create the connection to our Extension Context, which will allow you to access the initMe, isSupported, and vibrateMe functions you created in Java.
private static var extContext:ExtensionContext = null;
extContext variable has been initialized. If not, you'll call the static function ExtensionContext.createExtensionContext(), and pass in two identifiers. The first is an ID you'll set in an extension.xml file shortly. The second is a parameter that's passed to the createContext() function of VibrationExtension. You'll recall that it allows you to create different Extension Contexts; since you only have one, you ignored this parameter in your native code. If you have multiple extension contexts, you should make your native code parse the value you pass in with an if or switch statement, and create the appropriate values based on the available shared String values. Write the following: if ( !extContext )
{
extContext = ExtensionContext.createExtensionContext("com.yourDomain.Vibrate","vibrate");
extContext.call("initMe");
}
initMe via extContext.call(), passing in no additional parameters. This will map up with the VibrationInitFunction that you coded in Java, and will initialize the internal data structures necessary for you to vibrate the device.The Extension Context will now be created and initialized as soon as the Vibrate() constructor is called by any application that uses your new ActionScript 3 library. You still have two functions to implement, however. First create the isSupported() function, which will connect to the native isSupported function, and inspect the Boolean value that is returned by your application logic.
isSupported, which returns a Boolean:public static function get isSupported():Boolean
{
var supported:Boolean = false;
// ...
return supported;
}
extContext.call(), passing in isSupported as a String parameter, and which sets your supported variable to the returned Boolean value:supported = extContext.call("isSupported") as Boolean;
Repeat this process to create the vibrateMe function, which takes in a single Number as the duration. Creation of this function is straightforward:
public function vibrate(duration:Number):void
{
extContext.call("vibrateMe",duration);
}
Note that Flash Builder will automatically compile your library into a SWC file, located in the bin folder of the project. The SWC file is an archive that contains library.swf. You have to manually reference both the SWC and SWF whenever you package an ANE file with ADT. Thus, you should now open the SWC file in an archive management tool, extract library.swf, and place it in the bin/ directory of HelloANELibrary, as well as in the HelloANENative directory:
The library.swf file needs to be placed inside of the native code directory for every platform you target. For example, you'd place this file inside iOS/, android/, x86/, etc., depending on your project's targets. (For more advanced ANEs, you can specify different library.swf files, if you require that your AS3 library is different for different platforms. This goes against the best practice of defining a common interface, however, and it's recommended that you stick with a single version of library.swf.)
By extracting HelloANELibrary.swc, you now have all the files you need to create a native extension. Note that you have to repeat steps 1 through 4 whenever you change your library code, otherwise library.swf will be out of date.
At this point, you've written all of the library code you need to use your native extension.
You've created the requisite code, but you've yet to link everything together into an ANE file. First, create an extension.xml file inside of your Flex library project. For each native target, this file points to the native code (your JAR file) and describes the package location of the initializer function (and, optionally, a finalizer function, which you don't need in this example). You'll hand this file to the packager when you create your ANE file (which you'll then use in a sample application).
Create the extension.xml file inside of your Flex library project:
<extension xmlns="http://ns.adobe.com/air/extension/2.5">
<id>com.yourDomain.Vibrate</id>
<versionNumber>1</versionNumber>
<platforms>
<platform name="Android-ARM">
<!-- ... -->
</platform>
</platforms>
</extension>
Inside of the <platform> tags, you'll now set the location of the JAR file in a <nativeLibrary> tag, and set the location of the initializer to the location we set in the native code (recall that you created the initialize() function in your VibrationExtension class):
<applicationDeployment>
<nativeLibrary>HelloANENative.jar</nativeLibrary>
<initializer>com.yourDomain.example.android.helloANE.extensions.VibrationExtension </initializer>
</applicationDeployment>
You've now successfully created your extension.xml file, and have all the components you need to create your ANE file.
Currently, packaging a native extension requires using the command line tool adt, and passing it a number of parameters. I recommend creating a batch script in Windows (.bat file), or a bash script in OS X (typically a .sh file); the script you'll create will allow you to set your own variables in the top part of the script, allowing it to be easily adapted to your other native extension projects.
There are a number of pieces of information you need to plug into the script. I'll list the information, and show you the values I use on my own system:
You should create a similar list of values for your own system. You can then plug them in by referencing the variables with the following ADT command:
"%adt_directory%"\adt -package %signing_options% -target ane "%dest_ANE%" "%extension_XML%" -swc "%library_SWC%" -platform Android-ARM -C "%SWF_directory%" library.swf –C "%native_directory%" HelloANENative.jar
This command may look complicated, but it's simply running adt and passing in signing options, specifying ane as the target, providing the extension.xml file, specifying the HelloANELibrary.swc file, targeting Android-ARM as the platform, and telling ADT where to look for the native library files.
A compile_ane.bat file on Windows may look like this:
set adt_directory=C:\Program Files\Adobe Flash Builder 4.5\sdks\4.5.2\bin
set root_directory=C:\Users\dan\Programs
set library_directory=%root_directory%\HelloANELibrary
set native_directory=%root_directory%\HelloANENative
set signing_options=-storetype pkcs12 -keystore "c:\Users\dan\Programs\cert.p12"
set dest_ANE=HelloANE.ane
set extension_XML=%library_directory%\src\extension.xml
set library_SWC=%library_directory%\bin\HelloANELibrary.swc
set SWF_directory=%library_directory%\bin\Android-ARM\
"%adt_directory%"/adt -package %signing_options% -target ane "%dest_ANE%" "%extension_XML%" -swc "%library_SWC%" -platform Android-ARM -C "%SWF_directory%" library.swf –C "%native_directory%" HelloANENative.jar
On Mac OS X, the script might look like this:
#!/bin/bash
adt_directory="/Users/Applications/Adobe Flash Builder 4.5/sdks/4.5.2/bin" root_directory=/Users/dan/Programs library_directory=${root_directory}/HelloANELibrary
native_directory=${root_directory}/HelloANENative
signing_options="-storetype pkcs12 -keystore /Users/dan/Programs/cert.p12"
dest_ANE=HelloANE.ane
extension_XML=${library_directory}/src/extension.xml
library_SWC=${library_directory}/bin/HelloANELibrary.swc
SWF_directory=${library_directory}/bin/Android-ARM/
"${adt_directory}"/adt -package ${signing_options} -target ane "${dest_ANE}" "${extension_XML}" -swc "${library_SWC}" -platform Android-ARM -C "SWF_directory" library.swf -C "${native_directory}" HelloANENative.jar
Note that I use a p12 file as my signing certificate. You can substitute the file you normally use for signing AIR files. If you need to create one, the easiest way to do so is to open a Flex or AIR project in Flash Builder, and go to Project > Export Release Build. During the second step you'll have the option to create a certificate using the GUI.
Run your script from the command line, enter your cert's password, and the %dest_ANE% file should be created, and ready to use in a sample application!
You're now going to create a Flex mobile application that uses your native extension! The process of setting up a project and deploying it to Android is simple:
To finish setting up your project, you need to specify that your application requires the use of the Android vibration controls. Pay particular attention to this aspect when you're taking advantage of additional features of a device—it's easy to overlook that some features require additional permissions. The AIR application descriptor won't make these entries available in the commented sections either, as you're going above and beyond the features of the runtime. Should you forget to specify the proper permissions, the native code will fail to work, and will likely throw a permissions-related exception. (On Android, this output is easily visible by using logcat in the adb shell.)
To add permission to the AIR application descriptor:
<android>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
<uses-permission android:name="android.permission.VIBRATE"/>
Now that the project is configured, you can add a vibrate button to the home view:
s:View tags (in the main body of the class), add a new s:Button . Give it a label of Vibrate using ANE, and create a new click handler. (i.e. Type '<s:Button label="Vibrate using ANE" >' Type space > Type "click" and then double click the populate item "click – InterativeObject" > double click "Generate click handler"). Flash Builder should automatically create an fx:Script tag and click handler ActionScript 3 function for you.var v:Vibrate = new Vibrate();
v.isSupported , and then call your main vibrate function, passing in a hard-coded value of 100 for the number of milliseconds the motor should run: trace("Is this ANE supported on this platform? " + Vibrate.isSupported);
v.vibrate(100);
The Flex application should now launch on the device, and provide a button labelled Vibrate using ANE. Tapping this button should produce a 100ms long vibration from the motor in your Android device! You'll also notice the following output in the Console view in Flash Builder:
[SWF] com.yourDomain.Vibrate - 2,916 bytes after decompression
[SWF] HelloANESample.swf - 2,702,220 bytes after decompression
Is this ANE supported on this platform? True
You can add a TextInput or form of numeric input if you wish to control the duration of the vibration. Simply replace the 100 argument that we hard-coded with a locally scoped variable, and set this variable using a control. At this point, coding the ActionScript 3 side of your application is no different than other Flex application development.
In this guide you learned that AIR native extensions allow you to expand the capabilties of Adobe AIR, giving your applications access to device and hardware capabilities that would otherwise be inaccessible via the runtime APIs alone. You learned how to create native extensions for Android, and can use these skills to target other platforms. In this example, you focused on the simple task of making the vibration motor of an Android device activate for a specified duration; this illustrated how to create and initialize an native extension, as well as pass data back and forth between your native code and your AIR application.
To achieve this, you:
Our example didn't require any assets beyond compiled code—however, you may wish for your native extension to access images, files, database or configuration stores, etc. This is quite possible, and requires a few considerations on mobile:
FREContext.getResourceId(), by passing in the desired resource ID (see also Oliver Goldman's article, Extending Adobe AIR).You'll likely find that your native extension must perform asynchronous tasks in native code, and you'll require a way to pass notifications to your AIR application when the task completes. This is accomodated by the function dispatchStatusEventAsync(String code, String level); in the FREContext Class. As an example, the following Java code instructs a hypothetical native extension library that there has been a Status Event with a code of "DATA_CHANGED":
context.dispatchStatusEventAsync("DATA_CHANGED", stringData);
context.addEventListener(StatusEvent.STATUS, onStatus);
...
private function onStatus(e:StatusEvent):void
{
if (e.code == "DATA_CHANGED")
{
var stringData:String = e.level;
// ...
}
}
Status Events provide a convenient way of updating your native extension library (and thus your resulting AIR and Flex applications) on the status of a native code task.
You can continue learning about native extensions by referring to the "additional resources" section at the beginning of this guide. These resources include a link to the native extensions that Adobe has already created and distributed, allowing you to expand AIR's capabilities simply by dropping native extension files into your Flex and ActionScript 3 applications.
Also be sure to check out the native extensions samples in the Adobe AIR Developer Center.