JNI (Java Native Interface) is the mechanism that enables Java code to call native functions, and native code to call Java functions.
<jni.h>
, which basically mirror
Java’s reflection APIs.native
keyword, and then calling them as normal Java functions.jni_generator
generates boiler-plate code with the goal of making our code:
jni_generator
uses regular expressions to parse .Java files, so don’t do
anything too fancy. E.g.:
java.lang
classes, add an explicit import.void call(Outer.Inner inner)
The presense of any JNI within a class will result in ProGuard obfuscation for the class to be disabled.
Without Crazy Linker:
dlsym()
).With Crazy Linker:
dlsym()
, but JNI is hardcoded to use the system’s dlsym()
.
jni_registration_generator.py
.
.java
files
of an APK. Inefficient, but very convenient.dlsym()
is not used in this case, we use a linker script to avoid
the cost of exporting symbols from the shared library (refer to
//build/config/android:hide_all_but_jni_onload
).jni_registration_generator.py
exposes two registrations methods:
RegisterNonMainDexNatives
- Registers native functions needed by multiple
process types (e.g. Rendereres, GPU process).RegisterMainDexNatives
- Registers native functions needed only by the
browser process.Java methods just need to be annotated with @CalledByNative
. The generated
functions can be put into a namespace using @JNINamespace("your_namespace")
.
Because the generator does not generate any source files, generated headers must
not be #included
by multiple sources. If there are Java functions that need to
be called by multiple sources, one source should be chosen to expose the
functions to the others via additional wrapper functions.
@NativeMethods
.${OriginalClassName}Jni
with a get()
method that returns an implementation of the annotated
interface. The C++ function that it routes to is the same as if it would be
in the legacy method.long mNativePointer
), then
the bindings will generate the appropriate cast and call into C++ code.To add JNI to a class:
Enable the JNI processor by adding to your android_library
target:
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
deps = [ "//base:jni_java" ]
@NativeMethods
that contains
the declaration of the corresponding static methods you wish to have
implemented.${OriginalClassName}Jni.get().${method}
${OriginalClassName}_jni.h
. (The path will
depend on the location of the generate_jni
BUILD rule that lists your Java
source code.) Only include this header from a single .cc
file as the
header defines functions. That .cc
must implement your native code by
defining non-member functions named JNI_${OriginalClassName}_${UpperCamelCaseMethod}
for static methods and member functions named ${OriginalClassName}::${UpperCamelCaseMethod}
for non-static methods. Member functions need be declared in the header
file as well.Example:
class MyClass {
// Cannot be private. Must be package or public.
@NativeMethods
/* package */ interface Natives {
void foo();
double bar(int a, int b);
// Either the |MyClass| part of the |nativeMyClass| parameter name must
// match the native class name exactly, or the method annotation
// @NativeClassQualifiedName("MyClass") must be used.
//
// If the native class is nested, use
// @NativeClassQualifiedName("FooClassName::BarClassName") and call the
// parameter |nativePointer|.
void nonStatic(long nativeMyClass);
}
void callNatives() {
// MyClassJni is generated by the JNI annotation processor.
// Storing MyClassJni.get() in a field defeats some of the desired R8
// optimizations, but local variables are fine.
Natives jni = MyClassJni.get();
jni.foo();
jni.bar(1,2);
jni.nonStatic(mNativePointer);
}
}
#include "base/android/jni_android.h"
#include "<path to BUILD.gn>/<generate_jni target name>/MyClass_jni.h"
class MyClass {
public:
void NonStatic(JNIEnv* env);
}
// Notice that unlike Java, function names are capitalized in C++.
// Static function names should follow this format and don't need to be declared.
void JNI_MyClass_Foo(JNIEnv* env) { ... }
void JNI_MyClass_Bar(JNIEnv* env, jint a, jint b) { ... }
// Member functions need to be declared.
void MyClass::NonStatic(JNIEnv* env) { ... }
Using the ‘native’ keyword
native
JNI method declarations and
generates stubs for them. This used to be the norm, but is now obsolete.JniMocker
rule to your test.JniMocker#mock
in a setUp()
method for each interface you want to
stub out.JniMocker
will reset the stubs during tearDown()
.
/**
* Tests for {@link AnimationFrameTimeHistogram}
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class AnimationFrameTimeHistogramTest {
@Rule
public JniMocker mocker = new JniMocker();
@Mock
AnimationFrameTimeHistogram.Natives mNativeMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mocker.mock(AnimationFrameTimeHistogramJni.TEST_HOOKS, mNativeMock);
}
@Test
public void testNatives() {
AnimationFrameTimeHistogram hist = new AnimationFrameTimeHistogram("histName");
hist.startRecording();
hist.endRecording();
verify(mNativeMock).saveHistogram(eq("histName"), any(long[].class), anyInt());
}
}
If a native method is called without setting a mock in a unit test, an
UnsupportedOperationException
will be thrown.
@CalledByNative
will have stubs generated for them.
@CalledByNative("InnerClassName")
).h
files.All pointers to Java objects must be registered with JNI in order to prevent garbage collection from invalidating them.
For Strings & Arrays - it’s common practice to use the //base/android/jni_*
helpers to convert them to std::vectors
and std::strings
as soon as
possible.
For other objects - use smart pointers to store them:
ScopedJavaLocalRef<>
- When lifetime is the current function’s scope.ScopedJavaGlobalRef<>
- When lifetime is longer than the current function’s
scope.JavaObjectWeakGlobalRef<>
- Weak reference (do not prevent garbage
collection).JavaParamRef<>
- Use to accept any of the above as a parameter to a
function without creating a redundant registration.Minimize the surface API between the two sides. Rather than calling multiple functions across boundaries, call only one (and then on the other side, call as many little functions as required).
If a Java object «owns» a native one, store the pointer via
"long mNativeClassName"
. Ensure to eventually call a native method to delete
the object. For example, have a close()
that deletes the native object.
The best way to pass «compound» types across in either direction is to create an inner class with PODs and a factory function. If possible, make mark all the fields as «final».
generate_jni
- Generates a header file with stubs for given .java
filesgenerate_jar_jni
- Generates a header file with stubs for a given .jar
filegenerate_jni_registration
- Generates a header file with functions to
register native-side JNI methods (required only when using crazy linker).Refer to //build/config/android/rules.gni for more about the GN templates.
jni_generator
jni_generator_tests.py
//base/android/jni_generator:sample_jni_apk