The NDK is a development kit provided by Android, a set of tools that enable us to embed C or C++ (” native code “) into Android applications. The NDK has the ability to build native shared libraries (so) or native static libraries (.a) from C/C++ source code, and supports associating static libraries with other libraries. JNI is the interface that Java and C/C++ components use to communicate with each other. This way, through the NDK and JNI, we can easily use C and C++ code in Android applications.

The default build tool for Android Studio to compile native libraries is CMake, which is suitable for cross-platform projects. Since many existing projects use the NdK-build build kit, Android Studio also supports The NdK-build, which is faster than CMake, but only for Android.

ndk-build

Let’s take a look at chestnuts:

  • I’ve defined a native method, stringFromJNI(), which I want to implement in c++ code and use the NDK to build.so files (optionally named) from the c++ source for my Java layer to call. How do I do that?
  /* com.blog.a.jni.HelloJni.java */
  public class HelloJni {
      public native String stringFromJNI(a);
  }
Copy the code

To make it more intuitive, I’ll paste the overall directory structure below:

  • The first thing I need to know is the.h header for the helloJni.java native method, which is easy to get with the Javah command. The helloJni. class file can be compiled using javac command, in this case directly with AS automatically compiled.
  $ cd /Users/zuomingjie/gitSpace/BlogSample/app/build/intermediates/javac/debug/classes/
  $ javah com.blog.a.jni.HelloJni
Copy the code
  • Hellojni. class compiled.h header file, the format is very elegant, Java header + class fully qualified name + method name, more specification and JNI knowledge can see developer:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_blog_a_jni_HelloJni */

#ifndef _Included_com_blog_a_jni_HelloJni
#define _Included_com_blog_a_jni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/* * Class: com_blog_a_jni_HelloJni * Method: stringFromJNI * Signature: ()Ljava/lang/String; * /
JNIEXPORT jstring JNICALL Java_com_blog_a_jni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
Copy the code
  • Write our native-lib. CPP (optionally named) to implement this header by returning a string:
#include <jni.h>
#include <string>

#include "HelloJni.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_blog_a_jni_HelloJni_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
Copy the code
  • Write Application. Mk file and refer to developers for more other configurations:
The C++ standard library for this application.
Use system STL by default. Other options include c++_shared, c++_static, and none
APP_STL := c++_shared
Pass the tag to compile for all C++ in the project. These tags are not used in C code
APP_CPPFLAGS := -frtti -fexceptions
# schema, all: APP_ABI := all
APP_ABI := armeabi-v7a arm64-v8a
# Declare the Android API level for which this application is built, corresponding to the minSdkVersion of the application
APP_PLATFORM := android-19
Copy the code
  • Writing an Android.mk file:
# This variable represents the location of the source file in the development tree.
# In the above command, the macro function my-dir provided by the build system returns the path to the current directory (the directory where the Android.mk file itself is located).
LOCAL_PATH := $(call my-dir)
Declare the CLEAR_VARS variable, whose value is provided by the build system
The # CLEAR_VARS variable points to a special GNU Makefile that clears many LOCAL_XXX variables for us
include $(CLEAR_VARS)

The variable stores the name of the module you want to build
LOCAL_MODULE    := myJniTest
# Enumerates source files, separating multiple files with Spaces
LOCAL_SRC_FILES := cpp/native-lib.cpp

# Shared library
include $(BUILD_SHARED_LIBRARY)
Copy the code
  • Build the.so file with the nk-build command (the NDK compilation system will look for the name Android.mk by default in the $(APP_PROJECT_PATH)/jni directory) :
  $ cd /Users/zuomingjie/gitSpace/BlogSample/app/src/main/java/com/blog/a/jni
  $ ndk-build
Copy the code
  • Since the generated.so file is in the jNI libs directory, we can copy it directly to the jniLibs directory, or specify it directly in the gradle file:
  sourceSets {
      main() { jniLibs.srcDirs = ['src/main/java/com/blog/a/libs']}}Copy the code

CMake

The Android NDK supports compiling our C and C++ code using CMake, which is the most common way in daily development. It is very easy to build native libraries or compile.so/.a files by writing the build script cmakelists. TXT.

For intuition, I’ll paste the overall directory structure:

This is my newly created cmakelists. TXT file in any location. When you create a Navive C++ project with AS, AS will automatically configure the CMake environment and generate cmakelists. TXT file:

cmake_minimum_required(VERSION 3.41.# build a so shared library from native-lib. CPP and name ithello
add_library(# building library name hello # # SHARED library SHARED libraries of the original file, here with CMakeLists. TXT with directory, just write hello_lib. The CPP hello_lib. CPP)Use find_library to find the library to associate withfind_library(# set the name of the path variable. log-lib # set the name of the path variable. log# Link allows you to load both the source library and the third-party librarytarget_link_libraries${log-lib} ${log-lib}
Copy the code
  • In gradle file configuration, CMakeLists file location should be consistent:
  externalNativeBuild {
      cmake {
          path "src/main/java/com/blog/a/cpp/CMakeLists.txt"
          version "3.10.2"}}Copy the code
  • Create the Java equivalent loading class and use static code to quickly load libhello.so, which we added in CMakeLists.
public class HelloCMakeJni {
    static {
        System.loadLibrary("hello");
    }
    public native String stringFromCmakeJNI(a);
}
Copy the code
  • Create our native-lib. CPP and implement the logic for stringFromCmakeJNI. Note the naming conventions here, so we won’t expand:
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_blog_a_cpp_HelloCMakeJni_stringFromCmakeJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++ By cMaker";
    return env->NewStringUTF(hello.c_str());
}
Copy the code
  • Finally, it can be called in our Activity. And in the app/build/intermediates/cmake/debug/obj directory or become so file, here is not map, can be directly see lot BlogSample:
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.jni_show_layout)
        findViewById<TextView>(R.id.sample_text).text = showJNIStr()
    }

    fun showJNIStr(a) = StringBuilder().apply {
        append(HelloCMakeJni().stringFromCmakeJNI())
        append("\n")
        append(HelloJni().stringFromJNI())
    }.toString()
Copy the code

Call the source CPP method

CPP: native-lib. CPP: native-lib. CPP: native-lib. CPP: native-lib. CPP: native-lib. CPP: native-lib. CPP: native-lib. CPP:

For intuition, I’ll paste the overall directory structure:

  • Write the header file method2.h and define the method getHelloWorld:
#ifndef _GET_HELLO_WORLD_H_
#define _GET_HELLO_WORLD_H_

extern const char* getHelloWorld(a);

#endif
Copy the code
  • Write method2.cpp and implement the getHelloWorld method, which simply returns “HelloWorld” :
#include "method2.h"

extern const char* getHelloWorld(a) {
    return "HelloWorld";
}
Copy the code
  • We call this method in native-lib. CPP:
#include <jni.h>
#include <string>

#include "method2.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_blog_a_cpp_HelloCMakeJni_stringFromCmakeJNI(
        JNIEnv* env,
        jobject /* this */) {
    std: :string hello = getHelloWorld();
    return env->NewStringUTF(hello.c_str());
}
Copy the code
  • Finally, add method2.cpp to cmakelists.txt add_library:
add_library( # Sets the name of the library.
             hello

             # Sets the library as a shared library.
             SHARED

             method2.cpp
             hello_lib.cpp )
Copy the code

Associate a third-party SO library

In a real world scenario, our c++ source library might also reference some other tripartite libraries. How would that work? Here’s an example:

On the basis of our above CMake, then associate the SO file produced by the NDK-build in our liBS directory. For the sake of intuition, I first paste the overall directory structure:

  • First, place the.h file corresponding to the methods provided by the so library in the include directory. The purpose is to call the source library CPP when needed:
This allows us to use the methods provided in so.h within the source repository#include "xxx.h"
Copy the code
  • To configure the cmakelists. TXT file, import our libmyJniTest. So, here are the highlights:
Associate our.so with our hello_lib.cpp
include_directories(include)# import tripartite libraryadd_library(myJniTest SHARED IMPORTED)# set the name of the associated so library, the target location # ${CMAKE_SOURCE_DIR} is the directory of cmakelists. TXT set_target_properties(myJniTest PROPERTIES IMPORTED_LOCATION) ${CMAKE_SOURCE_DIR}/.. /libs/${ANDROID_ABI}/libmyJniTest. So myJniTest # Links the target library to thelog library
                       # included in the NDK.${log-lib} )
Copy the code
  • The libmyJniTest. So file is now in our Hello library. So HelloJni comments out loadLibrary and still works because libmyJniTest. So is loaded when libhello.so is loaded.
public class HelloJni {
    /* The hello library already references libmyJniTest. So, so when hello. So is loaded, myJniTest is automatically associated with */
    // static { System.loadLibrary("myJniTest"); }
    public native String stringFromJNI(a);
}
Copy the code

In the next section, we will talk about how a Java c++ call with an argument can generate a.so library by associating.a and.a libraries. I have uploaded the demo to Github, please check it if you need clone.

That’s the end of this article. If this article is useful to you, give it a thumbs up. Everyone’s affirmation is also the motivation for Dumb I to keep writing.