1、JNI是什么

JNI是Java Native Interface的缩写,它提供若干的API实现Java与其他语言之间的通信。而Android Framework由基于Java语言的的Java层与基于C/C++语言的C/C++层组成,每个层中的功能模块都是以有相应的语言编写,并且两层中的大部分模块有着千丝万缕的联系。而在两层之间充当连接桥梁这一角色的就是JNI,它允许Java代码和C/C++编写的应用程序与库之间进行交互;通常在以下几种情况下使用JNI

1、注重处理速度,C/C++的处理速度要优于Java语言

2、硬件控制,硬件驱动程序通常使用C语言编写,而要是Java层能够控制硬件,需要用到JNI

3、C/C++代码的复用,一些好的C/C++模块可以被多处复用

2、在Java中调用C库函数

下面以一个例子来说明在Java代码中调用C库函数的流程

1、编写Java代码

class HelloJNI {

    /*声明本地方法,该函数在C库中实现*/
    native void printHello();
    native void printString(String str);

    /*在静态块中加载C库,可以保证在main方法前加载完成*/
    static { System.loadLibrary("./hellojni"); }

    public static void main(String args[])
    {
        HelloJNI myJNI = new HelloJNI();

        /*调用C库中实现的函数*/
        myJNI.printHello();
        myJNI.printString("Hello world from printstring func");
    }
}

在上述代码中使用native关键字声明本地方法,告诉Java编译器,此函数由其他语言编写;在静态块中加载hellojni库,该库由C语言实现,

如果是在Linux系统下则会加载libhellojni.so,如果在Windows系统下则会加载hellojni.dll;(本文以Linux系统为测试环境)

2、编译Java代码

 javac HelloJNI.java

编译Java代码很简单,只要配置好JDK就可以完成编译,需要注意的是此时编译通过,但如果运行的话,由于没有实现本地函数,所以会抛出找不到函数的异常

3、生成C头文件

当Java调用本地函数printHello或者printString时并非直接映射到C语言的printHello或者printString函数,而是有一套自己的映射方法,使用如下命令即可生成C函数的头文件
javap HelloJni
执行完成后生成HelloJni.h如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

可以看到生成的函数原型并非与Java代码调用的函数一致,函数有JNIEXPORT和JNICALL两个关键字声明,这两个关键词是必须的,有了他们JNI才能正常调用函数;而通过观察函数名称,我们可以知道其命名方式是"Java_类名_本地方法名"; 再看参数,可知JNIEnv*和jobject是本地函数的共同参数,第一个参数是JNI接口的直接,用来调用JNI提供的基本函数集;第二个参数中保存着调用本地方法的对象的一个引用,上例中的jobject中保存的对象myJNI的引用,其他的参数根据Java代码的本地方法的调用生成的

4、编写C/C++代码

把上一步骤生成的HelloJni.h头文件include进来,实现其声明的函数即可,编写hellojni.c如下

#include "HelloJNI.h"
#include <stdio.h>

/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)
{
    printf("Hello World!\n");
    return;
}

/*
 * Class:     HelloJNI
 * Method:    printString
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string)
{
    /*JNI提供的基本函数集,将jstring转化成char *类型*/
    const char *str = (*env)->GetStringUTFChars(env, string, 0);
    printf("%s! \n" ,str);

    return ;
}

5、生成C动态链接库

gcc -fPIC -shared -o libhellojni.so hellojni.c -I$JAVA_HOME/include

其中JAVA_HOME已经配置到环境变量中,表示JDK安装的目录,需要指定其中的include目录使用jni.h头文件

6、运行Java程序

此时执行java HelloJni会提供找不到hellojni库,这是由于在加载C库的时候在默认目录中没有找到libhellojni.so库,只需将该库复制到/usr/lib/下再次执行

xlzh@cmos:~/code/jni/simpleJNI$ java HelloJNI
Hello World!
Hello world from printstring func!

3、调用JNI函数

上图来自<Android框架揭秘>

由上图可知此示例程序有JniFuncMain类、JniTest类和libjnifunc.so(linux系统)组成,此示例有Java和C代码混合而成。

JniFuncMain类:

public class JniFuncMain
{
    private static int staticIntField = 300;

    /*加载libjnifunc.so库*/
    static { System.loadLibrary("jnifunc"); }

    /*使用static关键字声明本地方法,再C库中实现*/
    public static native JniTest createJniObject();

    public static void main(String[] args) {
        System.out.println("[Java] createJniObject() call native method");
        /*调用C库的createJniObject,得到JniTest对象,注意不是用new*/
        JniTest jniObj = createJniObject();
        /*利用JniTest对象调用JniTest中的方法*/
        jniObj.callTest();
    }
}

此例中与上例不同的是本地方法返回了一个JniTest类的对象的引用,这样就可以在JniFuncMain类中调用JniTest类的方法。

JniTest类

class JniTest {

    private int intField;

    public JniTest(int num)
    {
        intField = num;
        System.out.println("[Java] call JniTest: intFiled" + intField);
    }

    public int callByNative(int num)
    {
        System.out.println("[Java] JniTest 对象的 callByNative(" + num + ")调用");
        return num;
    }

    public void callTest()
    {
        System.out.println("[Java] JniTest对象的callTest() 方法调用: intField = " + intField);
    }
}

JniTest类提供两个方法供JniFuncMain类和C库函数调用

JniFuncMain.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniFuncMain */

#ifndef _Included_JniFuncMain
#define _Included_JniFuncMain
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JniFuncMain
 * Method:    createJniObject
 * Signature: ()LJniTest;
 */
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

使用javah JniFuncMain生成头文件,需要注意的是第二个参数是jclass,而不是jobject,这是由于该本地方法在JniFuncMain类中声明的是static方法,所以第二个参数表示的该类的应用,而不需要对象的引用

jnifunc.cpp

#include "JniFuncMain.h"

JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz)
{
    jclass targetClass;
    jmethodID mid;
    jobject newObject;
    jstring hellostr;
    jfieldID fid;
    jint staticIntField;
    jint result;

    /*获取JniFuncMain类的staticField变量值*/
    fid = env->GetStaticFieldID(clazz, "staticIntField", "I");
    staticIntField = env->GetStaticIntField(clazz, fid);
    printf("[CPP] 获取 JniFuncMain类的staticIntField 值\n");
    printf("    JniFuncMain.staticIntField = %d\n", staticIntField);

    /*查找生成对象的类*/
    targetClass = env->FindClass("JniTest");

    /*查找构造方法*/
    mid = env->GetMethodID(targetClass, "<init>", "(I)V");

    /*生成JniTest对象*/
    printf("[CPP] JniTest 对象生成 \n");
    newObject = env->NewObject(targetClass, mid, 100);

    /*调用对象的方法*/
    mid = env->GetMethodID(targetClass, "callByNative", "(I)I");
    result = env->CallIntMethod(newObject, mid, 200);

    /*设置JniObject对象的intField值*/
    fid = env->GetFieldID(targetClass, "intField", "I");
    printf("[CPP] 设置JniTest对象的intField值为200\n");
    env->SetIntField(newObject, fid, result);

    /*返回对象引用*/
    return newObject;
}

如果想在C代码中访问Java中的成员变量,就需要获取相应成员变量的ID值,成员变量的ID值保存在jfieldID类型的变量中;获取成员变量ID的JNI本地方法有两个,分别是

/* 获取Java中的静态成员变量ID
 * env      : JNI接口指针
 * clazz    : 包含成员变量的类的jclass
 * name     : 成员变量名称
 * signature: 成员变量签名
 */
jfield GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature);
/*获取Java中的普通成员变量ID*/
jfield GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature);

上述示例中要访问Java类中的静态成员变量,所以需要使用GetStaticFieldID方法,其他参数很简单,直接使用即可,而对于变量的签名,则需要借助Java反编译器javap命令,如下所示

xlzh@cmos:~/code/jni/middleJNI$ javap -s -p JniFuncMain
Compiled from "JniFuncMain.java"
public class JniFuncMain {
  private static int staticIntField;
    Signature: I
  public JniFuncMain();
    Signature: ()V

  public static native JniTest createJniObject();
    Signature: ()LJniTest;

  public static void main(java.lang.String[]);
    Signature: ([Ljava/lang/String;)V

  static {};
    Signature: ()V
}

可以看到,staticIntField的签名是I,将I传入第四个参数即可,其他函数中用到签名的时候可用同样的方法获取

OK,我们得到了成员变量的ID,那么如何通过成员变量的ID来获取或者设置成员变量的值呢?就需要用到以下几个JNI函数

/*
 * 获取Java类中静态成员变量的值
 * <jnitype> jobject,jboolean,jbyte,jchar, jshort, jint, jlong, jfloat, jdouble
 * <type>    Object ,Boolean ,Byte,Char  , Short , Int,  Long , Float , Double
 * env:      JNI接口指针
 * jcalss:   包含成员变量的类
 * jfieldID: 成员变量ID
 */
<jnitype> GetStatic<type>Field(JNIEnv *env, jcalss jclazz, jfieldID fieldID)

/*
 * 获取Java类的对象中普通成员变量的值
 */
<jnitype> Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID)

/*
 * 设置Java类中静态成员变量的值
 */
<jnitype> SetStatic<type>Field(JNIEnv *env, jcalss clazz, jfieldID fieldID, <type> value)

/*
 * 设置Java类的对象中普通成员变量的值
 */
<jnitype> Set<type>Field(JNIEnv *env, jobject obj,  jfieldID fieldID, <type> value)
 
与成员变量类似, 获取和调用类中方法的JNI函数原型如下
/*获取Java类静态方法的ID*/
jmethod GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature)
/*获取Java类的对象中普通方法的ID*/
jmethod GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature)
/*调用Java类中的静态方法*/
 <jnitype> CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethod methodID, ...)
/*调用Java类的对象中的普通方法*/
 <jnitype> Call<type>Method(JNIEnv *env, jobject obj, jmethod methodID, ...)
如何获取Java类的对象呢?以示例中获取JniTest类的对象代码例,分为三步
1、获取JniTest的类
2、获取JniTest类的构造方法ID
3、通过构造方法的ID调用和JniTest类使用NeoObject方法生成对象
对比上例中获取JniTest的对象流程,可以很清楚的进行对照

4、在C代码中运行Java类

Java类编译的字节码需要在Java虚拟机上运行,那么在C/C++中运行Java类自然也需要加载Java虚拟机;JNI为我们提供了一套Invocation API,它允许本地代码在自身内存区域内加载Java虚拟机,同样我们以实例的方式进行讲解

InvocationApiTest.java

public class InvocationApiTest {

    public static void main(String[] args) {
        System.out.println(args[]0);
    }
}

invocationApi.c

#include <jni.h>

int main(void)
{
    JNIEnv *env;
    JavaVM *vm;
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jclass stringClass;
    jobjectArray args;

    /*加载虚拟机选项*/
    options[0].optionString = "-Djava.class.path=.";
    vm_args.version = JNI_VERSION_1_6;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = JNI_TRUE;

    /*生成虚拟机*/
    res = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);

    /*查找并加载类*/
    cls = (*env)->FindClass(env, "InvocationApiTest");

    /*获取main()方法的ID*/
    mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");

    /*生成字符串对象*/
    jstr = (*env)->NewStringUTF(env, "Hello Invocation API!!");
    stringClass = (*env)->FindClass(env, "java/lang/String");
    args = (*env)->NewObjectArray(env, 1, stringClass, jstr);

    /*调用main()方法*/
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    /*销毁虚拟机*/
    (*vm)->DestroyJavaVM(vm);
}
编译允许结果如下
xlzh@cmos:~/code/jni/superJNI$ javac InvocationApiTest.java
xlzh@cmos:~/code/jni/superJNI$ sudo echo "/usr/lib/jvm/java-1.7.0-openjdk-amd64/jre/lib/amd64/jamvm" >> /etc/ld.so.conf
xlzh@cmos:~/code/jni/superJNI$ sudo ldconfig
xlzh@cmos:~/code/jni/superJNI$ gcc -o a.out invocationApi.c -I$JAVA_HOME/include -L$JAVA_HOME/jre/lib/amd64/jamvm/ -ljvm
xlzh@cmos:~/code/jni/superJNI$ ./a.out
Hello Invocation API!!
xlzh@cmos:~/code/jni/superJNI$ 
 

Android学习笔记--JNI的使用方法的更多相关文章

  1. android学习笔记----JNI中的c控制java

    面向对象的底层实现 java作为面向对象高级语言,可对现实世界进行建模.和面向过程不同的是面向对象软件的编写不是流程的堆积,而是对业务逻辑的多视角分解和分类.其过程大致为:      1).将知识分解 ...

  2. Android 学习笔记之Volley(七)实现Json数据加载和解析...

    学习内容: 1.使用Volley实现异步加载Json数据...   Volley的第二大请求就是通过发送请求异步实现Json数据信息的加载,加载Json数据有两种方式,一种是通过获取Json对象,然后 ...

  3. Android学习笔记之JSON数据解析

    转载:Android学习笔记44:JSON数据解析 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,为Web应用开发提供了一种 ...

  4. Android学习笔记36:使用SQLite方式存储数据

    在Android中一共提供了5种数据存储方式,分别为: (1)Files:通过FileInputStream和FileOutputStream对文件进行操作.具体使用方法可以参阅博文<Andro ...

  5. Android学习笔记之Activity详解

    1 理解Activity Activity就是一个包含应用程序界面的窗口,是Android四大组件之一.一个应用程序可以包含零个或多个Activity.一个Activity的生命周期是指从屏幕上显示那 ...

  6. Pro Android学习笔记 ActionBar(1):Home图标区

     Pro Android学习笔记(四八):ActionBar(1):Home图标区 2013年03月10日 ⁄ 综合 ⁄ 共 3256字 ⁄ 字号 小 中 大 ⁄ 评论关闭 ActionBar在A ...

  7. 【转】Pro Android学习笔记(九八):BroadcastReceiver(2):接收器触发通知

    文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.sina.com.cn/flowingflying或作者@恺风Wei-傻瓜与非傻瓜 广播接 ...

  8. 【转】 Pro Android学习笔记(九二):AsyncTask(1):AsyncTask类

    文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowingflying/ 在Handler的学习系列中,学习了如何h ...

  9. 【转】 Pro Android学习笔记(七四):HTTP服务(8):使用后台线程AsyncTask

    目录(?)[-] 5秒超时异常 AsyncTask 实现AsyncTask抽象类 对AsyncTask的调用 在哪里运行 其他重要method 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注 ...

随机推荐

  1. VMware SphereESXi上传系统镜像

    VMware SphereESXi上传系统镜像 打开右侧[摘要]选项卡 在[资源]中选择存储器中的存储,右键[浏览数据库存储] 选择工具栏[创建文件夹]图标,命名后保存 这样随后找到存储设备,浏览刚才 ...

  2. EF数据存贮问题二之“无法定义这两个对象之间的关系,因为它们附加到不同的 ObjectContext 对象”

    “无法定义这两个对象之间的关系,因为它们附加到不同的 ObjectContext 对象”,这是在EF中,一对多关系表,有外键的类保存至数据库中出现的错误. 我原来是用JAVA开发的,习惯性的处理一对多 ...

  3. 在CentoOS中安装g++ 并连接Oracle数据库

    1.安装运行环境 # yum install gcc-c++ 备注:此时会将gcc-c++和libstdc++-devel都安装上. 2.查看g++是否安装成功[root@MyRHEL 桌面]# g+ ...

  4. keydown和keypress

    常见的键盘事件是keyup和keydown.淡蓝就经常用 document.onkeyup = function (e) { if ((e.keyCode || e.which) === 13) // ...

  5. jQuery选择器的学习

    jQuery的核心在于它的选择器,通过观看视频和阅读,发现jQuery选择器大体上的分类可分为这么几种(不同人方式不同,这里选择一个自认为比较好的): 1.基础选择器(对应api文档中的基本选择器和层 ...

  6. CSS选择器4是下一代CSS选择器规范

    那么,这一版本的新东西有哪些呢? 选择器配置文件 CSS选择器分为两类:快速选择器和完整选择器.快速选择器适用于动态CSS引擎.完整选择器适用于速度不占关键因素的情况,例如document.query ...

  7. 从运行原理及使用场景看Apache和Nginx

    用正确的工具,做正确的事情. 本文只作为了解Apache和Nginx知识的一个梳理,想详细了解的请阅读文末参考链接中的博文. Web服务器 Web服务器也称为WWW(WORLD WIDE WEB)服务 ...

  8. C++第一课(2013.9.26 )

    //C++三大特性:封装,继承,多态 //C++新增的数据类型:bool型 一个字节 真 true 假 false //case 定义变量的问题 ; switch(nValue) { : { prin ...

  9. Python中range的用法

    函数原型:range(start, end, scan): 参数含义:start:计数从start开始.默认是从0开始.例如range(5)等价于range(0, 5); end:技术到end结束,但 ...

  10. C语言递归分析

    思路 下图描述的是从问题引出到问题变异的思维过程: 概述 本文以数制转换为引,对递归进行分析.主要是从多角度分析递归过程及讨论递归特点和用法. 引子 一次在完成某个程序时,突然想要实现任意进制数相互转 ...