Android NDK开发系列教程6:JNI函数注册(JNI_OnLoad)

简介: 在使用native方法前都会先加载该native方法的so文件,通常在一个类的静态代码块中进行加载,当然也可以在构造函数,或者调用前加载。jvm在加载so时都会先调用so中的JNI_OnLoad函数,如果你没有重写该方法,那么系统会给你自动生成一个。

在使用native方法前都会先加载该native方法的so文件,通常在一个类的静态代码块中进行加载,当然也可以在构造函数,或者调用前加载。jvm在加载so时都会先调用so中的JNI_OnLoad函数,如果你没有重写该方法,那么系统会给你自动生成一个。JNI_OnLoad方法的调用顺序可以参考我的另一篇博文:JNI_OnLoad调用时机,下面我们可以在该方法中对自己的函数进行注册。这就很爽了,jni默认的那个方法命名又臭又长,改的时候不注意还可能该错。现在我们可以定义自己的函数名称,只需要在JNI_OnLoad中注册下对应的映射。在Google官网也有介绍:https://developer.android.com/training/articles/perf-jni.html

1. JNI_OnLoad简介

在编写JNI方法时有两种方法:一种是标准的通过javah生成头文件,然后自己实现对应的cpp文件,这种办法也是官方推荐的。还有一种方法是在JNI_OnLoad函数中进行函数映射,将java里面的方法映射到自己实现的方法。
​
当Android的DVM(Virtual Machine)执行到C组件里的System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。它的用途有二:
1. 告诉VM此C组件使用那一个JNI版本。
如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。
由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,
例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
2. 由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(), 所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 。
3. 在so被成功卸载时,会回调另一个JNI方法:JNI_UnOnLoad。这两个方法声明如下:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);

其中第一个参数vm表示DVM虚拟机,该vm在应用进程中仅有一个,可以保存在native的静态变量中,供其他函数或其他线程使用。其返回值表示当前需要native library需要的版本。

2. 举个栗子

首先在Java中写好native方法:

    //JNI_OnLoads使用实例
    public native void jniOnLoadTest();
    public native String jniOnload1(Person person);

然后编写对应的native方法

//空方法可以不用传任何字段
//也可以传这两个参数:void onLoadTest(JNIEnv*env,jobject obj);两个参数含义和用javah生成的一致。
void onLoadTest() {
    LOGE("调到我啦");
}
//如果有参数,那么需要加上前面两个参数,不然会导致参数不对应。参数含义和javah生成的头文件中参数含义一致。
jstring onloadTest1(JNIEnv *env, jobject instance, jobject obj) {
    jclass pCls = env->GetObjectClass(obj);
    jfieldID nameFid = env->GetFieldID(pCls, "name", "Ljava/lang/String;");
    jstring name = (jstring) env->GetObjectField(obj, nameFid);
    char *cname = jstringToChar(env, name);
    char *tmp = new char[100];
    sprintf(tmp, "我来自Native,我叫:%s", cname);
    jstring result = charTojstring(env, tmp);
    return result;
}

然后在JNI_OnLoad中注册改函数映射

//注册函数映射
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv *pEnv = NULL;
    //获取环境
    jint ret = vm->GetEnv((void**) &pEnv, JNI_VERSION_1_6);
    if (ret != JNI_OK) {
        LOGE("jni_replace JVM ERROR:GetEnv");
        return -1;
    }
    //在{}里面进行方法映射编写,第一个是java端方法名,第二个是方法签名,第三个是c语言形式签名(括号内表示方法返回值)
    JNINativeMethod g_Methods[] = {{"jniOnLoadTest", "()V", (void*) onLoadTest},
                                   {"jniOnload1", "(Lzqc/com/example/Person;)Ljava/lang/String;", (jstring*)onloadTest1}
    };
    jclass cls = pEnv->FindClass("zqc/com/example/NativeTest");
    if (cls == NULL) {
        LOGE("FindClass Error");
        return -1;
    }
    //动态注册本地方法
    ret = pEnv->RegisterNatives(cls, g_Methods,sizeof(g_Methods) / sizeof(g_Methods[0]));
    if (ret != JNI_OK) {
        LOGE("Register Error");
        return -1;
    }
    //返回java版本
    return JNI_VERSION_1_6;
}

其中JNINativeMethod的结构如下:

typedef struct {  
    const char* name;     // java层对应的方法名称  
    const char* signature;// 该方法的返回值类型和参数类型  
    void*       fnPtr;    // native中对应的函数指针  
} JNINativeMethod;  
    //注册本地方法,第一个是方法对应的类,第二个是方法映射,第三个是映射方法的个数
    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
        jint nMethods)
    { return functions->RegisterNatives(this, clazz, methods, nMethods); }

通过以上方法就可以实现方法映射,而不用遵循原有的命名规则。

3. 总结

JNI_OnLoad是加载so时最先调用的方法,而且该方法会把JavaVM* vm指针传过来,这样在native就可以保存该指针,该指针在整个应用进程中仅有一个,可以跨线程使用。我们通过在该方法中注册函数映射,当然也可以在该方法中做其他操作。比如我们可以在该方法中进行版本校验,也可以校验当前调用该so的应用是否合乎要求。

目录
相关文章
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
2013 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
12月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1280 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1346 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
594 0
|
存储 Java 开发工具
Android开发的技术与开发流程
Android开发的技术与开发流程
787 1
|
编解码 Android开发 iOS开发
安卓与iOS开发:平台差异下的技术创新之路
在数字时代的浪潮中,移动应用开发如同两股潮流——安卓与iOS,各自携带着独特的技术生态和文化基因。本文将深入探讨这两大平台的开发环境、编程语言和工具的差异,以及它们如何塑造了不同的用户体验和技术趋势。通过比较分析,我们旨在揭示跨平台开发的可能性和挑战,同时探索未来技术创新的方向。让我们一起跟随代码的足迹,穿越安卓的开放草原和iOS的精密园林,发现那些隐藏在平台差异之下的创新机遇。
320 28
|
安全 Android开发 Swift
安卓与iOS开发:平台差异与技术选择
【8月更文挑战第26天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各占一方。本文旨在探索这两个系统在开发过程中的不同之处,并分析开发者如何根据项目需求选择合适的技术栈。通过深入浅出的对比,我们将揭示各自平台的优势与挑战,帮助开发者做出更明智的决策。
424 5
|
移动开发 搜索推荐 Android开发
安卓与iOS开发:一场跨平台的技术角逐
在移动开发的广阔舞台上,两大主角——安卓和iOS,持续上演着激烈的技术角逐。本文将深入浅出地探讨这两个平台的开发环境、工具和未来趋势,旨在为开发者揭示跨平台开发的秘密,同时激发读者对技术进步的思考和对未来的期待。
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:技术选择的影响
【8月更文挑战第17天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文通过比较这两个平台的编程语言、开发工具及市场策略,揭示了技术选择对开发者和产品成功的重要性。我们将从开发者的视角出发,深入探讨不同平台的技术特性及其对项目实施的具体影响,旨在为即将步入移动开发领域的新手提供一个清晰的指南,同时给予资深开发者新的思考角度。
319 3
|
移动开发 Java Android开发
安卓与iOS开发:选择的艺术与技术的较量
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据着半壁江山。本文将探讨这两个平台在开发过程中的异同,以及它们如何影响开发者的选择。我们将从技术栈、市场份额、用户群体等方面进行分析,并结合案例来说明不同平台的优势与挑战。无论你是初涉移动开发领域的新手,还是经验丰富的老手,这篇文章都将为你提供有价值的见解。让我们一起探索这片充满机遇与挑战的土地吧!

热门文章

最新文章