Exporting as an Android application will create an Android package that is suitable for use on Android devices. The packages generated will only work on devices running Android version 2.3.3 and above.
To create an Android package, right-click the project .gde
> > .From the export file selection dialog, select the Storyboard application file (*.gde) that you want to export. Choose appropriate names for the application name, Android package file name, and the package name. Select the directory you want to export to and options for application orientation and fullscreen. For Android devices version 4.4 and newer, the fullscreen option uses Android's sticky immersion fullscreen.
Note
Currently, without rooting an Android device, there is no way to disable the bottom bar for some Android 3.0+ devices.
Select the Runtime tab to set Storyboard runtime options (see Storyboard Engine Options for list of available runtime options). Select the Manifest tab for advanced options to modify the default Permissions, Manifest file or Keystore settings (optional). Select the Icons tab for selecting the icons for the application. If you don't need to alter the settings, click Apply then Run to use the defaults.
By default the Android application package file (APK) will be created in the project's directory. To transfer this application package to an Android device, simply copy the package onto a USB or SD card.
Sometimes a user creates a Storyboard app that requires a library that isn't included with the Storyboard Runtime. When exporting for Android we need to tell the exporter which libraries to preload. We do this by giving the exporter a text file with a list of libraries. Make sure that the paths to these libraries are relative to the Storyboard app’s directory. As well, the order of the libraries in the list determine the order they get loaded in, therefore if one of the libraries has a dependency on another library make sure to have the dependent library higher in the list.
The example we’ll use is modifying the FilesystemExplorer app from the Crank Software public repository. In order for this app to work on Android we’ll need to include the LuaFileSystem module that's been compiled for Android (the lib's name is lfs.so). We’ll add this file in scripts/android-armle.
Create a text file, which we’ll call user_libs.txt, with the following contents:
When exporting the app make sure to include the path to this file and hit finish.
Now the app can make use of the functionality included in the new library.
On Android target platforms Storyboard provides an additional level of platform integration. In order to access the native Java service API on Android platforms Storyboard has incorporated the LuaJava module to provide a bridge from Storyboard Lua script functions to the Android Java API.
Access to the LuaJava bridge is through the luajava
Lua variable. On non-Android platforms, this variable will not be defined and this can be used to provide alternate or simulated behavior.
function my_callback(mapargs) if(luajava == nil) then print("LuaJava bridge not available") return end -- LuaJava available for use ... end
The general mapping of standard Lua/Java types such as strings and numbers is handled transparently so that Lua strings can be used in Java constructors and methods in the same way that the Java String class would normally be used and similarly for Lua numbers and vice/versa.
When a Lua variable is created that is a reference or proxy to a Java object, then access to the methods of that object are performed using the colon (:
) notation with the Lua variable, lua_variable:method_name()
notation. When accessing static member variables of an object, this can be performed using the traditional dot (.
) notation lua_variable.member_variable_name
. This is further demonstrated in the examples shown below.
In order to access a nested Java class for instantiation or binding, the dollar sign ($
) must be used as a separator. For instance, if the Java class Bar is a nested class of Foo, then binding would work as follows: luajava.bindClass("Foo$Bar"). This is further demonstrated in the examples below.
A description of the complete Android Java API is beyond the scope of this document. For a complete coverage of the Android API refer to http://developer.android.com/reference/packages.html Depending on the functionality that your application is going to access, there may be additional restrictions that must be explicitly declared in the AndroidManifest.xml
file. Permissions can be added in the Advanced Options section when exporting your Android project. The android:debuggable option has been changed to false by default. To change this, you will need to use your own custom manifest file. Export your manifest file to view it by clicking the Export button under the Manifest File tab. You can make changes to this file and then select it as a custom manifest file when exporting to ensure the manifest file is setup the way you want it to be.
Within the Android environment the Storyboard Engine execution takes place outside of the main Android/Java event loop. When integrating with the Android API's developers should always consider that they are using the Android API as if they were executing in a background thread and act accordingly. This may require the creation of additional Looper
message event handlers if callback event handlers are being used. For more information on Android process model and multi-threading considerations, refer to the Android documentation: http://developer.android.com/guide/components/processes-and-threads.html.
The mapping of Lua referenced objects to Android Java objects is relatively straightforward. All of the API functionality is accessed via the luajava
Lua global variable. This variable provides four functions that can be used to access and manipulate standard Java objects and one variable that provides the Android Activity
that is required.
- luajava.newInstance(className, ...)
-
This function creates a new Java object based on the fully qualified class name. Any additional parameters that are provided are passed through to the standard Java constructor.
The return value is a Lua variable that is a proxy to the Java object or
nil
if the class could not be instantiated.-- Create an instance of a Java string tokenizer local strTk = luajava.newInstance("java.util. StringTokenizer","a,b,c,d", ",") while strTk:hasMoreTokens() do print(strTk:nextToken()) end -- Create a new Android Intent object (unpopulated) local intent = luajava.newInstance ("android.content.Intent")
- luajava.bindClass(className)
-
This function creates a reference to a Java class based on a fully qualified class name. This is different from
newInstance()
in that a new Java object is not created and the constructor is not invoked, but simply a reference to the class is returned. Use this when you need access to static fields or methods of a Java object.The return value is a Lua variable that is a proxy to the Java Class object specified or
nil
if the class could not be found.-- Get the current system time local sys = luajava.bindClass("java.lang.System") print ( sys:currentTimeMillis() ) -- Parse a string into an Android Uri local uriClass = luajava.bindClass("android.net.Uri") local phoneURI = uriClass:parse("tel:6135951999")
- luajava.new(classObject, ....)
-
This function is similar to the
newInstance()
function but rather than taking a fully qualified class name it takes an existing Class reference, generally obtained from callingbindClass(). Additional parameters can be passed to the Java constructor.
.The return value is a Lua variable that is a proxy to the Java object or
nil
if the class could not be instantiated.-- Create a new string instance str = luajava.bindClass("java.lang.String") strInstance = luajava.new(str)
- luajava.createProxy(interfaceNames, luaObject)
-
If a Java API requires an interface to be implemented or provided as a set of callbacks, then it is where the
createProxy()
function can be used. The interfaceNames parameter is a comma separated list of fully qualified Java interfaces that will be implemented by the Lua variable luaObject. The names of the interface methods must be present in theluaObject
variable.The return value is a Lua variable that can be passed to any function or method that requires an implementation of that interface. If the creation of the proxy fails, then
nil
is returned.-- Create a Lua variable with the same interface as an ActionListener local button_cb = {} function button_cb.actionPerformed(ev) -- I would do something interesting here ... end -- Map the Lua variable to the Java interface buttonProxy = luajava.createProxy("java.awt.ActionListener", button_cb) -- Use the newly created interface instance on a Java object button = luajava.newInstance("java.awt.Button", "execute") button:addActionListener(buttonProxy)
- luajava.nativeActivity()
-
All significant interaction on an Android system involves working with an
Activity
(see http://developer.android.com/reference/android/app/Activity.html) Storyboard applications that are deployed to Android devices run as native activities which is a special class of the general Activity that allows those applications to interact directly with the graphics context and are generally C/C++ applications rather than pure Java applications.The return value of this function is a Lua variable that is a proxy for the NativeActivity Java class used by this application or
nil
if the class could not be instantiated.-- Start an activity specified by a previously created Intent object local na = luajava.nativeActivity() if(na ~= nil) then na:startActivity(intent) else print("No Native Activity") end
This example demonstrates how a phone call could be invoked as part of a Lua callback. In order for this example to work, the AndroidManifest.xml file must be changed to give permission for calls to be made: %<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
-- Log message routine to route diagnostic messages local function lm(msg) print(msg) end -- Call a selected phone number using the Android API -- Input is the string number value that is to be called local function call_phone_number(number) if(luajava == nil) then lm("No luajava Lua object") return end local na = luajava.nativeActivity() if(na == nil) then lm("No native activity available") return end local uriClass = luajava.bindClass("android.net.Uri") if(uriClass == nil) then lm("No java.lang.String object") return end local phoneURI = uriClass:parse("tel:" .. tostring(number)) if(phoneURI == nil) then lm("No java.net.URI object") return end local intentClass = luajava.bindClass("android.content.Intent") if(intentClass == nil) then lm("No intent class") return end local intent = luajava.newInstance("android.content.Intent", intentClass.ACTION_CALL, phoneURI) if(intent == nil) then lm("No intent object") return end lm("Calling " .. number) na:startActivity(intent) end
This example demonstrates how to create a new instance of a nested inner class of a Java class. This example gets media metadata from the android.provider.MediaStore.Audio.Media class, which is a nested class of android.provider.MediaStore.Audio, which in turn is a nested class of android.provider.MediaStore.
-- In order to pass array's to any of the Android Java API's we must explicitly create -- a Java array from a Lua table and this function covers that work. function make_array(dataClass, values) local arrayClass = luajava.bindClass("java.lang.reflect.Array") if(arrayClass == nil) then print("Can't get array class") return nil end local newTypedArray = arrayClass:newInstance(dataClass, #values) if(newTypedArray == nil) then print("Can't get array class") return nil end for i=1,#values do arrayClass:set(newTypedArray, i-1, values[i]) end return newTypedArray end function get_album_files(album_id) if (luajava == nil) then return end if (luajava.bindClass == nil) then return end local na = luajava.nativeActivity() local mediastore = luajava.newInstance("android.provider. MediaStore$Audio$Media") local externalURI = mediastore.EXTERNAL_CONTENT_URI local columns = {} columns[1] = mediastore.TITLE_KEY columns[2] = mediastore.DURATION columns[3] = mediastore.TITLE local stringClass = luajava.bindClass("java.lang.String") local array = make_array(stringClass, columns) local where = mediastore.ALBUM_KEY .. "=?" local what = {} what[1] = album_id local whatArray = make_array(stringClass, what) local cursor = na:managedQuery(externalURI, array, where, whatArray, nil); local res = cursor_to_table(cursor) return res end