在Android 中 动态注册 JNI 函数
写在最前面,本人没多写过文章,有什么建议或意见请告诉我
↑↑↑↑↑↑↑↑ 本文的表就是从这抄的 ↑↑↑↑↑↑↑↑
Android的JNI_OnLoad简介与应用
1. 普通的使用方法
在java类中 :
package com.fzy.test;
public class JNITest {
//定义一个方法,该方法在C中实现
public native void testMethod();
}
在Native中:
JNIEXPORT void JNICALL Java_com_fzy_test_JNITest_testMethod(JNIEnv *, jobject) {
*******
}
2. 动态注册
2.2 示例代码 (只列出重点部分)
java层 :
package com.fzy.learn.nativelib;
public class JNIClass {
public static native boolean testMethod(String info);
public static native boolean testMethod2(String data);
public static native String testMethod3(String data);
}
native 层:
/*----------------- 需要注册的函数 ------------------*/
// 对应 testMethod
jboolean needRegistMethod(JNIEnv *env, jobject /* this */,jstring info) {
if (NULL == info) {
return JNI_FALSE;
}
return JNI_TRUE;
}
// 对应 testMethod2
jboolean needRegistMethod2(JNIEnv *env,jobject /* this */, jstring data) {
if (nullptr == data ) {
return JNI_FALSE;
}
return JNI_TRUE;
}
// 对应 testMethod3
jstring needRegistMethod3(JNIEnv *env,jobject /* this */, jstring data) {
if (NULL == data) {
std::string hello = "参数为空";
return env->NewStringUTF(hello.c_str());
}
return data;
}
JNI 注册方法
// java层的类名
static const char *classPath = "com/fzy/learn/nativelib/JNIClass";
// 需要注册的方法的信息,在此处我们可以对 函数名 和 参数 进行加密,在注册之前进行解密
static JNINativeMethod gs_needRegestMethods[] =
{
{"testMethod", "(Ljava/lang/String;)Z", (void *) needRegistMethod},
{"testMethod2", "(Ljava/lang/String;)Z", (void *) needRegistMethod2},
{"testMethod3", "(Ljava/lang/String;)Ljava/lang/String;", (void *) needRegistMethod3},
};
static int RegisterNatives(JNIEnv* env)
{
int result = -1;
// 获取到需要注册的函数的数量
int max = sizeof(gs_needRegestMethods)/sizeof(gs_needRegestMethods[0x00]);
// 找到对应的类(需要判断是否存在)
jclass cls = env->FindClass(classPath);
// 对函数列表进行注册 但不建议这样做 因为无法精确到对每个函数进行控制
result = env->RegisterNatives(cls, localNathiveMethod , max);
// 注册结果
return result;
}
// 实现 jni.h 中的 JNI_OnLoad 方法,加载动态库时自行调用(见下文详解)
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jint result = -1;
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
goto error;
}
result = RegisterNatives(env);
if (JNI_OK != result) {
goto error;
}
error:
return result;
}
2.3 主要方法
2.3.1 注册方法
在 JNIEnv 中 提供有 RegisterNatives 方法 定义如下
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
其中 JNINativeMethod 的定义为
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
参数详解:
-
clazz —> 代表java层包含native方法的类名
-
JNINativeMethod 注册方法的信息
- name —> 代表java层中native方法的名称 例如上文中的 testMethod
- signature —> 代表java层native方法参数的缩写 (JLjava/lang/String;)Z 详细对应符号见 表 1
- fnPtr —> native 层中与java层对应的方法的函数 needRegistMethod
-
nMethods —> 需要注册的函数的数量,写少了就等着报错吧
Java 类型 | 符号 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
objects对象 | Lfully-qualified-class-name;L类名 |
Arrays数组 | [array-type [数组类型 |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
2.3.2 入口方法:
在jni.h文件中,定义了如下的方法
/*
* Prototypes for functions exported by loadable shared libs. These are
* called by JNI, not provided by JNI.
*/
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
本次的主角是 JNI_OnLoad,在我们学习jni的是,大部分给的示例都是例如我们在文章开头处所使用的那种方式,他会自动关联到的对应 native 方法,但实际上,这种关联到对应方法的过程,也是调用了__JNI_OnLoad__去实现的,
以下是从网络上找到的关于两种不同加载的介绍:
Android系统加载JNI Lib的方式有如下两种:
- 通过JNI_OnLoad
- 如果JNI Lib没有定义JNI_OnLoad,则dvm调用dvmResolveNativeMethod进行动态解析
System.loadLibrary调用流程如下所示:
System.loadLibrary->
Runtime.loadLibrary->(Java)
nativeLoad->(C: java_lang_Runtime.cpp)
Dalvik_java_lang_Runtime_nativeLoad->
dvmLoadNativeCode-> (dalvik/vm/Native.cpp)
1) dlopen(pathName, RTLD_LAZY) (把.so mmap到进程空间,并把func等相关信息填充到soinfo中)
2) dlsym(handle, "JNI_OnLoad")
3) JNI_OnLoad->
RegisterNatives->
dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
const char* signature, void* fnPtr)->
dvmUseJNIBridge(method, fnPtr)-> (method->nativeFunc = func)
此处仅为说明 JNI_OnLoad 是我们动态注册的入口,关于这个函数更多的使用和流程,可以转去看我开篇放的《Android的JNI_OnLoad简介与应用》,他写的比较详尽。
将我们的注册方法,在此函数中调用,就可以在加载动态库的时候,把我们的native方法关联起来。 但问题也和明显,当java层的一个方法,没能成功的和c层的函数关联起来,在使用时就会报错:java.lang.UnsatisfiedLinkError: No implementation found for ***
代码链接: 在路上了,稍等一两年