本文主要介绍调用 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调用方法
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();
}
Kotlin
package uso6.com.cmake
class OneJni {
init {
System.loadLibrary("one");
}
external fun getString() : String
}
四、生成调用头文件
使用命令行,进入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
头文件。
如果不想用命令, 可以把我下面的代码当作模版
#include <jni.h>
#ifndef ONENATIVE_H
#define ONENATIVE_H
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_uso6_com_cmake_OneJni_getString(JNIEnv *, jobject);
#ifdef __cplusplus
};
#endif
#endif //ONENATIVE_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);