不通过头文件,那通过啥?
答案是:通过
注册本地函数的方法来实现jni的调用,这种方式更适用于在
源码环境编译。
相关环境
- ubuntn14.04 (64位)
- 源码环境:full_z703_32-eng(全编)
- 编译所需要的文件,如截图:
其中,src文件夹是java代码,res文件夹是资源文件夹,结构跟Android studio项目的一致。
jni文件夹里面放的就是c,c++的源码文件,当你需要调用开源的c,c++代码时,也是放在这个文件夹里面。同时还需要在这个文件夹里编写Android.mk文件来把c,c++源码编译成库。
其中,选中的两个文件夹是关键,native3.cpp用来实现java本地方法,Android.mk文件用来指导编译出库文件。其他的都是开源的memtester的代码。
第一步,java层面
- 在java代码中声明一个类,并声明一个本地方法,同时加载库。
class Native
{
static {
System.loadLibrary("simplejni2");//加载库
}
static native int dotest(String size,String loopnum);//声明一个本地方法
}
第二步,c、c++层面
//可以看到该函数跟在java代码声明的格式有点区别,但是也是一一对应起来的
static jint
dotest(JNIEnv *env, jobject thiz, jstring size, jstring loopnum) {
return 0;
}
- 在c++代码中去调用注册本地函数的函数(注意,这里必须是c++代码才行)
步骤简介:
- 首先要指定好声明本地函数的java的类名
- 然后要写好函数签名的格式
- 声明并实现注册本地函数的函数
- 在JNI_OnLoad函数里调用上述(3)的本地注册函数(JNI_OnLoad函数会在java代码加载库的时候被调用)
- 写好Android.mk文件
- 再次明确哪些变量需要写对!!!
步骤1详解:
代码:
static const char *classPathName = "com/guohao/testcharforjni/Native2";
步骤2详解:
函数签名,不同的参数和返回值需要有对应的写法,
参考:
http://blog.csdn.net/conowen/article/details/7524744
对应:static
jint dotest(JNIEnv *env, jobject thiz,
jstring size, jstring loopnum)函数
本地函数签名的
代码写法:
//
Ljava/lang/String;Ljava/lang/String;对应的是两个String的参数
//
I对应的是返回的int类型
static JNINativeMethod methods[] = {"dotest", "(Ljava/lang/String;Ljava/lang/String;)I", (void*)dotest };
步骤3详解:
前两个步骤写好了,这一步的代码都是一个模板。
这函数的作用就是注册本地函数,然后将会在步骤4的
JNI_OnLoad函数里被调用。
代码:套路型的,可直接复制
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
步骤4详解:
该步骤要实现的JNI_OnLoad函数,将会被
第一步,java层面
的代码:
System.loadLibrary("memtester");//
加载库
执行的时候调用。
相关代码:套路型的,可直接复制
/*
* This is called by the VM when the shared library is first loaded.
*/
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
ALOGI("JNI_OnLoad");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
ALOGE("ERROR: registerNatives failed");
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
步骤5详解:
Android.mk文件的作用,告诉编译器要讲哪些文件编译,编译成什么类型的。
代码:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
#要将谁编译
LOCAL_SRC_FILES:= memtester.c tests.c native3.cpp
#要编译成什么
LOCAL_MODULE:= libsimplejni2
#引入哪些共享库
LOCAL_SHARED_LIBRARIES := \
libutils liblog
#引入哪些头文件
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE)
# No special compiler flags. 不太懂有啥用
LOCAL_CFLAGS += -fpermissive -Wwrite-strings -Wparentheses
#编译成共享库
include $(BUILD_SHARED_LIBRARY)
步骤6详解:
在编写这些代码文件的过程中,很容易混淆一些变量的对应值。
现在来总结一下:
围绕着库这个中心来展开,
首先编译生成的库名在jni/Android.mk文件指定:LOCAL_MODULE:= libsimplejni2
引入这个库的代码就在java代码中指定:System.loadLibrary("simplejni2");//
加载库
其次,在c++注册本地函数时,要指定java声明的本地类
c++代码:static const char *classPathName = "com/guohao/testcharforjni/Native2";
java声明的代码:
class Native
{ 。。。 }
最后,{项目路径}/Android.mk文件还要指定生成的apk的名字:
LOCAL_PACKAGE_NAME := MemTestDemo