The JNI.jsm
JavaScript code module abstracts all of the js-ctypes required for writing JNI code. To use it, you first need to import the code module into your JavaScript scope:
Components.utils.import("resource://gre/modules/JNI.jsm");
This module was available in Firefox since version 17. If you would like to support versions before that, you can copy and paste the contents of the JSM file int
JNI stands for Java Native Interface; this library allows calling Java code running in Java Virtual Machines (JVMs), etc. The most common use for this module is in add-ons and other works on Firefox for Android (Fennec). With this module, all of the Android SDK functions that Firefox has permissions for are at your fingertips.
A note about Firefox for Android, this JSM file is already globally imported and is available from the privileged window
scope as window.JNI
. There also exists a jenv
variable in the window scope, so if you need to run JNI from the global scope, use a different variable name then jenv
.
JNI.jsm contains all the js-ctypes needed to communicate with the JNI libraries. The js-ctypes is abstracted away while all the underlying data is all ctype data. Functions are declared similar to js-ctypes but in a very different syntax. Unlike C from js-ctypes, defining constants is not a manual magic number method in JNI, it is declared the same way we define functions, except in JNI they are called fields.
CData GetForThread(); |
CData LoadClass(CData aJenv, String aClassFullyQualifiedName, [optional] Object aDeclares); |
CData NewString(CData aJenv, String aStr); |
String ReadString(CData aJenv, CData aJavaString); |
void UnloadClasses(); |
GetForThread
()blah blah
CData GetForThread
();
This function does not take any arguments.
blah blah
LoadClass
()blah blah
CData LoadClass(
aJenv,
aFullyQualifiedName
,
[optional] Object aDeclares
);
aJenv
GetForThread()
.aFullyQualifiedName
L
and ;
.Lorg.mozilla.gecko.GeckoAppShell;
but we pass here without the L
and ;
. We can use dot notification here for the parent class. However if it was a subclass such as GeckoInterface (org.mozilla.gecko.GeckoAppShell.GeckoInterface
), then this will cause a crash. Therefore, it is always recommended to use slash notation. So for GeckoAppShell we would pass here "org/mozilla/gecko/GeckoAppShell"
and for GeckoInterface we would use "org/mozilla/gecko/GeckoAppShell$GeckoInterface"
.aDeclares optional
aFullyQualifiedName
must be an array type.aFullyQualifiedName
must be a class name. A required object that contains up to five fields of key names: constructors
, fields
, static_fields
, methods
, and static_methods
.blah blah
NewString
()blah blah
CData NewString(
aJenv, aStr);
aJenv
GetForThread()
.aStr
String
, use any encoding desired.blah blah
ReadString()
blah blah
String ReadString(
aJenv,
aJavaString );
aJenv
GetForThread()
.aJavaString
A Javascript String
.
UnloadClasses
()blah blah
void UnloadClasses();
This function takes no arguments.
This function has no return value, if you try to store the return value in a variable, it will be undefined
.
CData .get(Number aIndex); |
CData .getElements(Number aStart, Number aLength); |
void .set(Number aIndex, CData aValue); |
void .setElements(Number aStart, [array, size_is(arr.length > Number anyNumber > 0)] in CData aValsArray); |
.get
()Gets the value of the element in the array at given aIndex.
CData get(Number aIndex);
aIndex
obtain.
Returns primitive CData.
.getElements
()Returns a new CData object of the section of the array specified by aStart and ending at position aStart + aLength.
void setElements(Number aStart, Number aLength);
aStart
aLength
A new CData array object based on the array.
.set
()Sets an element in the array at given aIndex to the given aVal.
void set(Number aIndex, CData aVal);
aIndex
aVal
This function has no return value, if you try to store the return value in a variable, it will be undefined
.
.setElements
()Sets a section of a typed array to specified values. This function starts on the position of aStart
in the array and sets it to the values in aValsArray
. This is done by calling .set
for each element with the respective value in the aValsArray
.
void setElements(Number aStart, [array, size_is(arr.length > Number anyNumber > 0)] in CData aValsArray);
aStart
aValsArray
This function has no return value, if you try to store the return value in a variable, it will be undefined
.
Attribute | Type | Description |
length | Number |
The number of elements in the array |
var my_jenv = null; try { my_jenv = JNI.GetForThread(); var SIG = { String: 'Ljava/lang/String;', int: 'I' }; JNI.LoadClass(my_jenv, '[' + SIG.String); var StringArray = JNI.classes.java.lang.String.array; // Object { js#obj: CData, js#proto: function (), __cast__: function (), new: function () } var sa = StringArray.new(5); // Object { js#obj: CData, length: 5 } JNI.LoadClass(my_jenv, '[' + SIG.int); var IntArray = JNI.classes.int.array; // Object { js#obj: CData, js#proto: function (), __cast__: function (), new: function () } var ia = IntArray.new(5); // Object { js#obj: CData, length: 5 } ia.get(0); // 0 ia.get(1); // 0 ia.get(2); // 0 ia.get(3); // 0 ia.get(4); // 0 ia.set(2, 10); // void ia.get(2); // 10 ia.setElements(3, [50, 75]); // void ia.get(0); // 0 ia.get(1); // 0 ia.get(2); // 10 ia.get(3); // 50 ia.get(4); // 75 } finally { if (my_jenv) { JNI.UnloadClasses(my_jenv); } }
This example shows how to cast, the casting happens at JNI.classes.android.view.WindowManager.__cast__(wm)
below:
function main() { var my_jenv; try { my_jenv = JNI.GetForThread(); var SIG = { WindowManager: 'Landroid/view/WindowManager;', WindowManager_LayoutParams: 'Landroid/view/WindowManager$LayoutParams;', ViewGroup_LayoutParams: 'Landroid/view/ViewGroup$LayoutParams;', View: 'Landroid/view/View;', void: 'V', Context: 'Landroid/content/Context;', String: 'Ljava/lang/String;', Object: 'Ljava/lang/Object;', GeckoAppShell: 'Lorg/mozilla/gecko/GeckoAppShell;' }; var GeckoAppShell = JNI.LoadClass(my_jenv, fullyQualifiedNameOfClass(SIG.GeckoAppShell), { static_methods: [ { name: 'getContext', sig: '()' + SIG.Context }] }); var Context = JNI.LoadClass(my_jenv, fullyQualifiedNameOfClass(SIG.Context), { methods: [ { /* http://developer.android.com/reference/android/content/Context.html#getSystemService%28java.lang.Class%3CT%3E%29 * public abstract Object getSystemService (String name) */ name: 'getSystemService', sig: genMethodSIG([ SIG.String // name ], SIG.Object // return ) }], static_fields: [ { name: 'WINDOW_SERVICE', sig: SIG.String } // http://developer.android.com/reference/android/content/Context.html#WINDOW_SERVICE // public static final String WINDOW_SERVICE ] }); var WindowManager = JNI.LoadClass(my_jenv, fullyQualifiedNameOfClass(SIG.WindowManager), { methods: [ { name: 'addView', sig: '(' + SIG.View + SIG.ViewGroup_LayoutParams + ')' + SIG.void }, { name: 'removeView', sig: '(' + SIG.View + ')' + SIG.void }] }); var aContext = GeckoAppShell.getContext(); var wm = aContext.getSystemService(Context.WINDOW_SERVICE); var wm_casted = JNI.classes.android.view.WindowManager.__cast__(wm); } finally { if (my_jenv) { JNI.UnloadClasses(my_jenv); } } } // helper functions function genMethodSIG(aParamsArr, aRet) { // aParamsArr is an array of SIG's for each param. Not fully qualified name, meaning if its a class, it needs the surrouning L and ; // aRet is a SIG for the return value. Not fully qualified name, same meaning as above row return '(' + (aParamsArr ? aParamsArr.join('') : '') + ')' + aRet; } function fullyQualifiedNameOfClass(aClass) { // aClass is a string with L and ; arround it return aClass.substr(1, aClass - 2); }
This example shows how to read Java strings as Javascript strings. We will get paths to special folders on Fennec (Firefox for Android) into a Javascript variable with ReadString()
. This shows how to get the paths to the "Pictures" folder:
Components.utils.import("resource://gre/modules/JNI.jsm"); Components.utils.import("resource://gre/modules/osfile.jsm"); // because we use OS.Path.join in this example var my_jenv = null; try { my_jenv = JNI.GetForThread(); var SIG = { Environment: 'Landroid/os/Environment;', String: 'Ljava/lang/String;', File: 'Ljava/io/File;' }; var Environment = JNI.LoadClass(my_jenv, SIG.Environment.substr(1, SIG.Environment.length - 2), { static_fields: [ { name: 'DIRECTORY_PICTURES', sig: SIG.String } ], static_methods: [ { name:'getExternalStorageDirectory', sig:'()' + SIG.File } ] }); JNI.LoadClass(my_jenv, SIG.File.substr(1, SIG.File.length - 2), { methods: [ { name:'getPath', sig:'()' + SIG.String } ] }); var javaFile_dirExtStore = Environment.getExternalStorageDirectory(); // Object { js#obj: CData } var javaStr_dirExtStorePath = javaFile_dirExtStore.getPath(); // Object { js#obj: CData } var jsStr_dirExtStorePath = JNI.ReadString(my_jenv, javaStr_dirExtStorePath); // "/mnt/sdcard" var jsStr_dirPicsName = JNI.ReadString(my_jenv, Environment.DIRECTORY_PICTURES); // "Pictures" var jsStr_dirPics = OS.Path.join(jsStr_dirExtStorePath, jsStr_dirPicsName); // "/mnt/sdcard/Pictures" } finally { if (my_jenv) { JNI.UnloadClasses(my_jenv); } }
blah blah blah
code here