我们知道Androd 使用JNI的注册方法包括两个,静态注册和动态注册。
静态注册就是通过javah命令生成.h文件,然后实现声明的函数即可,但此类函数的函数名都比较长:Java+包名+类名+方法名。而动态注册则没有这一限制。先以下面这个例子进行说明,例子中的jni函数参考了Android M源码。
package com.example.jnidynamic;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView mTextView;
// 声明一个jni函数,待会去实现
private native String getStringFromJni();
static {
System.loadLibrary("myJNI"); // 加载库,待会会用NDK去生成该库
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.my_text_view);
mTextView.setText(getStringFromJni()); // 原理很简单,显示一个从JNI层返回的字符串
}
}
myJNI.cpp
#include <jni.h>
#include <android/log.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
/* 定义了一些宏,这里用来输出Log用的,JNI层输出Android LOG, JNI层输出LOG可以参考我以前写的,LOG_TAG是tag */
#ifndef LOG
#define LOG_TAG "myJNI"
#define ALOGD(...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
#define ALOGE(...) \
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
#define ALOGV(...) \
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
#endif LOG
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
// c函数
jstring native_hello(JNIEnv* env, jobject thiz)
{
return env ->NewStringUTF("hello world from jni~");
}
// java类的位置
static const char *classPathName = "com/example/jnidynamic/MainActivity";
// 映射表,这里将我们java函数和c函数对应起来
static JNINativeMethod gMethods[] = {
{"getStringFromJni", "()Ljava/lang/String;", (void*)native_hello},
};
/* 下面的可以说,基本上是模板了,以后直接copy就行 */
static int jniRegisterNativeMethods(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;
}
static int register_my_jni_methods(JNIEnv* env) {
return jniRegisterNativeMethods(env, classPathName, gMethods, NELEM(gMethods));
}
// 在我们load该so库的时候,JNI_OnLoad将会自动被调用,来注册JNI函数
jint JNI_OnLoad(JavaVM* vm, void*)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
if (register_my_jni_methods(env) < 0) {
ALOGE("ERROR: native registration failed\n");
goto bail;
}
/* success -- return valid version number */
ALOGE("SUCCESS: native registration successed\n");
result = JNI_VERSION_1_4;
bail:
return result;
}
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
LOCAL_MODULE := myJNI
LOCAL_SRC_FILES := myJNI.cpp
include $(BUILD_SHARED_LIBRARY)
其实这里的重点是JNINativeMethod这里结构体
typedef struct {
const char* name; //Java中函数的名字
const char* signature; //用字符串描述的函数的参数和返回值
void* fnPtr; //指向C函数的函数指针
} JNINativeMethod;
这里面我觉得比较难书写的是signature, 反映的是函数的参数和返回值。括号内()为参数,最后为返回值。
具体的每一个字符的对应关系如下
字符 | Java类型 | C类型 |
---|---|---|
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
[I | jintArray | int[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
[D | jdoubleArray | double[] |
[J | jlongArray | long[] |
[Z | jbooleanArray | boolean[] |
上面的都是基本类型。如果Java函数的参数是java的某个类,则以”L”开头,以”;”结尾,中间是用”/” 隔开的包名和类名。而其对应的C函数名的参数则统一为jobject。但String类是个例外,其对应的类为jstring。
举下面几个例子:
字符 | Java类型 | C类型 |
---|---|---|
Ljava/lang/String; | String | jstring |
Ljava/net/Socket; | Socket | jobject |
L包名/类名; | 类名 | jobject |
如果java类是一个内部类的话,则用$作为类名间的分隔符。
例如 :
“(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z”