概述
在开发framework的时候有时会遇到需要自己开发JNI,以便使Java能够调用自己底层开发的库。网上的文章一般都是介绍如何通过命名规则及javah,使jni层函数与java层函数自动建立链接(Java虚拟机通过命名规则建立),本文将讲解如何动态注册jni函数。
依赖库及头文件
先贴出Android.mk的代码
1 LOCAL_PATH :=$(call my-dir) 2 include $(CLEAR_VARS) 3 4 LOCAL_SRC_FILES := kiki_jni.cpp 5 kiki_local.cpp 6 7 LOCAL_SHARED_LIBRARIES := libandroid_runtime 8 libnativehelper 9 liblog 10 11 #dependence 12 LOCAL_REQUIRED_MODULES := 13 14 LOCAL_C_INCLUDES += $(JNI_H_INCLUDE) 15 16 #add micro define 17 LOCAL_CFLAGS += 18 19 LOCAL_MODULE := liblearnjni_jni 20 21 include $(BUILD_SHARED_LIBRARY)
LOCAL_SHARED_LIBRARIES:需要的共享库,在android中我们用刀libandroid_runtime及libnativehelper两个。
LOCAL_C_INCLUDES:需要的头文件包含,写$(JNI_H_INCLUDE)即可。
在Android的JNI架构中,一般会有一个JNI的cpp文件用于沟通上层Java与底层Cpp,具体的业务逻辑实现会放在底层Cpp的另一个文件中,因此这里用kiki_jni.cpp模拟JNI文件桥,kiki_local.cpp模拟实际实现逻辑的文件。
实际使用的头文件主要有:
#include <jni.h> #include <JNIHelp.h> #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h"
JNI注册流程
JNI注册流程其实很简单,主要分为三个步骤:
1.实现JNI本地函数
2.通过JNINativeMethod机构体将Java函数与本地函数一一关联,实现映射表
3.调用AndroidRuntime::registerNativeMethods方法注册函数映射表
那么什么时候注册函数映射表呢?一般实现在jint JNI_OnLoad(JavaVM* vm, void* reserved)函数中。这个函数会在动态库被加载后第一时间执行,因此一般会在里面做一些初始化操作。具体代码如下:
1 using namespace android; 2 3 static const char* const kLearnJni = "android/kiki/LearnJni"; 4 5 static jint 6 android_kiki_LearnJni_helloJni(JNIEnv *env, jobject thiz, jstring ip, jint port) { 7 return getMagicNum(); 8 } 9 10 static JNINativeMethod gMethods[] = { 11 { 12 "helloJni", 13 "(Ljava/lang/String;I)I", 14 (void *)android_kiki_LearnJni_helloJni 15 }, 16 }; 17 18 int register_android_kiki_LearnJni(JNIEnv *env) { 19 return AndroidRuntime::registerNativeMethods(env, kLearnJni, gMethods, NELEM(gMethods)); 20 } 21 22 jint JNI_OnLoad(JavaVM* vm, void* reserved) { 23 JNIEnv *env = NULL; 24 jint res = -1; 25 26 if(vm -> GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { 27 ALOGE("ERROR: Get java env failed"); 28 goto bail; 29 } 30 31 if(register_android_kiki_LearnJni(env) <= 0) { 32 ALOGE("ERROR: LearnJni native registration failed"); 33 goto bail; 34 35 } 36 37 res = JNI_VERSION_1_4; 38 ALOGD("JNI library onload success"); 39 40 bail: 41 return res; 42 }
其他信息
每个Java进程有一个JavaVM,每个执行线程有一个JNIEnv,可以通过JavaVM获取当前JNIEnv。JNIEnv的本质就是提供出来的JNI操作Java对象的方法和成员变量的API接口类。JNIEnv通过getMethod及get<Type>Field方法获得Java对象的方法及成员变量,通过对应的call<Type>Method方法及set<Type>Field方法操作他们。
Java函数在JNI中签名对应如下:
比较特殊的就是boolean转为Z,long转为J。