Before we move on to NDK development in AS, let’s briefly introduce some of the most confusing concepts:

  1. What exactly is JNI and what is NDK?
  2. What is “cross-compilation”?

So what is JNI? JNI stands for Java Native Interface. Like me, when you hear about interfaces, you’re probably confused: “I know it stands for Java native development interface, but I still don’t know what it means.” In fact, JNI is a kind of protocol. When it comes to protocol, it is a norm and constraint for something. To put it better, it is standardization. If you want to use my stuff, then you have to follow my rules. Like HTTP protocol, HTTP as a hypertext transfer protocol, it regulates our Internet from the client side to the server side and a series of operational processes. Because of this, we have unimpeded access to the Internet. The same is true for JNI, except that JNI is a protocol used to communicate Java code with external native code (C/C ++). JNI allows Java code to call C/C++ code, and C/C++ code to call Java code. Without this protocol, it would be impossible for Java and C/C++ code to call each other. Here’s a quick look at where the JNI protocol fits into the system architecture with two diagrams:

                                     

In the figure above, the upper green part is usually written in Java code, and the lower orange part is usually written in C/C++ code. As you can see, because of the presence of the intermediate JNI, we can call something in the Application layer through JNI. Now that we know the concept of JNI, we can take a look at the NDK, which is a “toolkit” for Native Development. Java development needs to use JDK, Android development needs to use SDK, so we need to carry out native development in Android, also need to use its corresponding toolkit, namely NDK. In layman’s terms, the NDK is a set of tools that allow you to use C/C++ to accomplish specific functions in Android applications. There are many functions of NDK, let’s briefly list two, for example:

1. First, NDK helps developers develop C(or C++) dynamic libraries “quickly”.

2. Second, the NDK integrates a “cross-compiler”. Using the NDK, we can develop high-performance application logic in C, thus improving the efficiency of application execution.

We mentioned “cross-compile” above, but let’s finally explain what cross-compile is. It is well known that when a compiler concatenates intermediate code into a binary program that can be executed on the current computer, the concatenator converts it based on the current computer’s CPU and operating system type. The CPU architecture varies according to the running device. There are three common CPU architectures:

  • Arm structure: mainly in mobile handheld, embedded devices. Almost all of our phones use this CUP architecture.
  • X86 architecture: mainly used in desktop computers and notebooks. Such as Intel and AMD cpus.
  • MIPS architecture: used in gateways, modems, set-top boxes and other devices.

If you want to compile code on an x86 based CPU that can run on an ARM based CPU, you must use cross-compilation. So to summarize: cross-compilation is the compilation of binary code on one platform (e.g. X86 CPU architecture, Windows operating system) that can be executed on another platform (e.g. Arm CPU architecture, Linux operating system). The NDK provided by Google does the job.

Ok, after the introduction of the above basic concepts, we officially enter the AS NDK development explanation.

 

The preparatory work

First, you need to download the relevant NDK files. As shown in the figure below, the red boxes are selected for use in development.

  • NDK: Native libraries are used through the ndK-build method
  • CMake: Use the local library through the CMake method
  • LLDB: a tool for debugging C/C++

 

Write the code

After the NDK development environment is configured, a TextView is added to the layout file of the project, and a string is returned by calling the C/C++ code written at the bottom, which is finally rendered on the TextView.

The specific code content is as follows:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.text); textView.setText(JNIUtils.getString()); }}Copy the code

JNIUtils is mentioned in a moment. Next, in the MainActivity class, create a new class that contains a native method.

Public class JNIUtils {public static native String getString(); }Copy the code

However, it can be found that the method name is floating red, indicating that it has not been identified:

 

Hover the mouse over it and it will tell us that the corresponding JNI header file is not found. So all we need to do is generate the header corresponding to the getString() method.

Generate.h header file

Enter the following commands in the Terminal command line window of the AS and press Enter:

cd app
cd src/main/java
javah -classpath . -jni com.example.shenjiaqi.myapplication.JNIUtils
Copy the code

Compile jniutils.java using the javac command, and then compile the jNI headers using the javah-jni command.

Here we use the following command:

Javah-classpath. -jni Package name. The name of the class. javah -classpath . -jni com.example.shenjiaqi.myapplication.JNIUtilsCopy the code

Note that the compile command must be run under the Java directory. If you compile successfully without a pit, you will see a.h file in the… SRC \main\ Java directory.

Next create a jni directory in your project and cut the newly generated.h file to this directory:

Let’s take a look at the contents of the.h file. In terms of Java concepts, these are equivalent to abstract methods in the interface, and we need to create.c files to implement these methods, which also realize the native methods defined by us:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_shenjiaqi_myapplication_JNIUtils */

#ifndef _Included_com_example_shenjiaqi_myapplication_JNIUtils
#define _Included_com_example_shenjiaqi_myapplication_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_shenjiaqi_myapplication_JNIUtils
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_shenjiaqi_myapplication_JNIUtils_getString
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif
Copy the code

Next, create a new c++ file and create a jnihello. CPP file in the jni directory to implement the abstract method in the.h file:

#include "com_example_shenjiaqi_myapplication_JNIUtils.h" JNIEXPORT jstring JNICALL Java_com_example_shenjiaqi_myapplication_JNIUtils_getString (JNIEnv *env, jclass jclass){ return env->NewStringUTF("Hello World From JNI!!!!!" ); }Copy the code

As you can see, we first need to import the JNIUtlis header file that was originally generated. The following code is basically copied and pasted from com_example_shenjiaqi_myApplication_jniutils.h and then modified slightly. The main changes are the two parameters of getString and the simple implementation inside, the parameter aspect is to add env and jclass two fields. The implementation inside the function simply returns the string “Hello World From JNI!!!!!” Env ->NewStringUTF(” XXXXXX “); env->NewStringUTF(” XXXXXX “); This line of code

The directory structure is shown below:

The NDK configuration

Next we add the NDK configuration in build.gradle:

Error found when running project:

Don’t panic, say let’s use CMake or NDK -build mode to achieve success. At this point we open the build directory as shown below:

There is a file called android. mk, which is needed to generate the.so file for us. The steps are as follows:

Select ndK-build in the popover and find the android. mk file mentioned earlier.

At this point, we go back to jniutils.java and find that there is no flutters. However, an error message will appear after running the compilation:

Said it didn’t find a way to implement getString (). Add the following code to JNIUtils to solve the above problem. You can see that the corresponding.so file is already generated in build.

public class JNIUtils {
    static {
        System.loadLibrary("JNIHello");
    }
    public static native String getString();
}
Copy the code

Compile again, run successfully:

        

 

Demo address: Android JNI programming example

 

References:

1. Import the application code from Eclipse into Android Studio by nk-build and Cmake methods (android_serialport_API as an example)

2. NDK development (a)———— How to develop NDK in Android Studio

3. Write your first JNI application using AndroidStudio