您的位置:首页 > 移动开发 > Android开发

Android JNI 学习笔记

2015-08-13 17:22 603 查看
JNI 是 Java Native Interface(Java 本地接口)。JNI不是Android 专有的东西,他是从Java继承来的。但是 对于Android来说JNI至关重要,Android 作为一种嵌入式操作系统,有大量和驱动、硬件相关的功能都是用C/C++来实现的。可以说在Android中不管应用级还是系统级的开发都离不开JNI。

Java语言的执行,离不开JVM,因此当需要在Java层中调用C/C++层时,需要先告诉JVM那个方法代表本地函数,伊基在哪里能找到这个函数,反之也一样。但是从Java层调用C/C++需要建立函数间的关联;而C/c++到Java。必须先得到Java对象的引用,才能调用该对象的方法(Java纯面向对象)。

需要注意的是,不论是从C/C++到Java 还是从Java到C/C++都是在一个线程中运行的。

从JAVA到C/C++

装载JNI动态库

为了能够使用JNI,在调用本地方法前必须把C/C++代码所在的动态库装载到进程的内存空间中。一般之间使用System的LoadLibrary() 方法

public  static void loadLibrary(String libName);
值得一提的是,libName是我们生成的动态库名称的一部分(注意:这里是一部分),在Android 中JNI动态库的名称必须为"lib"开始,那么一般我们的libName就会去掉前缀"lib"和后缀".so"或者“.dll”。加入我们本地的动态库文件为"libjni.so",那么我们的调用就是

System.loadLibrary("jni");


注:动态库的后缀在Linux中是".so",在Window中是“.dll”。

在这里我们并不需要指定我们的动态链接库的具体路劲。Android系统会自动在相关的系统路径里面查找。

一般情况下LoadLibrary()会在类的static代码块中执行(为了保证native方法在调用之间就已经加载完成,static代码块会首先执行);

public class Sample{

static {

System.loadLibrary("jni");

}

}


定义native方法

Java层申明native方法很简单:

private  static native  final void native_init();


在native方法中,可以使用任何类型作为参数,包括基本对象类型、数组、复杂对象。Java中调用native方法和普通方法无异。

JNI动态链接库

JNI动态链接库与非动态库的区别就在于:JNI动态库中定义了"JNI_OnLoader"函数,这个函数在System.LoadLibrary();后被系统调用,用于完成JNI函数的注册。

jint JNI_OnLoader(JavaVm* vm, void* reserved)
在"JNI_OnLoader"中,最重要的一件事就是通过registerNativeMethods()函数完成JNI函数的注册(完成Java层中申明的native方法和C/C++中C函数关联);这样一来,我们的JVM在运行native方法时就能够找到对应的C函数。

jnitest.cpp

#define LOG_TAG "hello-JNI"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
static jint com_jeason_jnitest_nadd(JNIEnv *env, jobject obj, jint a, jint b){
return (a * b);
}
static JNINativeMethod gMethods[] = {
{"nadd", "(II)I", (void *)com_jeason_jnitest_nadd},
};
static int register_android_test_add(JNIEnv *env){
return android::AndroidRuntime::registerNativeMethods(env, "com/jeason/jnitest/LoadLibraryJavaClass", gMethods, NELEM(gMethods));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved){
JNIEnv *env = NULL;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
printf("Error GetEnv\n");
return -1;
}
assert(env != NULL);
if (register_android_test_add(env) < 0) {
printf("register_android_test_add error.\n");
return -1;
}
return JNI_VERSION_1_4;
}
registerNativeMethods()函数的原型:

int registerNativeMethods(JNIEnv* env,const char* classname,const JNINativeMethos* gMethods,int numberMethods);


第二个参数className 指的是我们的Java类(调用SYstem.loadLibrary())的路径全名,但是在名称中‘.’需要用路径符'/'.

第三个参数gMethods 是JNINativeMethods类型数组

typedef  struct {

const char* name;
const char* signature;
void*   fnPtr;
} JNINativeMethods;


其中name 是指Java中申明native方法的名称;signature是指native方法参数签名(后面给出说明);

fnPtr是指native方法对应的本地函数指针,一般都为void* (无法直接指定,一般只能用void*,调用时在强制类型转换)

通常函数原型一般为

JNIEXPORT jint JNICALL com_jeason_jnitest_jnitext_nadd(JNIEnv *env, jobject obj ,jint a,jint b);


JNIEXPORT 和 JNICALL 可以省略

LoadLibraryJavaClass.java

public class LoadLibraryJavaClass {

static {

System.loadLibrary("jni");

}

public native int nadd(int a, int b);

}


参数签名

native方法中的参数签名使用了一些缩写符号来代表参数类型,这些符号都是Java语言规定的。签名由参数和返回值组成,参数必须用小括号括起来,没有的时候也必须要使用一对空括号。例如"(I)V"表示方法有一个整型参数,无返回值。"([IZ)I"表示有2个参数,第一个是整形数组第二个是布尔型,返回值为整形。

具体的对应关系见下面两张图:



数组则以”["开始,用两个字符表示



以上都是基本数据类型,前面我们解决了JNI函数的注册问题,下面我们来考虑这样一个问题。在java中调用native函数传递的参数是java数据类型,那么这些参数类型到了JNI会变成什么呢? 下面我们引出了一个新的话题——数据类型转换。

数据类型转换:

在java层调用native函数传递到JNI层的参数,JNI层会做一些特殊处理,我们知道java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待的。下表示出了java数据类型—>native类型的转换。



其中在java数据类型中,除了java中基本数据类型和数组,Class,String,Throwable,其余所有的java对象的数据类型在JNI中用jobject表示。
但是,是不是所有的都这么简单呢

{"myway",“(Ljava.lang.String;Ljava.lang.String;Landroid.bean.Bean;)V”,(void*)myway}


对于JNI的编译:

Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
jnitest.cpp
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime
LOCAL_MODULE := libjni
include $(BUILD_SHARED_LIBRARY)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: