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自动补全功能而相对麻烦些。
以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;