Android JNI的动态注册

我们知道Androd 使用JNI的注册方法包括两个,静态注册和动态注册。
静态注册就是通过javah命令生成.h文件,然后实现声明的函数即可,但此类函数的函数名都比较长:Java+包名+类名+方法名。而动态注册则没有这一限制。先以下面这个例子进行说明,例子中的jni函数参考了Android M源码。

  1. 我们新建一个的Android工程,包含一个默认的MainActivity。代码如下:
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层返回的字符串
    }
}
  1. 右键工程, Android tools -> Add native …,库的名字填写上面的myJNI即可。eclipse要先配置好NDK,这里不再说明。其实也可以不用在eclipse配置NDK,自己用NDK命令去编译也可以。

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”

你可能感兴趣的:(Android JNI的动态注册)