Introduction to JNI & NDK in Android (1) The first understanding of NDK and JNI

1 NDK

The full name of NDK is Native Develop Kit, translated as a native development kit. It allows you to use C / C ++ code for Android to implement application functions. In other words, in addition to the Android SDK, there is a tool called NDK for C / C ++ development. Generally, C / C ++ is compiled into a .co file with the NDK tool, and then called in Java. NDK may not be suitable for most Android programming beginners, these beginners only need to use Java code and framework API to develop applications. However, if you need to complete one or more of the following, then the NDK can come in handy:

       Improve the security of the code, because the Java layer code is easier to be decompiled, and the so library is more difficult to decompile.

       Reuse existing C / C ++ libraries.

       The dynamic library realized by C / C ++ can be easily transplanted between other platforms

       Further improve device performance to achieve low latency or run computationally intensive applications such as games or physical simulation.

2 JNI

JNI stands for Java Native Interface, or Java Native Interface. JNI is a feature of Java calling Native language. It is a layer of interface encapsulated for the convenience of calling local code such as Java and C / C ++.

3 Configure the NDK environment

The first step is to download the NDK

Check the NDK item in Android Studio's "Default Preferences" to download the NDK. After the download is successful, you can view the NDK directory in the "Project Structure", as shown in the following figure:

The second step is to configure environment variables

Open the "terminal" and enter the command: echo $ PATH to view the current environment variables

 Enter: sudo vi ~ / .bash_profile, press Enter to enter the password, and then use vi to open the bash_profile file in the user directory. You must use sudo, otherwise you do not have permission to save files 

Press i to start editing and fill in the NDK path in your computer to the back

After editing, press the ESC key, enter: wq, you can save and exit, if you do not want to save, enter: q

4 Hello world

After the preparation work is completed, let's start to create a JNI Demo. The Demo is very simple, that is, Java can be normally called into C ++ code. After creating the project, under normal circumstances, you will see the directory where ndk is specified in the local.properties file, if not, you must manually add:

ndk.dir=/Users/liyizhi/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/liyizhi/Library/Android/sdk

The first step is to create the JNIUtils class, which will execute loading the so library file and define a native method getInfo, and call the getInfo method in MainActivity:

JNIUtils.java

public class JNIUtils {
    static {
        System.loadLibrary("jni-demo");
    }
    public static native String getInfo();
}

MainActivity.java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String info = JNIUtils.getInfo();
        TextView tv = findViewById(R.id.tv1);
        tv.setText(info);
    }
}

The second step is to execute the command and generate the .h header file. As shown in the figure below, two commands were executed, first through the command: cd app / src / main / java jump to the java folder in the project, and then use the command: javac -h. Com / zyx / jnidemo / JNIUtils.java generated Corresponding .h file. Here I use jdk1.8, if it is the earlier version of jdk, you should use the command: javah -jni com.zyx.jnidemo.JNIUtils .

The above .h header file code needs to be explained

JINEXPORT and JNICALL : Macros defined in JNI, can be found in the header file jni.h

Jstring : represents the String return type of the getInfo method

Java_com_zyx_jnidemo_JNIUtils_getInfo : is the function name, the format follows the following rules: Java_package name_class name_method name

JNIEnv * : Represents a pointer to the JNI environment, through which you can access the interface methods provided by JNI

jclass : represents this in a Java object

The third step is to create a JNI Folder and move the com_zyx_jnidemo_JNIUtils.h header file to this folder

The fourth step is to create 2 files in the jni folder just created: JNIUtils.cpp and Android.mk, their implementation is as follows:

JNIUtils.cpp

#include "com_zyx_jnidemo_JNIUtils.h"
#include <stdio.h>
#include <android/log.h>

JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo(JNIEnv * env, jclass thiz) {
    __android_log_print(ANDROID_LOG_DEBUG, "zyx", "Hello world from JNI !");
    return env->NewStringUTF("Hello world from JNI !");
}

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jni-demo
LOCAL_SRC_FILES := JNIUtils.cpp

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

In the above code, LOCAL_MODULE indicates the name of the module, LOCAL_SRC_FILES indicates the source file that needs to participate in compilation, and LOCAL_LDLIBS indicates that log output is supported in C ++ code.

The fifth step is to modify the build.gradle in the app directory, as shown in the following code, please refer to the notes for instructions:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.zyx.jnidemo"
        minSdkVersion 22
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk {
            moduleName "jni-demo"                // so库的名称
            abiFilters "armeabi-v7a", "x86"      // 支持的cpu构架平台类型,all表示编译所有cpu平台
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'    // 指向Android.mk文件
            }
        }
    }
}

The sixth step is to execute the compile and run. At this time, two types of so files will be generated in the app / build / intermediates / ndkBuild / debug / obj / local directory, where the two types correspond to the type of CPU architecture platform configured in gradle. Then run the program on the phone and you will see that Java successfully called the C ++ code and returned the result.

5 Reference to external so library

In the actual development process, the C ++ project is often separated from the Android project, or the Android project directly references the externally provided ready-made so library file. Now let's simulate this happening.

First add another Application.mk file in the jni folder, the code is as follows:

Application.mk

APP_ABI := armeabi-v7a,x86

Then switch to the parent directory of the jni directory, and then manually compile the so library through the command: ndk-build. At this time, the NDK will create a directory libs that is the same level as the jni directory. The libs store the so library.

Then modify the Gradle file:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.zyx.jnidemo"
        minSdkVersion 22
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//        ndk {
//            moduleName "jni-demo"               // so库的名称
//            abiFilters "armeabi-v7a", "x86"     // 支持的cpu构架平台类型,all表示编译所有cpu平台
//        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
//        externalNativeBuild {
//            ndkBuild {
//                path 'src/main/jni/Android.mk'  // 指向Android.mk文件
//            }
//        }
        sourceSets.main {
            jni.srcDirs = []                      // 禁用自动NDK生成调用
            jniLibs.srcDirs = ['src/main/libs']   // so库存放目录
        }
    }
}

Let me explain here, because the JNI code must be placed in the jni folder created above. If you do not want to use the jni name, you can specify the path of the JNI code through jin.srcDirs in Gradle. Because the JNI code in the above example is placed in the created jni folder by default, you must add jni.srcDirs = [], otherwise it will not compile.

Finally , compile and run again, you can see the program running on the phone again can display the same result

In addition, if there is a problem of compatibility with the old version in the actual development process, you can try to add the following line of code to the gradle.properties file:

Android.useDeprecatedNdk=true 

 

 

Click to download example

 

 

 

Published 106 original articles · praised 37 · 80,000 views

Guess you like

Origin blog.csdn.net/lyz_zyx/article/details/88669229