先看一个参考说明:Android JNI(实现自己的JNI_OnLoad函数) http://blog.youkuaiyun.com/zhenyongyuan123/article/details/5862054
这次尝试使用 env->RegisterNatives 的方法向系统注册 jni 的接口。向系统注册,还可以使用 AndroidRuntime::registerNativeMethods 这个方法,要详细内容可自己百度。
jni_call.cpp文件修改如下:
#include "tools.h"
#include <jni.h>
#include "JNIHelp.h"
#include <android/log.h>
#define JNIREG_CLASS "com/example/jnicall/MainActivity"//指定要注册的类
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "jni_call", __VA_ARGS__)
#ifdef __cplusplus
extern "C" {
#endif
jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject thiz, jint add1, jint add2){
LOGD("add = %d",add(add1, add2));
return add(add1, add2);
}
#ifdef __cplusplus
} // extern "C"
#endif
JNIEXPORT void JNICALL jni_getImage(JNIEnv* env, jclass jclazz)
{
LOGD("jni_getImage");
return ;
}
static JNINativeMethod gMethods[] = {
{ "_getImage", "()V", (void*) jni_getImage }
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env)
{
if ( !registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) )
{
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
if ( (vm->GetEnv( (void**) &env, JNI_VERSION_1_4)) != JNI_OK) {
return -1;
}
if ( !registerNatives(env) ) { //注册
return -1;
}
return JNI_VERSION_1_4;
}
改动比较大,增加了 log 的输出,#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "jni_call", __VA_ARGS__) 是 jni 曾增加 log 的定义宏,要加头文件 <android/log.h> ,而且在 mk 文件里也要添加编译库,下面会附上mk文件内容。
在 JNI_OnLoad 增加了注册函数 registerNatives(env) ,有调用 registerNativeMethods 函数,最终调用 env->RegisterNatives(clazz, gMethods, numMethods) 这个函数,主要是里面传递的第二个参数 gMethods :
static JNINativeMethod gMethods[] = {
{ "_getImage", "()V", (void*) jni_getImage }
};
这个 JNINativeMethod 数据结构体就是把 java 的 native 方法映射成 C++ 函数。(个人观点)
然后把结构体向系统注册后,java 调用 native 方法时,就是调用 C++ 函数了。JNINativeMethod 的命名规范可上网查,简单来说,第一项是 java 里定义的 native 函数名,个人习惯用 _ 开头。第二项是表示函数的返回值和参数,()V 表示无返回无参数,括号内是参数,这里是空的,V 表示无返回值。第三项就是对应 C++ 函数名了。前面一定要加 (void*) ,无论是否带返回值。
注意:这里函数是有顺序的,否则编译会不通过,也就是说,在 JNI_OnLoad 之前,要先写 static int registerNatives(JNIEnv* env),不然编译的错误就是:
error: 'registerNatives' was not declared in this scope
我曾经就犯过这种错误,写出来也算是提醒自己不要再犯同样的错误吧。
apk 的 MainActivity.java 修改如下:
package com.example.jnicall;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
add(1,2);
_getImage();
}
static {
System.load("/system/jni/libjnicall.so");
}
public native int add(int add1, int add2);
public native void _getImage();
}
Andrfoid.mk 文件修改如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtools
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := tools.cpp
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -lm -llog
LOCAL_MODULE := libjnicall
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := jni_call.cpp
LOCAL_STATIC_LIBRARIES := libtools liblog
LOCAL_MODULE_PATH := $(LOCAL_PATH)/out/lib
include $(BUILD_SHARED_LIBRARY)
编译运行 apk,打印的 log 如下:
D/jni_call( 4715): add = 3
D/jni_call( 4715): jni_getImage
就说明 RegisterNatives 注册 jni 函数正常可使用了。
这种注册的方法对比前面固定包名的方法主要有点就是修改 C++ 函数会改动较少,网上说这种运行更有效率,我个人未必认同。但 android 里使用注册这种方法更多倒是对的。
代码文件:http://download.youkuaiyun.com/detail/u013820413/6996453