This article needs a technical review. How you can help.
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.
Method overview
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(); |
Methods
GetForThread
()
blah blah
CData GetForThread
();
Parameters
This function does not take any arguments.
Return value
blah blah
LoadClass
()
blah blah
CData LoadClass(
aJenv,
aFullyQualifiedName
,
[optional] Object aDeclares
);
Parameters
aJenv
- The return value of
GetForThread()
. aFullyQualifiedName
- A sig of a typed array or the name of the class that would be used in a signature, but without the surrounding
L
and;
. - If it is the sig of a typed array such as '[I' then aDeclares must not be declared. See the section "Working with arrays" to see how this is used to create typed arrays.
If it is the name of a class, then aDeclares MUST be declared. For example, the class of GeckoAppShell has the fully-qualified class name of org.mozilla.gecko.GeckoAppShell. The signature for this would beLorg.mozilla.gecko.GeckoAppShell;
but we pass here without theL
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
- If this is omitted, then
aFullyQualifiedName
must be an array type. - If this is provided, then
aFullyQualifiedName
must be a class name. A required object that contains up to five fields of key names:constructors
,fields
,static_fields
,methods
, andstatic_methods
.
Return value
blah blah
NewString
()
blah blah
CData NewString(
aJenv, aStr);
Parameters
aJenv
- The return value of
GetForThread()
. aStr
- A Javascript
String
, use any encoding desired.
Return value
blah blah
ReadString()
blah blah
String ReadString(
aJenv,
aJavaString );
Parameters
aJenv
- The return value of
GetForThread()
. aJavaString
- A CData object that is a Java string.
Return Value
A Javascript String
.
UnloadClasses
()
blah blah
void UnloadClasses();
Parameters
This function takes no arguments.
Return value
This function has no return value, if you try to store the return value in a variable, it will be undefined
.
Working with arrays
Creating/preallocating a typed array
Methods
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);
Parameters
aIndex
- The position in the array to
obtain.
Return value
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);
Parameters
aStart
- The position to start setting elements of the array in.
aLength
- The number of elements to copy from aStart. If 0 is supplied, an empty CData array is returned.
Return value
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);
Parameters
aIndex
- The position in the array to change.
aVal
- A value to set it to.
Return value
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);
Parameters
aStart
- The position to start setting elements of the array in.
aValsArray
- A Javascript array holding the values to set.
Return value
This function has no return value, if you try to store the return value in a variable, it will be undefined
.
Attributes
Attribute | Type | Description |
length | Number |
The number of elements in the array |
Demonstration
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); } }
Casting
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: [ { /* https://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 } // https://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); }
Examples
Read Java strings
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); } }
Example 2
blah blah blah
code here
See also
- Following the Android Toasts Tutorial from a JNI Perspective - A rewrite of the tutorial on the Android developers reference website. This rewrite shows how to approach Android SDK API work with a JNI point of view.
- Google Developer :: Android APIs - Documentation for the Android APIs. The go-to resource for learning about the API when writing scripts for Fennec.