稀土掘金技术社区 01月02日
一个Kotlin版Demo带你入门JNI,NDK编程
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入介绍了Android NDK开发中JNI的基础知识,包括JNI方法注册(静态与动态)、基础与引用数据类型、函数签名、JNIEnv的使用以及CMake编译配置。文章通过多个示例,详细展示了JNI如何与Java层进行交互,如获取JNI返回字符串、调用Java无返回值方法、传递参数、反射调用Java方法、访问Java变量以及实现带回调的交互。此外,还介绍了动态注册的实现方式,并提供了相关示例代码,为Android开发者提供了全面的NDK开发入门指南。

📖 JNI(Java Native Interface)是Java字节码调用C/C++的桥梁,而NDK(Native Development Kit)则是一套允许在Android应用中嵌入C/C++代码的工具集,它们是Android开发者进入深水区的必备技能。

📝 JNI方法注册分为静态注册和动态注册。静态注册要求JNI函数名遵循特定格式,初次调用效率较低;动态注册通过JNINativeMethod结构建立函数映射表,在JNI_OnLoad中完成注册,更加灵活高效。

🧮 JNI定义了Java与C/C++之间的数据类型映射,包括基础数据类型(如jint对应int)和引用数据类型(如jstring对应String)。函数签名信息用于区分重载方法,格式为“(参数类型)返回值类型”。

⚙️ JNIEnv是线程相关的结构体,代表Java在本线程的运行环境,通过它可以调用JNI系统函数。在C++子线程中获取JNIEnv需要使用JavaVM的AttachCurrentThread函数,并在线程退出时调用DetachCurrentThread释放资源。

🛠️ CMake是跨平台编译工具,用于生成makefile或project文件。`add_library`指令用于编译生成库文件,`target_link_libraries`指令用于添加链接库,`find_library`指令用于查找库文件路径。

原创 Wgllss 2025-01-02 08:30 重庆

点击关注公众号,“技术干货”及时达!

Android 越往深处研究,必然离不开NDK,和JNI相关知识

一、前言

Android开发中,最重要的一项技能便是NDK开发,它涉及到JNI,C,C++等相关知识
我们常见的MMKV,音视频库FFmpeg等库的应用,都有相关这方面的知识,它是Android开发人员通往深水区的一张门票。

本文我们就简单介绍JNI,NDK的相关入门知识:
「1. JNI方法注册(静态注册,动态注册)」
「2. JNI的基础数据类型」
「3. JNI引用数据类型」
「4. JNI函数签名信息」
「5. JNIEnv的介绍」
「6. JNI编译之Cmake」


「7. 示例:获取JNI返回字符串(静态注册)」
「8. 示例:调用JNI,JNI调用Java层无返回值方法(静态注册)」
「9. 示例:调用JNI,JNI调用Java层无返回值方法(带参数)(静态注册)」
「10. 示例:调用JNI去调用java方法(静态注册)」
「11. 示例:调用JNI去调用java 变量值(静态注册)」
「12. 示例:去调用JNI去调用Java带callback方法,带参数(静态注册)」
「13. 示例:动态注册」

二、基础介绍

「1. JNI是什么?」   JNI(Java Native Interface),它是提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术。

「2. NDK是什么?」 NDK(Native Development Kit)
Android NDK 是一组允许您将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集。能够在 Android 应用中使用原生代码对于想执行以下一项或多项操作的开发者特别有用:

「3. JNI方法静态注册:」
JNI函数名格式(需将”.”改为”—”):
Java_ + 包名(com.example.auto.jnitest)+ 类名(MainActivity) + 函数名(stringFromJNI)

静态方法的缺点:

「4. JNI方法动态注册:」
Java与JNI通过JNINativeMethod的结构来建立函数映射表,它在jni.h头文件中定义,其结构内容如下:

typedef struct {    const char* name;    const char* signature;    void*       fnPtr;} JNINativeMethod;

创建映射表后,调用RegisterNatives函数将映射表注册给JVM;
当Java层通过System.loadLibrary加载JNI库时,会在库中查JNI_OnLoad函数。可将JNI_OnLoad视为JNI库的入口函数,需要在这里完成所有函数映射和动态注册工作,及其他一些初始化工作。

「5. JNI的基础数据类型对照表:」

Java类型JNI类型描述
boolean(布尔型)jboolean无符号8位
byte(字节型)jbyte有符号8位
char(字符型)jchar无符号16位
short(短整型)jshort有符号16位
int(整型)jint有符号32位
long(长整型)jlong有符号64位
foat(浮点型)jfloat32位
double(双精度浮点型)jdouble64位

「6. JNI引用数据类型对照表:」

Java引用类型JNI类型Java引用类型JNI类型
All objectsjobjectchar[ ]jcharArray
java.lang.Classjclassshort[ ]jshortArray
java.lang.Stringjstringint[]jintArray
java.lang.Throwablejthrowablelong[ ]jlongArray
Object[ ]jobjectArrayfloat[]jfloatArray
boolean[ ]jbooleanArraydouble[ ]jdoubleArray
byte[ ]jbyteArray

「7. JNI函数签名信息」
由于Java支持函数重载,因此仅仅根据函数名是没法找到对应的JNI函数。为了解决这个问题,JNI将参数类型和返回值类型作为函数的签名信息。

JNI规范定义的函数签名信息格式:  「(参数1类型字符…)返回值类型字符」

函数签名例子:

JNI常用的数据类型及对应字符对照表:

Java类型字符
voidV
booleanZ (容易误写成B)
intI
longJ (容易误写成L)
doubleD
floatF
byteB
charC
shortS
int[ ][I (数组以"["开始)
StringLjava/lang/String; (引用类型格式为”L包名类名;”,要记得加";")
Object[][Ljava/lang/object;

「8. JNIEnv的介绍」

    JNIEnv概念 : JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境。通过JNIEnv可以调用到一系列JNI系统函数。

    JNIEnv线程相关性:每个线程中都有一个 JNIEnv 指针。JNIEnv只在其所在线程有效, 它不能在线程之间进行传递。

注意:在C++创建的子线程中获取JNIEnv,要通过调用JavaVM的AttachCurrentThread函数获得。在子线程退出时,要调用JavaVM的DetachCurrentThread函数来释放对应的资源,否则会出错。

「9. JNI编译之Cmake」

CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译, 在Android Studio 2.2 之后支持Cmake编译。

#将compress.c 编译成 libcompress.so 的共享库add_library(compress SHARED compress.c)
#指定 compress 工程需要用到 libjpeg 库和 log 库target_link_libraries(compress libjpeg ${log-lib})
find_library(libX  X11 /usr/lib)find_library(log-lib log)  #路径为空,应该是查找系统环境变量路径

示例工程Cmake截图如下:

三、示例工程代码

示例工程截图:

示例MainActivity内需要加载SO:

companion object {    // Used to load the 'native_kt_demo' library on application startup.    init {        System.loadLibrary("native_kt_demo")    }}

「1. 示例:获取JNI返回字符串(静态注册)」

Kotlin 代码

external fun stringFromJNI(): String

JNI层下代码

//extern "C" 避免编绎器按照C++的方式去编绎C函数extern "C"//JNIEXPORT :用来表示该函数是否可导出(即:方法的可见性//1、宏 JNIEXPORT 代表的就是右侧的表达式:__attribute__ ((visibility ("default")))//2、或者也可以说:JNIEXPORT 是右侧表达式的别名;//3、宏可表达的内容很多,如:一个具体的数值、一个规则、一段逻辑代码等JNIEXPORT//jstring 代表方法返回类型为Java中的 Stringjstring//用来表示函数的调用规范(如:__stdcall)JNICALLJava_com_wx_nativex_kt_demo_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {    std::string hello = "Hello from C++";    return env->NewStringUTF(hello.c_str());}

「2. 示例:调用JNI,JNI调用Java层无返回值方法(静态注册)」
Kotlin代码:

external fun callJNI()

JNI层代码:

extern "C" JNIEXPORT void JNICALLJava_com_wx_nativex_kt_demo_MainActivity_callJNI(JNIEnv *env, jobject thiz) {    LOGE("-----静态注册 , 无返回值方法 调用成功-----");    jclass js = env->GetObjectClass(thiz);    jmethodID jmethodId = env->GetMethodID(js, "toast", "(Ljava/lang/String;)V");    env->CallVoidMethod(thiz, jmethodId, env->NewStringUTF("静态注册 无返回值方法 调用成功"));}

「3. 示例:调用JNI,JNI调用Java层无返回值方法(带参数)(静态注册)」
Kotlin代码:

external fun stringFromJNIwithParameter(str: String): String

JNI层代码:

extern "C" JNIEXPORT jstring JNICALLJava_com_wx_nativex_kt_demo_MainActivity_stringFromJNIwithParameter(JNIEnv *env, jobject thiz, jstring str) {    const char *data = env->GetStringUTFChars(str, NULL);    LOGE("-----获取到Java 传来的数据:data %s-----", data);    env->ReleaseStringChars(str, reinterpret_cast<const jchar *>(data));    const char *src = "111---";    const int size = sizeof(data) + sizeof(src);    char datares[size] = "111---";    return env->NewStringUTF(strcat(datares, data));}

「4. 示例:调用JNI去调用java方法(静态注册)」
Kotlin代码:

external fun callNativeCallJavaMethod()

JNI层代码:

extern "C"JNIEXPORT void JNICALLJava_com_wx_nativex_kt_demo_MainActivity_callNativeCallJavaMethod(JNIEnv *env, jobject thiz) {    jclass js = env->GetObjectClass(thiz);    jmethodID jmethodId = env->GetMethodID(js, "toast", "(Ljava/lang/String;)V");    env->CallVoidMethod(thiz, jmethodId, env->NewStringUTF("jni 通过反射调用 java toast方法"));}

「5. 示例:调用JNI去调用java 变量值(静态注册)」

Kotlin代码:

external fun callNativeCallJavaField()

JNI层代码:

extern "C"JNIEXPORT void JNICALLJava_com_wx_nativex_kt_demo_MainActivity_callNativeCallJavaField(JNIEnv *env, jobject thiz) {    jclass js = env->GetObjectClass(thiz);    jfieldID jfieldId = env->GetFieldID(js, "androidData", "Ljava/lang/String;");    jstring newDataValue = env->NewStringUTF("四海一家");//    jclass js = env->GetObjectClass(thiz);    jmethodID jmethodId = env->GetMethodID(js, "toast", "(Ljava/lang/String;)V");    env->CallVoidMethod(thiz, jmethodId, newDataValue);//    env->SetObjectField(thiz, jfieldId, newDataValue);}

「6. 示例:去调用JNI去调用Java带callback方法,带参数(静态注册)」
Kotlin代码:

external fun callNativeWithCallBack(callBack: NativeCallBack)

JNI层代码:

extern "C"JNIEXPORT void JNICALLJava_com_wx_nativex_kt_demo_MainActivity_callNativeWithCallBack(JNIEnv *env, jobject thiz, jobject call_back) {    LOGE("-----静态注册 , callback 调用成功-----");    jclass js = env->GetObjectClass(call_back);    jmethodID jmethodId = env->GetMethodID(js, "nmd", "(Ljava/lang/String;)V");    env->CallVoidMethod(call_back, jmethodId, env->NewStringUTF("我是Jni Native层callBack回调回来的数据值"));}

「7. 示例:动态注册」Kotlin代码:

external fun dynamicRegisterCallBack(callBack: NativeCallBack)

JNI层代码:


void regist(JNIEnv *env, jobject thiz, jobject call_back) {
LOGD("--动态注册调用成功-->");
jclass js = env->GetObjectClass(call_back);
jmethodID jmethodId = env->GetMethodID(js, "nmd", "(Ljava/lang/String;)V");
env->CallVoidMethod(call_back, jmethodId, env->NewStringUTF("我是Jni Native层动态注册callBack回调回来的数据值"));
}
jint RegisterNatives(JNIEnv *env) {
jclass activityClass = env->FindClass("com/wx/nativex/kt/demo/MainActivity");
if (activityClass == NULL) {
return JNI_ERR;
}
JNINativeMethod methods_MainActivity[] = {
{
"dynamicRegisterCallBack",
"(Lcom/wx/nativex/kt/demo/NativeCallBack;)V",
(void *) regist
}
};
return env->
RegisterNatives(activityClass, methods_MainActivity,
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}
//JNI_OnLoad java
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGE("-----JNI_OnLoad 方法调用了-----");
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jint result = RegisterNatives(env);
// 函数注册
return JNI_VERSION_1_6;
}

总结

本文简单介绍了NDK编程中JNI的基础:并写了相关示例Demo代码

    JNI方法注册(静态注册,动态注册)

    JNI的基础数据类型

    JNI引用数据类型

    JNI函数签名信息

    JNIEnv的介绍

    JNI编译之Cmake

    示例:获取JNI返回字符串(静态注册)

    示例:调用JNI,JNI调用Java层无返回值方法(静态注册)

    示例:调用JNI,JNI调用Java层无返回值方法(带参数)(静态注册)

    示例:调用JNI去调用java方法(静态注册)

    示例:调用JNI去调用java 变量值(静态注册)

    示例:去调用JNI去调用Java带callback方法,带参数(静态注册)

    示例:动态注册

感谢阅读:

欢迎用你发财的小手:点点赞、收藏收藏,或者 关注关注

这里你会学到不一样的东西

项目地址

Gitee地址
Github地址

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Android NDK JNI C++ CMake Native开发
相关文章