文章目录
1.静态注册
通过将 Java 方法和对应的 C++ 函数的名称严格按照 JNI 的命名规则进行匹配来实现的。这种方法比较简单直观,但函数名称必须按照特定的规则命名。
- 在Activity中声明JNI函数接口
- 在cpp 文件中生成对应的c++的函数实现
示例:
在MainActivity中声明 public native String stringFromJNI();
在native-lib.cpp中声明具体的实现,名字有严格的要求,格式为Java+包名+函数名
实现如下:
extern "C" JNIEXPORT jstring JNICALL
Java_com_marxist_firstjni_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Java 调用 C++ 静态注册方法";
return env->NewStringUTF(hello.c_str());
}
2.动态注册
动态注册方法允许在运行时注册 C++ 函数到 Java 方法。这种方法灵活性更高,不需要严格遵循命名规则。
主要步骤:
- 在Activity中声明JNI函数接口
- 在cpp 文件中生成对应的c++的函数实现(名字随意)
- 生成对应的动态注册表
- 提供运行时注册函数
示例如下:
声明接口
在MainActivity中声明 getStringFromNative
在native-lib.cpp中声明具体的实现,函数名字不做具体的严格要求
jstring getStringFromNative(JNIEnv* env, jobject /* this */) {
std::string hello = "Java 调用 C++ 动态注册方法";
return env->NewStringUTF(hello.c_str());
}
生成对应动态的注册表
static JNINativeMethod methods[] = {
{"getStringFromNative", "()Ljava/lang/String;", (void *)getStringFromNative}
};
JNINativeMethod
是一个结构体,用于表示 Java 本地方法和其对应的 C/C++ 实现。它的定义如下:
typedef struct {
const char* name; //Java 方法的名称。
const char* signature; //Java 方法的签名,用于描述方法的参数和返回类型。
void* fnPtr; //指向实现该方法的本地函数的指针。
} JNINativeMethod;
methods[]
为数组,这个数组用于动态注册 JNI 方法。对应的元素为
{"getStringFromNative", "()Ljava/lang/String;", (void *)getStringFromNative}
这个元素具体表示一个 Java 方法与其对应的本地实现的关系:
name
:"getStringFromNative"
这是 Java 方法的名称,必须与 Java 类中声明的本地方法名称一致。signature
:"()Ljava/lang/String;"
这是方法的签名,描述了方法的参数和返回类型:()
表示这个方法没有参数。Ljava/lang/String;
表示这个方法返回一个java.lang.String
类型的对象。
fnPtr
:(void *)getStringFromNative
这是一个指向本地函数的指针,即 C/C++ 实现的方法getStringFromNative
的地址。(void *)
是将函数指针转换为void*
类型,符合JNINativeMethod
结构体的定义。
运行时注册函数
// 注册本地方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
// 寻找JEnv
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
//寻找 加载native的类对象 , 从包名开始 用/分割,Android Studio有动态提示
jclass clazz = env->FindClass("com/marxist/firstjni/MainActivity");
if (clazz == nullptr) {
return JNI_ERR;
}
//根据动态注册表的参数进行动态注册
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
当JVM运行的时候,将会注册 C++ 函数到 Java 方法,然后被调用。
3.完整示例代码
MainActivity:
package com.marxist.firstjni;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.marxist.firstjni.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
// 加载C++库
static {
System.loadLibrary("firstjni");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TextView tv = binding.sampleText;
tv.setText(stringFromJNI()+"\n"+getStringFromNative());
}
//声明一个JNI函数 接口
public native String stringFromJNI();
public native String getStringFromNative();
}
native-lib.cpp:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_marxist_firstjni_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Java 调用 C++ 静态注册方法";
return env->NewStringUTF(hello.c_str());
}
jstring getStringFromNative(JNIEnv* env, jobject /* this */) {
std::string hello = "Java 调用 C++ 动态注册方法";
return env->NewStringUTF(hello.c_str());
}
// 动态注册表
static JNINativeMethod methods[] = {
{"getStringFromNative", "()Ljava/lang/String;", (void *)getStringFromNative}
};
//JVM启动的时候调用的函数
jint JNI_OnLoad(JavaVM *vm,void* reserved){
JNIEnv *env = NULL;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jclass clazz = env->FindClass("com/marxist/firstjni/MainActivity"); //调用native 方法的Java 类
if (clazz == nullptr) {
return JNI_ERR;
}
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
静态注册:简单、直接,通过严格的命名规则将 Java 方法与 C++ 函数对应起来。
动态注册:灵活性更高,允许在运行时注册方法,不受命名规则限制。
效果展示:
4.附录:JNI中的签名(signature)
JNI(Java Native Interface)的签名(signature)用于描述 Java 方法的参数和返回类型。签名字符串是方法签名的编码表示,用于在本地代码和 Java 虚拟机之间传递方法的类型信息。以下是 JNI 签名的详细解释。
基本类型签名
每种 Java 基本类型都有一个对应的签名字符:
Z
- booleanB
- byteC
- charS
- shortI
- intJ
- longF
- floatD
- doubleV
- void
对象类型签名
对象类型的签名由 L
开头,后跟类的全限定名,并以分号 ;
结尾。例如:
Ljava/lang/String;
表示java.lang.String
类。Ljava/util/List;
表示java.util.List
类。
数组类型签名
数组类型的签名由一个或多个左方括号 [
开头,后跟元素类型的签名。例如:
[I
表示int[]
数组。[Ljava/lang/String;
表示java.lang.String[]
数组。[[D
表示double[][]
数组。
方法签名
方法签名由参数类型签名列表和返回类型签名组成,参数类型签名列表用圆括号 ()
括起来,返回类型签名放在括号之后。例如:
()V
表示一个没有参数且没有返回值的方法。(I)V
表示一个接受一个int
参数且没有返回值的方法。(Ljava/lang/String;)I
表示一个接受一个java.lang.String
参数并返回一个int
的方法。([I)Ljava/lang/String;
表示一个接受int[]
参数并返回java.lang.String
的方法。
示例解析
以下是一些示例签名的解析:
示例 1
Java 方法:
public void myMethod();
JNI 签名:
()V
解释:
()
表示方法没有参数。V
表示方法返回void
类型。
示例 2
Java 方法:
public int add(int a, int b);
JNI 签名:
(II)I
解释:
(II)
表示方法有两个int
参数。I
表示方法返回int
类型。
示例 3
Java 方法:
public String getMessage(String prefix);
JNI 签名:
(Ljava/lang/String;)Ljava/lang/String;
解释:
(Ljava/lang/String;)
表示方法有一个java.lang.String
参数。Ljava/lang/String;
表示方法返回一个java.lang.String
类型。
示例 4
Java 方法:
public int[] processArray(int[] input);
JNI 签名:
([I)[I
解释:
([I)
表示方法有一个int[]
参数。[I
表示方法返回一个int[]
类型。