Java calls to C/C++ are inherent in the Java language, not unique to Android, namely JNI. JNI is the specification for Java to call C++.
JNI overview
JNI (Java Native Interface) is a feature of Java calling Native language. Java and C/C++ can interact with EACH other through JNI. Java language is a cross-platform language, and the cross-platform behind all rely on Java virtual machine, the virtual machine is written in C/C++, adapted to various systems, through JNI to provide a variety of services for the upper Java, to ensure cross-platform. Before the Advent of the Java language, many programs and libraries were written in Native languages. If you want to reuse these libraries, you can use JNI to implement them. On Android, JNI is a bridge between the Java world and the Native world.
JNI instance: Camera
The latest is related to the Camera of the system, so the application of JNI is analyzed from the perspective of the system Camera. The following examples are based on Camera2
After Android5.0(21), Android.hardware.Camera was scrapped in favor of the new Android.Hardware.Camera2
Related codes:
frameworks/base/core/jni/AndroidRuntime.cpp
frameworks/base/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
frameworks/base/core/jni/android_hardware_camera2_CameraMetadata.cpp
Copy the code
Corresponding Camera2 Java layer is CameraMetadataNative. Java, is Native layer corresponding android_hardware_camera2_CameraMetadata. CPP
Java layer CameraMetadataNative
Relevant code in CameraMetadataNative. Use Java Camera2 CameraManager (camera manager), specific operation can be carried through CameraMetadataNative CameraManager. Initialization of CameraMetadataNative
public class CameraMetadataNative implements Parcelable
static {
/* * We use a class initializer to allow the native code to cache some field offsets */
nativeClassInit();
registerAllMarshalers();
}
private static native void nativeClassInit(a);
}
Copy the code
The static method calls the Native layer method nativeClassInit. The Native layer implementation of this method is android_hardware_camera2_CameraMetadata. CPP
Native layer CameraMetadata
The Native layer code is initialized in the android_hardware_camera2_CameraMetadata. CPP Native method
static const JNINativeMethod gCameraMetadataMethods[] = {
// static methods
{ "nativeClassInit"."()V",
(void *)CameraMetadata_classInit }, // Corresponds to the Java layer nativeClassInit()
{ "nativeGetAllVendorKeys"."(Ljava/lang/Class;) Ljava/util/ArrayList;",
(void *)CameraMetadata_getAllVendorKeys},
{ "nativeGetTagFromKey"."(Ljava/lang/String;) I",
(void *)CameraMetadata_getTagFromKey },
{ "nativeGetTypeFromTag"."(I)I",
(void *)CameraMetadata_getTypeFromTag },
{ "nativeSetupGlobalVendorTagDescriptor"."()I",
(void*)CameraMetadata_setupGlobalVendorTagDescriptor },
// instance methods
{ "nativeAllocate"."()J",
(void*)CameraMetadata_allocate },
{ "nativeAllocateCopy"."(L" CAMERA_METADATA_CLASS_NAME ";) J",
(void *)CameraMetadata_allocateCopy },
{ "nativeIsEmpty"."()Z",
(void*)CameraMetadata_isEmpty },
{ "nativeGetEntryCount"."()I",
(void*)CameraMetadata_getEntryCount },
{ "nativeClose"."()V",
(void*)CameraMetadata_close },
{ "nativeSwap"."(L" CAMERA_METADATA_CLASS_NAME ";) V",
(void *)CameraMetadata_swap },
{ "nativeReadValues"."(I)[B",
(void *)CameraMetadata_readValues },
{ "nativeWriteValues"."(I[B)V",
(void *)CameraMetadata_writeValues },
{ "nativeDump"."()V",
(void *)CameraMetadata_dump },
// Parcelable interface
{ "nativeReadFromParcel"."(Landroid/os/Parcel;) V",
(void *)CameraMetadata_readFromParcel },
{ "nativeWriteToParcel"."(Landroid/os/Parcel;) V",
(void *)CameraMetadata_writeToParcel },
};
Copy the code
When will gCameraMetadataMethods be loaded?
int register_android_hardware_camera2_CameraMetadata(JNIEnv *env)
{...// Register native functions
returnRegisterMethodsOrDie(env, CAMERA_METADATA_CLASS_NAME, gCameraMetadataMethods, NELEM(gCameraMetadataMethods)); }...static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
LOG_ALWAYS_FATAL_IF(res < 0."Unable to register native methods.");
return res;
}
Copy the code
When register_android_hardware_camera2_CameraMetadata is called, you need to know how the JNI lookup is performed.
JNI search mode
During the startup process of the Android system, the Kernel is started to create the init process, and then the init process forks the first process that crosses Java and C/C++, namely Zygote process. The startVm in AndroidRuntime. CPP is used to create the VM. After the VM is created, the JNI method in the VM is registered by calling startReg.
CameraMetadata register_android_hardware_camera2_CameraMetadata (); androidRuntime.cpp
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
Copy the code
Then static declaration in gRegJNI
static const RegJNIRec gRegJNI[] = {
......
REG_JNI(register_android_hardware_camera2_CameraMetadata),
......
}
Copy the code
The gRegJNI method is called in startReg
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
ATRACE_NAME("RegisterAndroidNatives");
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
ALOGV("--- registering native functions ---\n");
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return - 1;
}
env->PopLocalFrame(NULL);
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
Copy the code
Register_jni_procs (gRegJNI, NELEM(gRegJNI), env) loops through the methods corresponding to the gRegJNI array members
static int register_jni_procs(const RegJNIRec array[].size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
ALOGD("-- -- -- -- -- -- -- -- -- --!!! %s failed to load\n".array[i].mName);
#endif
return - 1; }}return 0;
}
Copy the code
The int register_android_hardware_camera2_CameraMetadata(JNIEnv *env) in android_hardware_camera2_CameraMetadata. CPP is called. In addition to this Android system startup, the corresponding JNI method is registered. There is also the application’s custom JNI method, in the case of MediePlay: the associated code path
frameworks/base/media/java/android/media/MediaPlayer.java
frameworks/base/media/jni/android_media_MediaPlayer.cpp
Copy the code
MediaPlayer statement:
public class MediaPlayer extends PlayerBase
implements SubtitleController.Listener
{...private static native final void native_init(a); .static {
System.loadLibrary("media_jni"); native_init(); }}Copy the code
The static code block uses system. loadLibrary to load the dynamic library. Media_jni is the Android equivalent of libmedia_jni.so. The jni directory/frameworks/base/media/jni Android. Mk in the corresponding statement:
LOCAL_SRC_FILES:= \
android_media_MediaPlayer.cpp \
...
LOCAL_MODULE:= libmedia_jni
Copy the code
Find the corresponding Native(natvie_init) method in android_media_mediaPlayer. CPP:
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
jclass clazz;
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return; }... }Copy the code
The methods of JNI registration are the two described above:
- Register at Android startup, declared in the gRegJNI method in AndroidRuntime.cpp
- Register with system.loadLibrary ()
JNI basis
The above section mainly describes the interaction and implementation of Java layer and Native layer in the system, without analyzing the basic theory and process of JNI
JNI naming rules
JNI method name specification:
Return value + Java prefix + full path class name + method name + parameter ① JNIEnv + parameter ② jobject + other parametersCopy the code
A simple example returns a string
extern "C" JNIEXPORT jstring JNICALL
Java_com_yeungeek_jnisample_NativeHelper_stringFromJNI(JNIEnv *env, jclass jclass1) {
LOGD("##### from c");
return env->NewStringUTF("Hello JNI");
}
Copy the code
- Return value: jstring
- Full path class name: com_yeungeek_jnisample_NativeHelper
- Method name: stringFromJNI
JNI development process
- Declare a native method in Java
- Compile the Java source javac to get the.class file
- Export the. H header file of jNI using the javah-jni command
- Implement Native methods declared in Java using Native code that Java needs to interact with (if Java needs to interact with C++, then implement Java Native methods in C++).
- Compile native code into a dynamic library (.dll on Windows,.so on Linux,.jnilib on Mac)
- Execute Java program through Java command, finally realize Java call local code.
The data type
Basic data types
Signature | Java | Native |
---|---|---|
B | byte | jbyte |
C | char | jchar |
D | double | jdouble |
F | float | jfloat |
I | int | jint |
S | short | jshort |
J | long | jlong |
Z | boolean | jboolean |
V | void | jvoid |
Reference data type
Signature | Java | Native |
---|---|---|
L+classname +; | Object | jobject |
Ljava/lang/String; | String | jstring |
[L+classname +; | Object[] | jobjectArray |
Ljava.lang.Class; | Class | jclass |
Ljava.lang.Throwable; | Throwable | jthrowable |
[B | byte[] | jbyteArray |
[C | char[] | jcharArray |
[D | double[] | jdoubleArray |
[F | float[] | jfloatArray |
[I | int[] | jintArray |
[S | short[] | jshortArray |
[J | long[] | jlongArray |
[Z | boolean[] | jbooleanArray |
The method signature
JNI method signature format:
(Parameter signature format...) Return value signature formatCopy the code
Native method of demo:
public static native java.lang.String stringFromJNI(a);
Copy the code
Method signatures can be generated using the Javap command:
()Ljava/lang/String;
Copy the code
JNI principle
The Execution environment of the Java language is the Java Virtual Machine (JVM), which is actually a process in the host environment. Each JVM VIRTUAL machine has a JavaVM structure in the local environment, which is returned when the Java VIRTUAL machine is created. The function for creating a JVM in a JNI environment is JNI_CreateJavaVM. JNI defines two key data structures, “JavaVM” and “JNIEnv”, both of which are essentially second-level Pointers to function tables.
JavaVM
JavaVM represents the Java virtual machine at the JNI layer. JavaVM provides “call interface” functions that you can use to create and destroy JavaVM. In theory, each process can contain multiple JavavMs, but AnAndroid only allows one JavaVM per process.
JNIEnv
JNIEnv is a thread-specific structure that represents the Java execution environment on the local thread. JNIEnv provides most of the JNI functions. Your native functions all accept JNIEnv as their first argument. JNIEnv:
- Calling a Java function
- Manipulating Java code
JNIEnv definition (jni. H) : libnativehelper/include/nativehelper jni. H
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
Copy the code
You can see JavaVM in the definition. In Android, there is only one JavaVM per process, one JavaVM structure per JVM, while it is possible to create multiple Java threads in a JVM, one JNIEnv structure per thread
Register JNI functions
How the Java world and Native world methods are related is through the JNI function registry. JNI functions can be registered in two ways:
Static registration
The method is to find the corresponding JNI function by function name and generate the JNI header file from the Javah command line
javah com.yeungeek.jnisample.NativeHelper
Copy the code
Generate the corresponding com_YEungeek_jnisample_nativeHelper. h file, generate the corresponding JNI function, and then implement this function is ok
/* * Class: com_yeungeek_jnisample_NativeHelper * Method: stringFromJNI * Signature: ()Ljava/lang/String; * /
JNIEXPORT jstring JNICALL Java_com_yeungeek_jnisample_NativeHelper_stringFromJNI
(JNIEnv *, jclass);
Copy the code
In the static registration method, how does Native find the corresponding JNI function? The process of the system is introduced in the JNI search method, and the search for static registration is not detailed. Here is a brief description of this process (declared above as example S) : When Java calls the native stringFromJNI function, it looks for Java_com_yeungeek_jnisample_NativeHelper_stringFromJNI in the corresponding JNI library. If the function is not found, an error is reported. The static registration method, which associates Java functions with JNI functions based on function names, has some disadvantages because JNI functions need to follow a specific format:
- Java classes that declare native methods need to be passed
javah
To generate the header file - JNI function names are very long
- When calling native function for the first time, it is necessary to search corresponding JNI function by function name, which is inefficient
How to solve these problems, let native function, know JNI function in advance, can solve this problem, this process is dynamic registration.
Dynamic registration
Dynamic registration is already declared in the JNI function classInit in the Camera example above.
static const JNINativeMethod gCameraMetadataMethods[] = {
// static methods
{ "nativeClassInit"."()V",
(void *)CameraMetadata_classInit }, // Corresponds to the Java layer nativeClassInit(). }Copy the code
There is a structure in JNI to record the association between Java Native methods and JNI methods. It is JNINativeMethod, which is defined in jni.h:
typedef struct {
const char* name; //Java layer native function name
const char* signature; //Java function signature, record parameter type and number, and return value type
void* fnPtr; //Native layer corresponding function pointer
} JNINativeMethod;
Copy the code
When it comes to JNI search, there are two kinds of JNI registration time. The first one has been introduced. Our customized native functions basically use System.loadLibrary(” XXX “) to associate JNI functions.
LoadLibrary (Android7.0)
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
Copy the code
Calls to the Runtime (libcore ojluni/SRC/main/Java/Java/lang/Runtime Java) loadLibrary0 method:
synchronized void loadLibrary0(ClassLoader loader, String libname) {... String libraryName = libname;if(loader ! =null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\" ");
}
//doLoad
String error = doLoad(filename, loader);
if(error ! =null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
/ / loader is null.for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.} lastError = error; }}... }Copy the code
doLoad
private String doLoad(String name, ClassLoader loader) {
// Call native methods
synchronized (this) {
returnnativeLoad(name, loader, librarySearchPath); }}Copy the code
nativeLoad
Into the virtual machine code/libcore/ojluni/SRC/main/native/Runtime. C
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
Copy the code
Then call JVM_NativeLoad JVM_NativeLoad method stated in the JVM. H, implementation in OpenjdkJvm. Cc (/ art/runtime/OpenjdkJvm OpenjdkJvm. Cc)
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std: :string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg);
if (success) {
return nullptr; }}// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
Copy the code
LoadNativeLibrary
JavaVMExt LoadNativeLibrary (art/runtime/java_vm_ext.cc) LoadNativeLibrary (art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std: :string& path,
jobject class_loader,
jstring library_path,
std: :string* error_msg) {
......
bool was_successful = false;
// select JNI_OnLoad () from JNI_OnLoad ();
// If JNI_OnLoad is found, the JNI_OnLoad method is called. The JNI_OnLoad method usually stores functions registered with the method.
// If you use dynamic registration, you must implement the JNI_OnLoad method, otherwise the native method declared in Java will throw an exception
void* sym = library->FindSymbol("JNI_OnLoad".nullptr);
if (sym == nullptr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\]" ";
was_successful = true;
} else {
// Call JNI_OnLoad. We have to override the current class
// loader, which will always be "null" since the stuff at the
// top of the stack is around Runtime.loadLibrary(). (See
// the comments in the JNI FindClass function.)
ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\]" ";
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
// Call JNI_OnLoad
int version = (*jni_on_load)(this.nullptr);
if(runtime_->GetTargetSdkVersion() ! =0 && runtime_->GetTargetSdkVersion() <= 21) {
// Make sure that sigchain owns SIGSEGV.EnsureFrontOfChain(SIGSEGV); } self->SetClassLoaderOverride(old_class_loader.get()); }... }Copy the code
The main logic in the code:
- If there is no system, it will be considered as static registration, and return true directly, indicating that the so library has been loaded successfully
- If JNI_OnLoad is found, the JNI_OnLoad method is called. JNI_OnLoad usually holds functions registered with the method
- So if you use dynamic registration must be implemented
JNI_OnLoad
Method, otherwise an exception will be thrown when calling native methods in Java
Jclass, jmethodID and jfieldID
To access the fields of an object through native code, do the following:
- Use FindClass to get a class object reference for a class
- Use GetFieldID to get the field ID of the field
- Get the content of the field using the appropriate content, such as GetIntField
The specific usage will be explained in the second article
JNI references
Three types of references are defined in the JNI specification:
- Local Reference
- Global Reference
- Weak Global Reference
Local reference
Local references, also known as Local references, are used by functions in the JNI layer. The most important feature is that when the JNI function returns, these declared references may be garbage collected
Global references
Such declared objects do not actively release resources and are not garbage collected
Weak global reference
A special global reference that may be reclaimed during runtime and needs to be checked for null before being used
reference
- Android: A clear explanation of JNI and NDK
- Android JNI learning
- Android JNI principle analysis
- Android in-depth understanding of JNI (a) JNI principle and static, dynamic registration
- JNI Tips