Android JNI静态注册和动态注册方法详解

9 篇文章 0 订阅
本文详细介绍了AndroidJNI中的静态注册和动态注册方法,包括Java代码示例、C++代码实现、区别分析以及何时选择静态或动态注册。通过实际代码演示,帮助读者理解这两种注册方式的工作原理和应用场景。
摘要由CSDN通过智能技术生成

Android JNI静态注册和动态注册方法详解

一、前言

这里的JNI静态方式注册方法和JNI的static方法是没有关系的。

本文主要介绍jni.cpp代码中动态注册方法的讲解。

很多人对这块知识还是比较陌生的,什么是静态注册?什么是动态注册?有些人可能都不清楚。

JNI静态注册和动态注册是哪里的代码?

什么时候使用静态注册?什么时候使用动态注册?为什么呢?

其实一般情况用不到动态注册,但是复杂JNI项目或者系统源码中可能会用到动态注册。

特别是系统源码中基本都是用的动态注册的形式,注册JNI方法,所以是有必要进行学习的。

本文讲解以实际demo代码示例,学起来比较通俗易懂,不像网上的只说一些概念,和简单的片段代码。

想了解的可以继续看看下面内容。本文最后有解答所有问题的描述总结。

二、JNI静态注册和动态注册方法示例代码

1、定义native方法的Java代码

package com.demo.jnistatic;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    String TAG = "MainActivity.java";
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate" );
        TextView tv = findViewById(R.id.sample_text);
        String hello = stringFromJNI();
        Log.i(TAG, "onCreate hello = " + hello);
        int age = intFromJNI(18);
        Log.i(TAG, "onCreate age = " + age);
        tv.setText(hello);
    }

    public native String stringFromJNI();
    public native int intFromJNI(int age);
}

这里是在最简单的jni demo多加了一个native,添加了一些打印。

2、JNI静态注册代码:

#include <jni.h>
#include <string>

//默认的静态注册方法:
public native String stringFromJNI()
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jnistatic_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

//public native int intFromJNI(int age);
extern "C" JNIEXPORT jint JNICALL
Java_com_demo_jnistatic_MainActivity_intFromJNI(JNIEnv *env, jobject thiz, jint age) {
    return age + 10;
}

上面这段代码,看过一点jni的都是可以理解的。

所以重点是对比同样的逻辑在下面动态注册方法的代码是怎么写的。

3、JNI动态注册代码:

重点在 JNI_OnLoad 函数里面。

#include <jni.h>
#include <string>

#include <android/log.h> //添加头文件

#define LOG_TAG "native-lib.cpp" //定义TAG
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

//动态注册:
//public native String stringFromJNI()
extern "C" JNIEXPORT jstring JNICALL
cpp_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    LOGD("cpp_stringFromJNI  hello = %c", hello.c_str());
    return env->NewStringUTF(hello.c_str());
}

//public native int intFromJNI(int age);
extern "C"
JNIEXPORT jint JNICALL
cpp_intFromJNI(JNIEnv *env, jobject thiz, jint age) {
    LOGD("cpp_intFromJNI  age = %d", age);
    return age + 10;
}

//包名+类名字符串定义:
const char *mainactivity_class_name = "com/demo/jnistatic/MainActivity";

// 重点:定义类名和函数签名,如果有多个方法要动态注册,在数组里面定义即可
static const JNINativeMethod methods[] = {
        {"stringFromJNI", "()Ljava/lang/String;", (void *) cpp_stringFromJNI},
        {"intFromJNI",    "(I)I",                 (void *) cpp_intFromJNI},
};


// 定义注册方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGD("动态注册");
    JNIEnv *env;
    if ((vm)->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGD("动态注册GetEnv  fail");
        return JNI_ERR;
    }

    // 获取类引用
    jclass clazz = env->FindClass(mainactivity_class_name);

    // 注册native方法
    jint regist_result = env->RegisterNatives(clazz, methods,
                                              sizeof(methods) / sizeof(methods[0]));
    if (regist_result) { // 非零true 进if
        LOGD("动态注册 fail regist_result = %d", regist_result);
    } else {
        LOGI("动态注册 success result = %d", regist_result);
    }
    return JNI_VERSION_1_6;
}

上面是加了日志查看,jni动态注册代码复杂一些,写得不对的就会崩溃。

对比一下上面的静态注册的方法,可以发现:

动态注册也是要定义Java的全类名,方法名称,并且还要定义方法签名。

动态注册的好处:

1、只要定义一次全类名
2、可以自定义方法名
3、下次Java中再添加一个nativie方法,在定义一个自定义方法,并且在methods数值里面加一行绑定就可以了
4、cpp代码中的函数方法不用那么长

缺点就是:

1、需要写JNI_OnLoad函数绑定自定义方法名和nativie方法名
2、需要写nativie方法名的签名字符串

其实主要就是让cpp函数名称不那么长。

但是了解过这个简单的demo代码,复杂一些的代码也是可以慢慢看懂的,

系统中的写法可能不一样,有的是把全类名和methods数值都定义写在 JNI_OnLoad函数里面。

JNI动态注册代码就上面这些代码,多看看就理解了。

其实动态注册的代码学习起来是不难的,但是使用场景和一些相关概念很多人不清楚。

4、动态注册代码示例程序运行日志:

2024-03-06 16:44:33.603 D/native-lib.cpp: 动态注册
2024-03-06 16:44:33.603 I/native-lib.cpp: 动态注册 success result = 0
2024-03-06 16:44:33.665 I/MainActivity.java: onCreate
2024-03-06 16:44:33.665 D/native-lib.cpp: cpp_stringFromJNI  hello = Q
2024-03-06 16:44:33.665 I/MainActivity.java: onCreate hello = Hello from C++
2024-03-06 16:44:33.665 D/native-lib.cpp: cpp_intFromJNI  age = 18
2024-03-06 16:44:33.665 I/MainActivity.java: onCreate age = 28

Java的static模块方法中没有添加打印,如果有添加,是会先执行里面的。

三、其他

1、JNI静态注册和动态注册的官方说法

 在JNI中,静态注册和动态注册是两种不同的注册方式,适用于不同的场景。

 静态注册通常在编译时完成,即在Java代码中通过`System.loadLibrary()`方法加载JNI库时,

会根据库中的函数名查找对应的JNI函数。

如果找到了,会在native函数与JNI函数之间建立关联关系,保存JNI函数的函数指针。

这种方式在编译时就需要确定所有的native函数和对应的JNI函数,

因此对于一些固定的、已知的native函数调用场景比较适用。 


动态注册则是在运行时进行注册,通过提供一个函数映射表,注册给JVM虚拟机。

这种方式不需要在编译时确定所有的native函数和对应的JNI函数,而是在需要调用native函数时,
通过JNI库中的`RegisterNatives()`函数将映射表注册给JVM。

这种方式适用于一些动态的、不确定的native函数调用场景,例如需要在运行时动态加载库或调用库中的函数。
因此,根据上下文中的内容,用户可以结合实际情况选择使用静态注册还是动态注册。


总结:一般来说,对于一些已知的、固定的native函数调用场景,可以使用静态注册;

而对于一些动态的、不确定的native函数调用场景,可以使用动态注册。

具体选择哪种方式还需要根据实际情况进行评估和考虑。 

很多人看了上面的介绍也是比较懵逼的。

再往后面看看我对于这块的理解,就知道什么场景用动态注册和静态注册。

下面都是我自己对这块知识的总结和理解。

2、JNI静态注册和动态注册方法

(1) 静态注册和动态注册是哪里的代码?

静态注册和动态注册都是jni.cpp里面的代码说法;

无论Java怎么写nativie方法,jni.cpp都是可以写成静态注册或者动态注册。

(2)什么是静态注册和动态注册?

Android Studio 创建的jni项目,默认的方法就是静态注册的。

静态注册的代码:

Java定义的nativie 方法在cpp代码中对应的方法是: Java_包名(中间的点.替换成下划线_)_类名_方法名

动态注册的代码:

Java定义的nativie 方法在cpp代码中对应的方法可以写成自定义的。写法上主要就是这个区别。

其实动态注册,也是要声明类名全称和方法名和方法签名类型,另外声明注册而已。

(3)JNI静态、动态注册的区别?

静态注册和动态注册的主要区别在于效率。

静态注册每次使用native方法时,都需要去寻找,而动态注册由于有张表的存在,查找效率更高。

因此,在NDK开发中,静态注册方法通常用于性能要求不高的场景,

而动态注册方法则更适用于性能要求较高的场景。

具体代码写法的区别就是:

静态注册cpp中的方法名是固定写法,用于映射 Java native方法;

动态注册cpp中的方法名是可以自定义的,但是要多写一个 JNI_OnLoad方法,

用于映射自定义方法和Java方法的关系。

不管是静态注册还是动态注册,只要Java中修改了native方法名称,jni.cpp代码中都是要修改对应的名称的。

如果增加了native方法,那么静态注册直接加一个号函数就行;

动态注册就要加一个自定义函数以及在method数组里面添加一行绑定的信息。

3、JNI静态/普通方法、静态注册/动态注册方法、静态/动态库so

三者有啥关系吗?很多人会混淆了。这里简单说一下。

其实三者没啥关系,但是后两者是有点关系。

动态so库一定要动态方法注册吗?

(1)JNI静态/普通方法

JNI静态/普通方法其实是Java的代码,和Java里面的普通方法和静态方法一样,主要区别就是需要的调用者不同。

比如下面这段NI静态/普通方法 代码示例:

//1、native 的静态方法和普通方法
    public native void normalNativeMethod(); // 声明普通native方法
    public static native void staticNativeMethod(); // 声明静态native方法


//2、Java 的普通方法和静态方法,C++调用过来的处理区别?
    // 普通方法
    public void normalMethod() {
        System.out.println("这是一个普通方法");
    }
    // 静态方法
    public static void staticMethod() {
        System.out.println("这是一个静态方法");
    }

具体区别和代码中的处理可以看看 之前写的 JNI静态方法和普通方法 文章:

https://blog.csdn.net/wenzhi20102321/article/details/136511359

(2)静态注册/动态注册方法

JNI静态/动态注册和Java 的普通/静态方法是没啥关系的。

不管Java定义的nativie 普通/静态方法都是可以在cpp代码中静态或者动态注册。

JNI静态注册方法的代码,就是Android Studio默认创建的Jni项目中cpp的代码;

关于JNI动态注册方法,关键就是 JNI_OnLoad 函数,看到这个函数大概就是动态注册方法的,

其实看看上面的具体代码,就会有大概认识,毕竟很多人可能还没看到过JNI动态注册的代码。

(3)静态/动态库so

静态/动态库so和静态/动态注册方法、普通方法/动态方法没啥关系。

静态so库是嵌入到应用中的,修改某个so的底层逻辑要编译整个apk文件;

动态so库是可以放到系统固定目录(一般是/system/lib64/)加载的,

如果需要修改某个so的底层逻辑,只要把对应的so替换到系统目录,重启系统就可以了。

静态/动态库so的主要区别在连接方式、apk文件大小、移植性、内存占用等方面。

具体选择哪种方式取决于应用的具体需求和性能要求。

Android动态so(共享库)可以动态注册,也可以进行静态注册方法

但是一般动态so库项目,进行动态方式注册比较多;

因为一般提示so库的项目,都是很多native方法的,动态注册效率会更高,

并且cpp的实现函数也没有那么长,代码简洁,可以和Java的方法保持一致。

(4)三者之间没有必然要求

JNI 静态方法和静态注册和静态so之间是没有必然要求的。

JNI 静态方法可以动态注册也可以静态注册,可以使用动态so或者普通so加载;

JNI 普通native方法也是如此,没有硬性要求,想怎么写都可以。

三者的关联关系差不多是上中下三层的关系:

Java上层:JNI 静态方法或者动态方法,定义nativie的那个Java类

jni.cpp文件:JNI静态注册或者动态注册,中间连接Java的那个jni cpp

系统加载的整个cpp代码形式:静态so库或者动态so库,jni cpp和相关的所有cpp代码

(5)什么时候使用静态、动态注册?

上面可能有说到静态、动态注册的使用场景,但是并没有说得很明白。

从上面示例代码看,动态注册比静态注册要写很多代码。

所以

使用静态注册的场景一般是:

	native方法只有几个的情况,并且nativie的方法后续不会怎么修改和增加;
​	简单项目或者示例程序代码一般使用静态注册即可。

使用动态注册的场景一般是:

	native方法非常多,并且后续会进行修改和增加。
​	大型项目一般使用动态注册。

JNI静态、动态注册代码对应用内存和效率那些基本没啥太大变化,不像动态加载so对内存影响是比较大的。

最后说一句,JNI动态和静态注册学习,其实核心就是看懂动态注册的代码就行。

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峥嵘life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值