本文主要介绍调用 jni 的方式, 分别使用 NDK 和 cmake 列举。

博主博客

NDK 配置编译

一、配置NDK路径

1.右键项目名称。
2.选择open modules settings
3.选择SDK Location选项卡。
4.设置Android NDK location的路径(例如我的路径为 C:\sdk\ndk-bundle)。

二、Java调用方法

例如我在文件com.uso6.www.ndktest.MainActivity中加入Java的调用方法。

public native String getResult();

static {
	System.loadLibrary("myNdk");
}

三、生成调用头文件

使用命令行,进入java目录, 然后使用命令 javah
javah的使用:javah -d <路径> <包名>.<类名>

我使用的命令为javah -d ../jni com.uso6.www.ndktest.MainActivity

jdk11需要使用 javac -h 代替 javah, 比如 javac -h ../jni com/uso6/www/ndktest/MainActivity.java

调用命令之后,会在java文件夹的同级生成一个jni文件夹,jni文件夹下有一个com_uso6_www_ndktest_MainActivity.h头文件。

四、实现头文件功能

jni目录下新建hi.c(名字随便取.c/.cpp都行)。
hi.c内容如下:

#include "com_uso6_www_ndktest_MainActivity.h"
/*
 * Class: com_uso6_www_ndktest_MainActivity
 * Method: getResult
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_uso6_www_ndktest_MainActivity_getResult (JNIEnv* env, jobject obj){
	return (*env)->NewStringUTF(env, "Hello Test NDK!");
}

五、修改配置

1.在gradle.properties末尾加入:android.useDeprecatedNdk=true
2.在build.gradle(Module app)defaultConfig里面加入ndk支持。

    ndk {
        moduleName "myNdk"
        ldLibs "log", "z", "m"
        abiFilters "armeabi", "armeabi-v7a", "x86"
    } 

六、运行

在需要调用的地方使用getResult()即可。

Cmake 配置编译

文件结构如下图所示:
文件结构

一、配置build.gradle

1.在android里面的defaultConfig中加入

externalNativeBuild {
	cmake {
		cppFlags ""
		// 生成多个版本的so文件
		abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86','x86_64'
	}
}

2.在android中加入

// 配置CMakeLists.txt路径
externalNativeBuild {
	cmake {
		path "CMakeLists.txt"  // 设置所要编写的c源码位置,以及编译后so文件的名字
	}
}

二、配置CMakeLists.txt

1.在项目的app文件夹下新建CMakeLists.txt

# CMake 的最小版本
cmake_minimum_required(VERSION 3.4.1)

# C 的编译选项是 CMAKE_C_FLAGS
# CMAKE_CXX_FLAGS
# CMAKE_CXX_FLAGS_DEBUG/CMAKE_CXX_FLAGS_RELEASE
# 指定编译参数,可选
# SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

# so输出路径 CMAKE_LIBRARY_OUTPUT_DIRECTORY
# 设置生成的so动态库最后输出的路径, 获取当前编译的abi , ANDROID_ABI
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

# 设置头文件搜索路径(和此txt同个路径的头文件无需设置),可选
#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common)

# 指定用到的系统库或者NDK库或者第三方库的搜索路径,可选。
#LINK_DIRECTORIES(/usr/local/lib)

# 配置头文件的包含路径
# include_directories(${PROJECT_SOURCE_DIR}/src/main/cpp/)

add_library(
    one
    SHARED
    src/main/cpp/OneNative.cpp
)

set_target_properties(
    one
    PROPERTIES IMPORTED_LOCATION
    ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libone.so
)

find_library(
    log-lib
    log
)

target_link_libraries(
    one
    ${log-lib}
)
# 添加子目录,将会调用子目录中的CMakeLists.txt
ADD_SUBDIRECTORY(src/main/cpp/two)

2.在项目的app/src/main/cpp/two文件夹下新建CMakeLists.txt。

# CMake 的最小版本
cmake_minimum_required(VERSION 3.4.1)

add_library(
    two
    SHARED
    TwoNative.c
)

set_target_properties(
    two
    PROPERTIES IMPORTED_LOCATION
    ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libtwo.so
)

find_library(
    log-lib
    log
)

target_link_libraries(
    two
    ${log-lib}
)

三、Java调用方法

新建类OneJni.java和TwoJni.java。

package uso6.com.cmake;
public class OneJni {

    static {
        System.loadLibrary("one");
    }

    public native String getString();

}
package uso6.com.cmake;
public class TwoJni {

    static {
        System.loadLibrary("two");
    }

    public native String getString();

}

四、生成调用头文件

使用命令行,进入java目录, 然后使用命令 javah
javah的使用:javah -d <路径> <包名>.<类名>

我使用的命令为:
javah -d ../cpp uso6.com.cmake.OneJni
javah -d ../cpp/two uso6.com.cmake.TwoJni

注: 如果使用的是JDK10以上, 没有 javah 命令,需要用
javac -h ../cpp uso6.com.cmake.OneJni.java
javac -h ../cpp/two uso6.com.cmake.TwoJni.java
代替。

调用命令之后,会在java文件夹的同级生成一个cpp文件夹,cpp文件夹下有一个uso6_com_cmake_OneJni.h头文件和two文件夹,在two文件夹下有一个uso6_com_cmake_TwoJni.h头文件。

五、实现头文件功能

cpp目录下新建OneNative.cpp
OneNative.cpp内容如下:

#include "uso6_com_cmake_OneJni.h"

JNIEXPORT jstring JNICALL Java_uso6_com_cmake_OneJni_getString
  (JNIEnv *env, jobject object) {
    return env->NewStringUTF("I am one.");
  }

two目录下新建TwoNative.c
TwoNative.c内容如下:

#include "uso6_com_cmake_TwoJni.h"
JNIEXPORT jstring JNICALL Java_uso6_com_cmake_TwoJni_getString
  (JNIEnv *env, jobject object) {
    return (*env)->NewStringUTF(env, "I am two.");
  }

六、运行

在需要调用的地方使用OneJni和TwoJni类即可。

七、JNI 类型对应表

JNI Types
Type Signature
jobject Ljava/lang/Object;
jclass Ljava/lang/Class;
jstring Ljava/lang/String;
jthrowable Ljava/lang/Throwable;
jobjectArray Ljava/lang/Object;
jarray [<type>
jarray [Z
jbyteArray [B
jcharArray [C
jshortArray [S
jintArray [I
jlongArray [J
jfloatArray [F
jdoubleArray [D
Primitive Types
Type Signature
jboolean Z
jbyte B
jchar C
jshort S
jint I
jlong J
jfloat F
jdouble D
Other
Type Signature
void V
Custom type L<fully-qualified-name>;

八、常用的数据转换

std::string转jstring

jstring toJString(JNIEnv* env, const std::string& value) {
	return env->NewStringUTF(value.c_str());
}

jstring转std::string

std::string toStdString(JNIEnv* env, jstring value) {
	jboolean isCopy;
	const char* c_value = env->GetStringUTFChars(value, &isCopy);
	std::string result(c_value);
	if (isCopy == JNI_TRUE) {
		env->ReleaseStringUTFChars(value, c_value);
	}
	return result;
}

bool转jboolean

jboolean toJBool(bool value) {
	return value ? JNI_TRUE : JNI_FALSE;
}

jboolean转bool

bool ToCppBool(jboolean value) {
	return value == JNI_TRUE;
}

jint转QString
static QString number(int, int base=10);

QString toQString(jint value) {
    return QString::number(value);
}

九、常用的方法

// 获取 field
env->getFieldID(class, "name", field类型);
// 获取method
env->getMethodID(class, "方法名", (参数)返回类型);

// New 完后要删除
jstring string = env->newStringUTF(const char *);
env->DeleteLocalRef(string);