List of articles in this series:

Introduction to Android JNI (1) – The first Android JNI project

Android JNI Introduction (ii) – A detailed analysis of the first JNI project

Android JNI introduction (iii) – Java and Native calls to each other

Android JNI Introduction (4) – Exception handling

Android JNI Introduction (5) – Function registration

Android JNI introduction (6) – Rely on other libraries

Android JNI Introduction (7) – Reference management

Android JNI Introduction (eight) – The use of CMakeLists


In the last article, we introduced the rough structure of a JNI project, and this article will cover some of the details of the project.

I. Introduction to the operation process

For a general JNI project, the process of compiling, installing, and running is as follows:

Compile:

  1. The configuration of engineering
  2. Compiling dynamic libraries
  3. Package dynamic libraries into APK

Installation:

  1. The system will install the dynamic library of the primary ABI according to the ABI supported by the device. If the dynamic library cannot be found by the primary ABI, the system will continue to install the dynamic library by the secondary ABI. If the dynamic library still cannot be found, the system will not install the dynamic library, which is also explained on the Android developer website

Run:

  1. At runtime, dynamic libraries need to be loaded first, which we typically use in a static block of code for a classSystem.loadLibraryLoad the dynamic library and parse the symbols
  2. If the library cannot be found, it will be promptedjava.lang.UnsatisfiedLinkErrorAnd print related information in logs
  3. If the load is successful, the function will be parsed, first will be useddlsymFunction to check if it is overwrittenJNI_OnLoadFunction, which is executed if overridden
  4. At run time, for already inJNI_OnLoadFunction for dynamic registration of functions, you can directly find the corresponding function to run; For functions that are not dynamically registered, theJava_ package name _ Class name _ function nameOf course, the parameters and return values of the function also need to be verified

This is roughly the process used, but let’s go back to the project and explain some of the details of the default project.

Ii. Description of project details

  • The Java part does two things:

    1. Loading the Dynamic library

      Because dynamic libraries only need to be loaded once, we usually load them in the static code block of the class, which has the added benefit of early error detection
      static {
          System.loadLibrary("native-lib");
      }
      Copy the code
    2. Native function declaration

      The following declaration says that this function is native and does not pass any arguments, but returns a String
      public native String stringFromJNI();
      Copy the code
  • JNIEXPORT Returned value type: jstring Parameter type: JNIEnv* and jobject are automatically added

    extern "C" JNIEXPORT jstring JNICALL Java_com_wsy_jnidemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */){
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    Copy the code
  • Part CMakeLists

    Declare the lowest version of cmakeCmake_minimum_required (VERSION 3.4.1 track)SRC /main/native-lib.cpp/SRC /main/native-lib.cpp
    add_library( native-lib The name of the library
                 SHARED # SHARED a STATIC library
                 src/main/native-lib.cpp # source file, can be multiple
                 )
                 
    Find the system log library, save in the log-lib variable
    find_library( log-lib 
                  log )
                  
    # native-lib will rely on log-lib
    target_link_libraries( native-lib
                           ${log-lib} )
    Copy the code

Iii. Detailed explanation of native function declaration

Let’s take a closer look at this native function. Although its implementation is very simple, what are these messy symbols?

1. extern "C"

  • Extern “C”

    Extern “C” and extern “C” are extern and extern and extern “C” are extern and extern and extern are extern and extern and extern are extern and extern and extern are extern and extern and extern are extern and extern and extern are extern and extern and extern are extern and extern and extern are extern and extern.

    The Duplicate declaration of function XXX is not supported by Android Studio. The Duplicate declaration of function XXX is not supported by Android Studio.

  • So what happens if I get rid of extern “C”?

    First, the compiler will kindly prompt you:

    And suggest you

  • What happens if you don’t take advice?

    The crash

        Process: com.wsy.jnidemo, PID: 27921 
        java.lang.UnsatisfiedLinkError: No implementation found forjava.lang.String com.wsy.jnidemo.MainActivity.stringFromJNI() (tried Java_com_wsy_jnidemo_MainActivity_stringFromJNI and  Java_com_wsy_jnidemo_MainActivity_stringFromJNI__) at com.wsy.jnidemo.MainActivity.stringFromJNI(Native Method) at com.wsy.jnidemo.MainActivity.onCreate(MainActivity.java:22) at android.app.Activity.performCreate(Activity.java:7815) at  android.app.Activity.performCreate(Activity.java:7804) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1318) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3349) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3513) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2109)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7682)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
    Copy the code

    The prompt appears after the button is clicked, which means that the dynamic library loaded successfully, which means that the function name has been changed.

  • What is the function name changed to?

    • Tool path

      We can use the nm provided in the NDK for analysis, assuming that our dynamic library is Armeabi-V7A, Toolchains arm-linux-androideabi-4.9 prebuilt Windows-x86_64 bin arm-linux-androideabi-nm.exe toolChains arm-linux-androideabi-4.9 prebuilt Windows-x86_64 bin arm-linux-androideabi-nm.exe toolChains arm-linux-androideabi-4.9 prebuilt Windows-x86_64 bin Do not choose other directories, although there may be compatibility, but some are incompatible.

    • Tool Usage

      Nm-d dynamic library file

    • Analyze two extern ‘C’ dynamic libraries with and without extern ‘C’

      Perform gradlew externalNativeBuildRelease obtaining dynamic library, parsing

      • addextern 'C'The dynamic library
      • Do not addextern 'C'The dynamic library

For static registration function, function name changes, according to the original function, can’t find the corresponding rules. So will be submitted to the Java lang. UnsatisfiedLinkError

2. JNIEXPORTThe macro

Are defined as follows

#define JNIEXPORT __attribute__ ((visibility ("default")))
Copy the code

JNIEXPORT describes that its visibility is default, so we can remove it without confusing the native code, but what if we change visibility to hidden?

Let’s try changing the function declaration to:

 extern "C" __attribute__ ((visibility ("hidden"))) jstring JNICALL Java_com_wsy_jnidemo_MainActivity_stringFromJNI
Copy the code

Run:

3. JNICALLThe macro

This macro is defined as follows

#define JNICALL
Copy the code

Yes, this is empty, this can be used for marking, the compilation of the marking function used this macro, as for the function, has not been found, have a friend to inform you.

Iv. Description of native function parameters and return values

Function declaration:

extern "C" JNIEXPORT jstring JNICALL
Java_com_wsy_jnidemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

Extern “C”, JNIEXPORT, and JNICALL are all specified above. Let’s look at the function name and return value.

1. The function name

JNI provides a set of rules to implement function name lookup during static registration by:

Java_ package name _ Class name _ function name

By the way, when we did not carry out dynamic registration, the function was registered when the function was called for the first time. Therefore, when the dynamic library loading was completed, the time of calling a native function for the first time would be higher than the time of subsequent calls. We can verify this:

  • Call way
    for (int i = 0; i < 100; i++) {
        long start = System.nanoTime();
        stringFromJNI();
        long end = System.nanoTime();
        Log.i(TAG, "onCreate: " + (end - start));
    }
Copy the code
  • The log

Parameters of 2.

JNI functions automatically add two arguments: JNIEnv * and jobject. The first argument is JNIEnv *env, and JNI operations depend on this variable. The second argument is jClass when a Java function is static and jobject when a member function, where jClass is a subclass of Jobject.

3. The return value

For Java functions, the return value is a java.lang.String object, corresponding to the Native JString object.

This is an introduction to a JNI project.