ndk之C调用java方法以及动态注册
作者:互联网
一、静态注册和动态注册
ndk开发需要在java层和native层相互调用代码,如何确定native方法与jni函数之间的映射关系呢?这就涉及到jni函数的注册,注册方式有两种:静态注册和动态注册。
静态注册采用基于约定的命名规则(Java_开头,后接类的全限定名加下划线,方法名这三个组成部分组成,如下代码所示),可以通过javah或IDE自动生成native方法对应的函数声明。
优点是简单;缺点是不灵活,修改java类名或jni方法名时,需要同步修改对应的native函数命名。
extern "C"
JNIEXPORT jint JNICALL
Java_com_shan_dynamicndk_MainActivity_jniCallAdd(JNIEnv *env, jobject thiz, jint num1, jint num2) {
// TODO: implement jniCallAdd()
}
动态注册通过JNINativeMethod函数和JNI_OnLoad函数的编辑可以在native代码中自由定义jni函数与native函数的映射。优点是灵活,缺点可能会由于无法使用javah和IDE自动补全功能而相对麻烦些。
二、C代码调用java代码(静态注册)
以native层调用java层方法获取uuid的方法为例
java层代码MainActivity.java如下
//MainActivity.java
package com.shan.ndkapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.shan.ndkapp.databinding.ActivityMainBinding;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ndk_test_java";
// Used to load the 'ndkapp' library on application startup.
static {
System.loadLibrary("ndkapp");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(getUUidFromJava());
}
public String getUuid() {
String uuid = UUID.randomUUID().toString();
Log.d(TAG, "getUuid:"+uuid);
return uuid;
}
public native String getUUidFromJava();
}
native层代码native-lib.cpp如下:
//native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "ndk_test_c"
#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_shan_ndkapp_MainActivity_getUUidFromJava(JNIEnv *env, jobject thiz) {
// TODO: implement getUUidFromJava()
jclass j_class = env->GetObjectClass(thiz);//根据jobject获取jclass
jmethodID j_methodId = env->GetMethodID(j_class,"getUuid","()Ljava/lang/String;");//最后一个参数方法签名可以通过javap -s className得到
jobject j_uuid = env->CallObjectMethod(thiz, j_methodId);
const char* uuid = env->GetStringUTFChars(static_cast<jstring>(j_uuid),NULL); //jstring转化为char*才可以在C语言打印
LOGD("Java_com_shan_ndkapp_MainActivity_getUUidFromJava,uuid=%s",uuid);
env->ReleaseStringUTFChars(static_cast<jstring>(j_uuid),uuid); //回收字符串
return static_cast<jstring>(j_uuid);
}
上面native代码的第13行获取方法签名时,如果忘记了签名规则,可以在当前工程目录下面找到
build/intermediates/javac/debug/classes/com/shan/ndkapp/MainActivity.class,在该目录下打开命令行,通过javap -s MainActivity.class获取该类所有签名信息。
三、动态注册
以java层调用native层代码获取字符串以及native层代码调用java层add函数为例,这两个函数都采用动态注册实现。
java代码MainActivity.java如下
//MainActivity.java
package com.shan.dynamicndk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.shan.dynamicndk.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
// Used to load the 'dynamicndk' library on application startup.
static {
System.loadLibrary("dynamicndk");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI()+":"+jniCallAdd(2,3));
}
public int add(int num1,int num2) {
return num1+num2;
}
/**
* A native method that is implemented by the 'dynamicndk' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int jniCallAdd(int num1,int num2);
}
native层代码如下
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
c_getString(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jint JNICALL
c_callAdd(
JNIEnv* env,
jobject thiz,jint num1, jint num2) {
jclass j_class = env->GetObjectClass(thiz);//根据jobject获取jclass
jmethodID j_methodId = env->GetMethodID(j_class,"add","(II)I");//最后一个参数方法签名可以通过javap -s className得到
jint j_add = env->CallIntMethod(thiz, j_methodId,num1,num2); //调用java层的add方法
return j_add;
}
static JNINativeMethod methods[] = {
{ "stringFromJNI", "()Ljava/lang/String;", (void*)c_getString},
{ "jniCallAdd", "(II)I", (void*)c_callAdd},
};
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
// 获取JNI env变量
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
// 失败返回-1
return JNI_ERR;
}
// 获取native方法所在类
jclass clazz = env->FindClass("com/shan/dynamicndk/MainActivity"); //包名类名必须完全一致,否则匹配失败
if (clazz == NULL) {
return JNI_ERR;
}
// 动态注册native方法
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
// 返回成功
return JNI_VERSION_1_6;
}
动态注册分为三步:1、定义JNINativeMethod结构体确定映射关系 2、调用JNI_OnLoad函数去加载 3、具体实现native函数。
JNINativeMethod结构体中记录了java层函数名称name、函数签名signature以及native层代码的函数指针fnPtr,JNINativeMethod结构体定义在jni.h中,有兴趣的话可以查看一下jni.h文件。
//jni.h
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
标签:ndk,调用,java,uuid,MainActivity,env,JNI,native 来源: https://blog.csdn.net/u013795543/article/details/122754053