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 passedjavahTo 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 implementedJNI_OnLoadMethod, 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:

  1. Use FindClass to get a class object reference for a class
  2. Use GetFieldID to get the field ID of the field
  3. 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