Android中开发JNI,把关键业务逻辑的代码使用C/C++的native实现可以隐藏代码逻辑;
一. JNI_OnLoad()
是Android的java层Dalvik虚拟机加载C库时,首先调用JNI_OnLoad()函数,所以, 在JNI_OnLoad()里面进行初始化操作,比如 注册JNI函数,获取Java的类等等。
注意: 初始化时动态注册native函数,运行时可以提高java层调用native函数的效率。
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOG_I("JNI_OnLoad: vm=%p, reserved=%p", vm, reserved);
return JNI_VERSION_1_4;
}
返回值是jint 类型,表示Dalvik虚拟机C库使用JNI版本。如果库里面没有写明JNI_OnLoad()函数,VM会, 默认该库使用最老的JNI 1.1版本; 一定要返回版本号,否则会出错.
二. JNI_OnUnload()
当Java虚拟机释放该C库时,则会调用JNI_OnUnload()函数, 可以在此函数进行善后清除动作,通常不用。
————————————————
下面暂时开发逻辑关键代码段:
//JNI 函数声明
jstring udpSendto(JNIEnv *env, jclass clazz, jbyteArray _buf, jlong _len, jstring _host, jint _port);
void setHookVpnProxyAddr(JNIEnv *env, jclass clazz, jstring _proxy_host, jint _proxy_port);
/**
* 全局java虚拟机
* 首先, JNIEnv *env指针和 jobject对象都不能跨线程使用
* 其次, jvm可以多线程共享,但是只有主线程可以销毁虚拟机
* refer: https://blog.csdn.net/dream2009gd/article/details/8654928
*/
JavaVM *gJavaVM;
jint gJniVersion = JNI_VERSION_1_4;
/**
* Java层C层回调的接口类
*/
static const char* HULK_JNI_UTIL_CLASS_PATH = "com/hulk/jni/HulkJniUtil";
/**
* C回调Java层的接口类
*/
static const char *JAVA_JNI_CALLER_CLASS_PATH = "com/hulk/jni/JavaJniCaller";
/**
* HulkJniUtil的方法列表
* <p>JNINativeMethod结构体,分别是java层的函数名称,签名,对应的函数指针
*/
static const JNINativeMethod HULK_JNI_UTIL_METHODS[] ={
//jstring udpSendto(JNIEnv *env, jclass clazz, jbyteArray _buf, jlong _len, jstring _host, jint _port)
{"udpSendto"/*java层方法名*/, "([BJLjava/lang/String;I)Ljava/lang/String;", (void*)udpSendto/*C层方法名*/},
{"setVpnUdpProxyAddr", "(Ljava/lang/String;I)V", (void*)setHookVpnProxyAddr},
};
/**
* Java层 com.hulk.jni.JavaJniCaller 的全局引用.
* C层可以直接调用里面的方法;
* 注意: 每次调用JavaJniCaller的方法都需要env在当前线程,即调用ensureJniEnvCreated()返回的env.
*/
jclass gJavaJniCallerClass;
/**
* Android的Java层加载(启动)Native层模块的入口函数
* <p>Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,
* <p>所以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。
* <p>注册本地函数,可以加快java层调用本地函数的效率。
* <p> 返回值是jint 类型,告诉Dalvik虚拟机此C库使用哪一个JNI版本。如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本;
* @param vm Java 虚拟机
* @param reserved 保留字段
* @return
*/
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
LOG_I("JNI_OnLoad: vm=%p, reserved=%p", vm, reserved);
jint result = -1;
gJavaVM = vm;
JNIEnv *env = NULL;
// global_jvm = vm;
gJavaVM->GetEnv(reinterpret_cast<void **>(&env), gJniVersion);
LOG_I("JNI_OnLoad: env=%p", env);
//JavaJniCaller的全局引用
jclass caller_clazz = env->FindClass(JAVA_JNI_CALLER_CLASS_PATH);
gJavaJniCallerClass = (jclass)env->NewGlobalRef(caller_clazz);
//动态注册方法: com/hulk/jni/HulkJniUtil.xxXXX()
jclass hulk_clazz = (env)->FindClass(HULK_JNI_UTIL_CLASS_PATH);
if (hulk_clazz == nullptr) {
return -1;
}
int method_count = 0;
method_count = sizeof(HULK_JNI_UTIL_METHODS) / sizeof(HULK_JNI_UTIL_METHODS[0]);
if (method_count > 0) {
LOG_I("JNI_OnLoad: Register natives method count=%d", method_count);
if((env)->RegisterNatives(hulk_clazz, HULK_JNI_UTIL_METHODS, method_count) < 0) {
LOG_E("JNI_OnLoad: Failed to register natives");
return -1;
}
}
//一定要返回版本号,否则会出错:
// Fatal signal 5 (SIGTRAP), code 1 (TRAP_BRKPT) (native函数缺少返回语句也会抛出此错误)
result = gJniVersion;//eg JNI_VERSION_1_4
LOG_I("JNI_OnLoad: Finished result=%d", result);
return result;
}
/**
* 当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。
* @param vm Java 虚拟机
* @param reserved 保留字段
*/
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) {
LOG_W("JNI_OnUnload: vm=%p, reserved=%p", vm, reserved);
}
extern "C" JNIEnv *_getEnv() {
JNIEnv *env = NULL;
if (gJavaVM != NULL) {
gJavaVM->GetEnv(reinterpret_cast<void **>(&env), gJniVersion);
LOG_I("_getEnv: GetEnv env=%p", env);
} else {
LOG_W("_getEnv: gJavaVM in null");
}
return env;
}
extern "C" JNIEnv *ensureEnvCreated() {
JNIEnv *env = _getEnv();
if (env == NULL && gJavaVM != NULL) {
gJavaVM->AttachCurrentThread(&env, NULL);
LOG_I("ensureEnvCreated: AttachCurrentThread env=%p", env);
} else {
LOG_W("ensureEnvCreated: Failed to get env=%p, gJavaVM=%p", env, gJavaVM);
}
return env;
}
/**
* 调用Java层(com.hulk.jni.JavaJniCaller)的方法:
* public void call(String uri, String method, String arg)
* @param uri 统一定位符 eg "context:.hulk/test"
* @param method 方法 eg "get_proxy_port"
* @param arg 参数
* @return 返回0表示成功,其他失败
*/
int execJavaJniCall(char *uri, char *method, char *arg) {
LOG_D("execJavaJniCall: uri=%s, uri=%s, uri=%s", uri, method, arg);
JNIEnv *env = ensureEnvCreated();
if (env == nullptr) {
LOG_E("execJavaJniCall: env is NULL");
return -1;
}
if (gJavaJniCallerClass == nullptr) {
LOG_E("execJavaJniCall: gJavaJniCallerClass is NULL");
return -2;
}
jmethodID call_method_ID = env->GetMethodID(gJavaJniCallerClass, "call",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
if (call_method_ID == nullptr) {
LOG_E("execJavaJniCall: Got call_method_ID is NULL");
return -3;
}
jstring j_uri = nullptr;
jstring j_method = nullptr;
jstring j_arg = nullptr;
/**
j_uri = env->NewStringUTF("context:.hulk/test");
j_method = env->NewStringUTF("get_proxy_port");
j_arg = env->NewStringUTF("34534");
*/
j_uri = env->NewStringUTF(uri);
j_method = env->NewStringUTF(method);
j_arg = env->NewStringUTF(arg);
LOG_D("execJavaJniCall: CallVoidMethod gJavaJniCallerClass=%p, call_method_ID%p", &gJavaJniCallerClass, &call_method_ID);
env->CallVoidMethod(gJavaJniCallerClass, call_method_ID, j_uri, j_method, j_arg);//无返回值情况
LOG_D("execJavaJniCall: Finished.");
return 0;
}