通常来说我们使用JNI,Java层中对应的接口方法在本地代码中有对应的方法实现。
一、通过动态链接器根据条目的名称解析
通过动态链接器根据条目的名称解析条目。本地方法名称由以下组件连接而成:
- 前缀Java_
- 一个全限定类名
- 下划线(" _ ")分隔符
- 一个方法名
- 对于重载的本地方法,两个下划线(“__”)后面跟着参数签名
VM检查方法名与驻留在本地库中的方法是否匹配。VM首先查找短名称;即没有参数签名的名称。然后,它查找具有参数签名的长名称。只有在本地方法被另一个本地方法重载时,程序员才需要使用长名称。但是,如果本地方法与非本地方法具有相同的名称,则这不是问题。非本机方法(Java方法)不驻留在本地库中。
示例代码通过Android Studio编译即可。
1.1 方法没有重载版本
package com.example.ndk.ndkdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
Java层stringFromJNI()方法对应的实现实际上就是本地库中以下方法(Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI(…)):
#include <jni.h>
#include <string>
jstring NewStringUTF(JNIEnv *env, const char *str) {
return env->NewStringUTF(str);
}
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
本地方法使用Java_前缀,然后跟着以下划线(_)分割的Java全限定类名(com_example_ndk_ndkdemo_MainActivity),最后是和Java方法同名的stringFromJNI字符串。因为我们只有一个方法叫stringFromJNI,VM首先查找短名称(即没有参数签名的名称)就可以找到对应的本地方法实现。
1.2 方法含有重载版本
在没有短名称的前提下,VM会查找具有参数签名的长名称。只有在本地方法被另一个本地方法重载时,程序员才需要使用长名称。如果存在重载版本的函数,而不使用长名称则都会调用到短名称方法上,不能按照我们的预期执行对应的重载版本函数。下面再定义一个名称为stringFromJNI的JNI方法,我们则必须采用含有两个下划线(“__”)后面跟着参数签名的本地方法名才可以。
package com.example.ndk.ndkdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI("hello"));
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String stringFromJNI(String str);
}
定义了重载版本的JNI方法,本地方法必须采用长名称才行。
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__Ljava_lang_String_2(
JNIEnv *env,
jobject /* this */,
jstring jstr) {
std::string hello = "Hello from C++ with param";
return env->NewStringUTF(hello.c_str());
}
Java中stringFromJNI()方法对应的参数签名是空的,所以本地代码写作Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__,仅仅跟了两个下划线(__),而Java中stringFromJNI(String str)方法,对应的本地方法为Java_com_example_ndk_ndkdemo_MainActivity_stringFromJNI__Ljava_lang_String_2。我们知道String类对应的签名为Ljava/lang/String;,将/替换为_,而“;”需要转义,就被转义为了_2,这就是后面跟了个_2的原因。
二、调用RegisterNatives()来注册本地方法
程序员还可以调用JNI函数RegisterNatives()来注册与类关联的本地方法。RegisterNatives()函数对于静态链接的函数特别有用。
2.1 涉及API
2.1.1 RegisterNatives
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
使用clazz参数指定的类注册本地方法。methods参数指定JNINativeMethod结构的数组,其中包含本地方法的名称,签名和函数指针。 JNINativeMethod结构的名称和签名字段是指向修改后的UTF-8字符串的指针。nMethods参数指定数组中的本地方法数。JNINativeMethod结构定义如下:
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
函数指针在名义上必须有以下签名:
ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);
LINKAGE:
JNIEnv接口函数表中的索引215。
PARAMETERS:
env:JNI接口指针。
clazz:一个Java类对象。
methods:类中的本地方法。
nMethods:类中本地方法的数量。
RETURNS:
成功时返回“0”; 失败时返回负值。
THROWS:
NoSuchMethodError: 如果找不到指定的方法或该方法不是本地方法。
2.1.2 JNI_OnLoad
jint JNI_OnLoad(JavaVM *vm, void *reserved);
当加载本地库时,VM调用JNI_OnLoad(例如,通过System.loadLibrary)。JNI_OnLoad必须返回本地库所需的JNI版本。
为了使用任何新的JNI函数,本地库必须导出返回JNI_VERSION_1_2的JNI_OnLoad函数。如果本地库不导出JNI_OnLoad函数,VM假设该库只需要JNI版本JNI_VERSION_1_1。如果VM不识别JNI_OnLoad返回的版本号,则无法加载本地库。
LINKAGE:
从包含本地方法实现的本地库导出。
SINCE:
JDK/JRE 1.4
为了使用J2SE版本1.2中引入的JNI函数,除了JDK/JRE 1.1中提供的那些函数外,本地库必须导出一个JNI_OnLoad函数,该函数返回JNI_VERSION_1_2。
为了使用J2SE发行版1.4中引入的JNI函数,除了发行版1.2中提供的函数外,本地库还必须导出返回JNI_VERSION_1_4的JNI_OnLoad函数。
如果本地库不导出JNI_OnLoad函数,VM假设该库只需要JNI版本JNI_VERSION_1_1。如果VM不识别JNI_OnLoad返回的版本号,则无法加载本地库。
2.2 实例
继续修改上面的例子,Java层的代码没有保持不变,本地代码修改如下:
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring
JNICALL
stringFromJNI1(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring
JNICALL
stringFromJNI2(
JNIEnv *env,
jobject /* this */,
jstring jstr) {
std::string hello = "Hello from C++ with param";
return env->NewStringUTF(hello.c_str());
}
static JNINativeMethod methods[] = {
{"stringFromJNI", "()Ljava/lang/String;", reinterpret_cast<void *>(stringFromJNI1)},
{"stringFromJNI", "(Ljava/lang/String;)Ljava/lang/String;", reinterpret_cast<void *>(stringFromJNI2)}
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (JNI_OK != vm->GetEnv(reinterpret_cast<void **> (&env), JNI_VERSION_1_4)) {
return JNI_ERR;
}
jclass clazz = env->FindClass("com/example/ndk/ndkdemo/MainActivity");// 申明了JNI方法的Java类
//注册Native方法
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof((methods)[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
在JNI_OnLoad方法内我们把本地方法注册上了,主要调用了RegisterNatives方法,这样可以避免本地方法很长的方法名。reinterpret_cast<void*>(stringFromJNI1) 和reinterpret_cast<void*>(stringFromJNI2) 为本地实现的方法名,这里强制转换成了函数指针。