Introduction:

Java JNI is a Java Native Interface encapsulated to facilitate Java to call Native code such as C or C++. Java provides JNI specifically for interacting with native code because Java’s cross-platform nature leads to poor local interaction ability and some features related to the operating system cannot be completed in Java.

The main content

  • JNI development process
  • NDK development process
  • JNI data types and type signatures
  • The flow of JNI calling Java methods

The specific content

The NDK is a set of tools provided by Android that make it easier to access native code through JNI in Android. The NDK also provides cross-compilation tools that allow developers to generate dynamic libraries for specific CPU platforms by simply modifying mk files. The benefits are as follows:

  • Protection of code. Since APK’s Java layer code is easy to decompile, C/C++ libraries are more difficult to decompile.
  • C/C++ open source libraries are readily available.
  • Portable, libraries written in C/C++ can be easily reused on other platforms
  • The performance of the provider in certain situations, however, does not significantly improve the performance of the Android application.

JNI development process

Declare natvie methods in Java

Create a class

There are two native methods: get and set(String). This is the approach that needs to be implemented in JNI. JniTest head has a load, the process of dynamic libraries loaded so library name fill in is the jni – test, but so libraries full name should be libjni – test. So, this is loaded so specification of library.

Edit the Java source file to get the class file, then export the JNI header file using the Javah command

In the root path of the package, perform command operations

javac com/szysky/note/androiddevseek_14/JNITest.java
javah com.szysky.note.androiddevseek_14.JNITest
Copy the code

This will generate a com_szysky_note_androiddevseek_14_JNITest. H header in the path of the operation. This is what the second step generates:

  • Function name: Format follows :Java package name Class name Method name Between package names. Partition all replaced by partition.
  • Parameter: jstring is a String parameter. The specific type relationships will be explained later.
    • JNIEnv *: represents a pointer to the JNI environment through which to access methods provided by JNI.
    • Jobject: represents this in a Java object.
    • JNIEXPORT and JNICALL: these are macros defined by JNI species and can be found in the jni.h header file.
#ifdef __cplusplus
extern "C" {
#endif
Copy the code

This macro definition is required to specify that functions inside extern “C” will be compiled in the C naming style. If set, when JNI is implemented in C++, JNI calls will be invalid because the C/C++ compilation process has different naming styles for functions, which will cause JNI to be unable to find specific functions based on function names when linking.

Implement the Natvie method in C/C++

JNI methods are native methods declared in Java, which can be implemented in C ++ or C. The process is similar. There are only a few differences, so let’s implement both.

Create a subdirectory of any name in the home directory of the project, and copy the. H header files generated by the javah command to the created directory. Then create test. CPP and test.c files as follows:

#include "com_szysky_note_androiddevseek_14_JNITest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_szysky_note_androiddevseek_114_JNITest_get(JNIEnv *env, jobject thiz){
    printf("Execute get method \n in c++ file");
    return env->NewStringUTF("Hello from JNI .");
}
JNIEXPORT void JNICALL Java_com_szysky_note_androiddevseek_114_JNITest_get(JNIEnv *env, jobject thiz, jstring string){
    printf("Execute set method \n in c++ file");
    char* str = (char*) env->GetStringUTFChars(string, NULL);
    printf("\n, str");
    env->ReleaseStringUTFChars(string, str);
}
Copy the code
#include "com_szysky_note_androiddevseek_14_JNITest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_szysky_note_androiddevseek_114_JNITest_get(JNIEnv *env, jobject thiz){
    printf("Execute get method \n in c file");
    return (*env)->NewStringUTF("Hello from JNI .");
JNIEXPORT void JNICALL Java_com_szysky_note_androiddevseek_114_JNITest_get(JNIEnv *env, jobject thiz, jstring string){
    printf("Execute set method \n in c file");
    char* str = (char*) (*env)->GetStringUTFChars(env, string, NULL);
    printf("%s\n, str");
    (*env)->ReleaseStringUTFChars(env, string, str);
}}
Copy the code

In fact, C\C++ is similar in implementation, but it operates differently with env.

C++: env->ReleaseStringUTFChars(string, str);
C:  (*env)->ReleaseStringUTFChars(env, string, str); 
Copy the code
Compile the so library and call it in Java

To compile the so library, use the GCC. CD command to place the c/ C ++ directory you just generated. Use the following command:

gcc -shared -I /user/lib/jvm/java-7-openjdk-amd64/include -fPIC test.cpp -o libjni-test.so
gcc -shared -I /user/lib/jvm/java-7-openjdk-amd64/include -fPIC test.c -o libjni-test.so
Copy the code

/user/lib/ JVM/java-7-openJK-amd64 is the installation path of the local JDK, libjni-test.so is the name of the produced so library. Load in Java via: system.loadLibrary (“jni-test”), where lib and.so need not be specified.

Switch to the home directory and execute the Java program with the Java instruction: java-djava.library.path =jni com.ryg.jnitest. -djava.library. path=jni specifies the path to the so library.

NDK development process

Download and configure the NDK

Download the NDK development package and configure the NDK global variables.

Create an Android project and declare the required native methods
public static native String getStringFromC(a);
Copy the code
Implement native methods as stated in the Android project
  1. Generate C/C++ header files

Open the console, use the CD command to switch to the current directory of the current project, use the Javah command to generate the header file.

javah -classpath bin\classes; C:\MOX\AndroidSDK\platforms\android-23\android.jar -d jni cn.hudp.hellondk.MainActivityCopy the code

Bin \classes is the relative path of the project’s class file; C: \ MOX \ AndroidSDK \ platforms \ android – 23 \ android jar for android. The full path to the jar, because our Activity using the android SDK, so the generated header file need to he; -d jni is the generated header file output to the project’s Jni folder; Finally with the cn. Hudp. Hellondk. MainActivity is native methods in class package and class names. 2. Compile and modify the corresponding Android. mk file (mk file is the configuration file used for NDK development)

# Copyright (C) 2009 The Android Open Source Project
 # #
 Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 # #
 http:/ / www.apache.org/licenses/LICENSE-2.0
 # #
 Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.# OCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #loadLibrary(String libName) LOCAL_MODULE := hello ## LOCAL_SRC_FILES := hello.c include $(BUILD_SHARED_LIBRARY)Copy the code
  1. Write application. mk to specify the platform to generate the corresponding dynamic library, here is platform support, can also specify. Common architectural platforms are Armeabi, x86, and MIPS. Mobile devices are mainly Armeabi, so most APKS contain only Armeabi’s SO library.
APP_ABI := all
Copy the code
Switch to the parent directory of the JNI directory, and compile the SO library using the ndK-build command

By default, the ndk-build command specifies the Jni directory as the local source directory. Install the compiled so library in the app/ SRC /main/jniLbis directory of your Android project, or use the following app gradle to create a new so library directory:

Android {... sourceSets.main{ jniLibs.srcDir 'src/main/jni_libs' } }Copy the code

You can also add NDK options through the defaultConfig area:

Android {... DefaultConfig {... ndk{ moduleName "jni-test" } } }Copy the code

ProductFlavors can be packaged dynamically into apK libraries corresponding to cpus on different platforms.

Gradle android {... productFlavors{ arm{ ndk{ adiFilter "armeabi" } } x86{ ndk{ adiFilter "x86" } } } }Copy the code

Call from Android:

public class MainActivity extends Activity {
    public static native String getStringFromC(a);
    static{// Call the required so file in the static code block with the LOCAL_MODULE corresponding to the. So file;
        System.loadLibrary("hello");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Call native methods where neededToast.makeText(getApplicationContext(), get(), Toast.LENGTH_LONG).show(); }}Copy the code

More reference

JNI data types and type signatures

There are two types of JNI data types: base type and reference type. Jboolean, jchar, jint, etc.

JNI type Java type describe
jboolean boolean Unsigned 8-bit integer
jbyte byte Unsigned 8-bit integer
jchar char Unsigned 16-bit integer
jshort short Signed 16-bit integer type
jint int A 32-bit integer
jlong long A 64 – bit integer
jfloat float 32-bit floating-point type
jdouble double 64-bit floating point type
void void No type

The main reference types in JNI are classes, objects, and arrays. Their mapping to Java reference types is as follows:

JNI type Java type describe
jobject Object The Object type
jclass Class The Class type
jstring String Type String
jobjeckArray Object[] An array of objects
jbooleanArray boolean[] Boolean array
jbyteArray byte[] Byte array
jcharArray char[] Char array
jshortArray short[] Short array
jintArray int[] An array of int
jlongArray long[] Long array
jfloatArray float[] A float array
jdoubleArray double[] A double array
jthrowable Throwable Throwable

JNI’s type signature identifies a specific Java type, which can be either a class, method, or data type.

Class signature is relatively simple, it takes “L+ package name + type +;” In the form of, just put the. For example, java.lang.String, whose signature is Ljava/lang/String; At the end of; That’s part of it.

The signature of a basic data type is represented by a series of uppercase letters, as follows:

JNI type Java type describe JNI type Java type describe
boolean Z byte B char C
short S int I long J
float F double D void V

Basic data types are usually signed with the first letter of a word, except for Boolean because B is already used by byte, and the representation of long is also used by Java class signatures. So different.

For objects and arrays, the signature of the object is the signature of the class the object belongs to, and the signature of the array is [+ type signature for example, byte array. First, the type is byte, so the signature is B. Then, because it is an array, the final signature is [B.

char[]      [C
float[]     [F
double[]    [D
long[]      [J
String[]    [Ljava/lang/String;
Object[]    [Ljava/lang/Object;
Copy the code

If the array is multi-dimensional then the number of [is determined by the number of dimensions of the array, for example int[][] then it is [[I

  • The signature of the method is (parameter type signature)+ return value type signature.

Boolean fun(int a, double b, int[] c). If the signature of the parameter type is concatenated, then the signature rule for the method is (ID[I] Z

  • Void fun(int a, String s, int[] c) = ILjava/lang/String; [I)V
  • Method :int fun(), corresponding to signature ()I
  • Method :int fun(float f), corresponding to signature (f)I

The flow of JNI calling Java methods

The JNI process for calling a Java method is to find the class by its name, then find the method ID by its name, and finally call the method. If you are calling a Java non-static method, you need to construct an object of the class before you can call it.

To demonstrate calling static methods:

  1. First declare the static method to be called in Java. Here the trigger timing is a button click that adds itself.
static{
         System.loadLibrary("jni-test");
     }
 /** * defines a static method to provide to the JNI call */
 public static void methodCalledByJni(String fromJni){
     Log.e("susu"."I am calling the message from JNI, and JNI returns the value :"+fromJni );
 }
 // Define a call to a local method that calls back to a Java method
 public native void callJNIConvertJavaMethod(a);
 @Override
 public void onClick(View view) {
     switch (view.getId()){
         case R.id.btn_jni2java:
             // Call JNI's methods
             callJNIConvertJavaMethod();
             break; }}Copy the code
  1. Add a C function to JNI’s test.cpp to handle the logic of calling Java and provide a method for Java code to invoke. There are two methods.
// Define functions that call methods in Java
 void callJavaMethod( JNIEnv *env, jobject thiz){
     // Find the class to call first
     jclass clazz = env -> FindClass("com/szysky/note/androiddevseek_14/MainActivity");
     if (clazz == NULL){
         printf("Could not find the class to which the method to call belongs");
         return;
     }
     // Get the Java method ID
     // Parameter two is the name of the method to be called, and parameter three is the signature of the method
     jmethodID id = env -> GetStaticMethodID(clazz, "methodCalledByJni"."(Ljava/lang/String;) V");
     if (id == NULL){
         printf("Unable to find method to call");
         return;
     }
     jstring msg = env->NewStringUTF("I'm a string generated in C.");
     // Start calling static methods in Java
     env -> CallStaticVoidMethod(clazz, id, msg);
 }
 void Java_com_szysky_note_androiddevseek_114_MainActivity_callJNIConvertJavaMethod(JNIEnv *env, jobject thiz){
     printf("Call c code successful, now callback Java code");
     callJavaMethod(env, thiz);
 }
Copy the code

Explain it a little bit, the program according to the first class name com/szysky/note/androiddevseek_14 MainActivity find the class, and then according to the method name methodCalledByJni find ways, And pass the method the corresponding signature (Ljava/lang/String;) The final call is made through the CallStaticVoidMethod() method of the JNIEnv object.

Finally, just call the callJavaMethod method from the Java_com_szysky_note_androiddevseek_114_MainActivity_callJNIConvertJavaMethod method.

The flow is — > the button triggers the clicked onClikc — > Then JNI’s callJNIConvertJavaMethod() is called in Java — > JNI’s callJNIConvertJavaMethod() method internally calls the implementation callback in Java callJavaMethod() — > The method ends up calling Java’s methodCalledByJni() via CallStaticVoidMethod() to receive a parameter and print a log.

The file to generate the so library is stored in the app/ SRC /main/backup directory in Git. The first one is the NDK development code in section 2, and the second one is the code in section 4. The SO library is up to date and contains all the library files generated by JNI code.

The process of JNI calling Java is closely related to the definition of methods in Java. For different types of Java methods, JNIEnv provides different interfaces to call. For more details, please go to the development or visit the website to learn more.