Android系统加载JNI Lib的方式
要想在Java中调用C的函数,必然要有一定的规则去映射二者的函数名,也就是加载JNI库的方式,下面介绍这两种方式。
JNI_OnLoad
当Android的VM(Virtual Machine)执行到C组件(即*so)里的System.loadLibrary()函数时,
首先会去执行C组件里的JNI_OnLoad()函数。这种方法有两个优点,1. 可以通知VM此时native使用哪个版本的JNI,默认是最低版本;2. 使得native层在被加载的时候做一些初始化工作。
JNI_OnLoad()中声明的JNI函数在进程空间中的起始地址被保存在ClassObject->directMethods中,也就是说在加载so的时候就记录下来了JNI函数的地址,等到使用的时候直接从特定地址执行相关函数就可以了。
在JNI_OnLoad
函数中最重要的事就是调用RegisterNatives
函数完成动态库中JNI函数的注册,jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
这个函数的第二个参数JNINativeMethod
是如下的结构体:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
name
为java中定义的native函数的函数名,signature
为函数的签名,fnPtr
为对应的native C函数的函数指针。
函数签名查看方法,比如有Example类,先编译javac Example.java
,然后使用命令javap -s -p Example.class
可以查看函数和成员变量的签名。
示例:JNI_OnLoad
假设Java中有 public class JniManager { public native String nGetStudentInfo(); }
方法,native C中定义的对应的函数为jstring jniGetStudentInfo(JNIEnv *env, jobject object)
,具体的JNI_OnLoad
代码如下所示:
“`cpp
static const JNINativeMethod gMethods[] = {
{“nGetStudentInfo”, “()Ljava/lang/String”, (void *) jniGetStudentInfo},
{“nHello”, “()V”, (void*) jniHello}
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return result;
}
jclass clazz = env->FindClass("com/lmshao/jniexample/JniManager");
if (clazz == NULL) {
return result;
}
jint count = sizeof(gMethods) / sizeof(gMethods[0]);
if (env->RegisterNatives(clazz, gMethods, count) != JNI_OK) {
return result;
}
result = JNI_VERSION_1_6;
return result;
}
“`
JNIEXPORT
和JNICALL
为jni.h中的宏定义,#define JNIEXPORT __attribute__ ((visibility ("default")))
,JNIEXPORT
用来导出动态库的函数符号,JNICALL
暂时定义为空。
dvmResolveNativeMethod延迟解析机制
如果JNI Lib中没有JNI_OnLoad函数,即在执行System.loadLibrary时,
无法把此JNI Lib实现的函数在进程中的地址增加到ClassObject->directMethods。则直到需要调用的时候才会解析这些javah风格(包名+类名+方法名)的函数。这种方法是Android Studio默认的native工程使用的方法。
示例:
比如Java里面MainActivity类中有个方法声明为public native String stringFromJNI();
,则Native中对应的函数名字应该是jstring Java_com_example_demo_MainActivity_stringFromJNI(){}
。
虽然这种方法是Android Studio默认Native工程使用的方法,但是因为种种原因在实际Native开发中很少使用。
示例
Android Studio 3新建一个Native工程,默认有如下示例函数。
MainActivity.java
public class MainActivity extends AppCompatActivity {
// 加载动态库
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());
}
// Native方法
public native String stringFromJNI();
}
native-lib.cpp
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv *env, jobject) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
参考:
- https://blog.csdn.net/fireroll/article/details/50102009
签名
JNI签名是使用一些缩写符号来代表参数类型,这些符号由Java语言规定的。
数据类型的签名
Java基本数据类型的签名如下表所示:
签名 | Java类型 | JNI类型 |
---|---|---|
Z | boolean | jboolean |
C | char | jcahr |
B | byte | jbyte |
S | short | jshort |
I | int | jint |
J | long | jlong |
F | float | jfloat |
D | double | jdouble |
[B | byte[ ] | jbyteArray |
[I | int[ ] | jintArray |
… | … | … |
Java复杂类型签名格式是:“L”+“全限定类名”+“;” 。例如String
类型签名为Ljava/lang/String;
,对应的JNI类型为jstring
。其余的复杂数据类型对应的JNI类型都是jobject
,jstring本质上也是jobejct类型,因为使用频率高,所以又单独定义方便使用。
函数参数签名
函数的参数签名由参数和返回值组成,参数用一对小括号包起来,即使参数为空也要使用空括号,括号后面是返回值类型,如果没有返回值则用字母V表示。
如:(I)V
表示参数为int,无返回值。()I
表示参数为空,返回值为int。([IZ)I
表示参数为int[]和boolean,返回值为int。
参考:
刘超. 深入理解Android 5.0系统[M]. 人民邮电出版社, 2015.