一步一步学习JNI

本文来自网易云社区

作者:孙有军

前言

本篇的主要目的就是JNI开发入门,使大家对JNI开发流程有一个大致的了解,后续再进行深入学习。

JNI不是Android特有的,JNI是Java Native Interface单词首字母的缩写,就是指用C或者C++开发的接口。JNI是JVM规范中的一部份,因此JNI程序在任何实现了JNI规范的Java虚拟机中都可以运行。

作为一个Android开发,这里不大书特书学习JNI的必要性和重要性,很多博客,文章都有讲述,这里主要给出入门步骤,让大家可以根据一步一步操作进行学习。

俗话说学习动最快的方式,就是找一个会的人,照着学,跟着做。照猫画虎,也能描出一个大概!

准备工作

我的开发环境为Mac os x 10.10.5,Eclipse 4.4.1(也可以不需要,有这个只是方便写代码,记事本,Sublime都可以)。由于环境为Mac,因此下面大部分的配置都是针对Mac的。

1,Java环境变量

由于需要Java环境,因此必须要设置Java环境变量,网上有很多方式教大家怎么设置。这里给出mac的设置方式。进入terminal。

1,输入cd ~进入当前用户

2,ls -al 可以看到一个.bash_profile文件,这就是设置环境变量的地方

3,如果没有输入:touch .bash_profile (用来修改文件时间戳,或者新建一个不存在的文件。)

4,输入 vim .bash_profile 输入E(表示进入编辑模式),输入I表示进入编辑输入

5,输入 export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home  (这是我的目录,你可以去Library下查看自己的JavaVirtualMachines)

6,按ESC键退出输入模式,:wq 退出编辑模式

7,source .bash_profile (使配置生效)

8,echo $JAVA_HOME 如果有输入表示设置生效

实例操作

1,编写一个Java类

这里类名为Hello,包名为com.sunny(可以随便设置),代码如下:

package com.sunny;public class Hello {   
 static{       
 //System.loadLibrary("Hello");
        System.load("/Users/doc/Jni/jniHello.jnilib");
    }    public static native int getSum(int a, int b);    
    public static void main(String[] args) {        // 虚拟机扫描加载的lib路径
        System.out.println(System.getProperty("java.library.path"));     
           int sum = getSum(2, 5);
        System.out.println(sum);
    }

}

从上面的代码中我们设置了一个native函数,进行两数字的求和,并且在静态代码块中加载了动态链接库代码。这里有两种加载方式:

System.loadLibrary("Hello")

该方式不需要加入lib前缀,也不需要jnilib(不同的操作系统会有不同的后缀),虚拟机会自动从System.getProperty("java.library.path")路径进行扫描加载。后面会讲述怎么将自己的动态链接库加入到扫描路径。如果不设置会抛出UnsatisfiedLinkError异常。

System.load("/Users/doc/Jni/jniHello.jnilib");

该方式是设置的全路径,他会从设置的路径进加载。

2,生成.class文件

这里我们对上述的Java类编译生成 .class文件,我上述的代码在/Users/doc/Documents/Eclipse/Jni路径中,这里如果你直接采用Eclipse->Run As -> Java Application是不能运行的,因为这里还没有动态链接库,会抛出以下的异常。

Exception in thread "main" java.lang.UnsatisfiedLinkError: 
Can‘t load library: 
/Users/doc/Jni/jniHello.jnilib   
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1827)  
   at java.lang.Runtime.load0(Runtime.java:809)   
    at java.lang.System.load(System.java:1086)  
      at com.sunny.Hello.<clinit>(Hello.java:7)

输入命令:

javac src/com/sunny/Hello.java -d bin

-d 表示将生成的.class文件放到bin目录下,你可以去本地看看是否已经有该文件。

3,生成.h文件

输入命令:

javah -jni -classpath bin -d jni com.sunny.Hello

上述生成的.h文件为comsunny_Hello.h,生成规则为包名加类名,.转为,尾缀为.h。
-jni为可选参数
-classpath 类查找路径,当前查找路径为bin目录
-d 与上述相同,表示将生成的.h放置到jni目录。

也可以通过-o参数生成指定的Hellox.h,该参数与-d互斥。命令如下:

javah -jni -classpath bin -o Hellox.h com.sunny.Hello

生成的.h文件如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_sunny_Hello */#ifndef _Included_com_sunny_Hello#define _Included_com_sunny_Hello#ifdef __cplusplusextern "C" {#endif/*
 * Class:     com_sunny_Hello
 * Method:    getSum
 * Signature: (II)I
 */JNIEXPORT jint JNICALL Java_com_sunny_Hello_getSum
  (JNIEnv *, jclass, jint, jint);#ifdef __cplusplus}#endif#endif

4,编写.c文件实现.h

这里可以采用VS来进行编写,也可以用一个文本文档来编写。代码如下:

#include "com_sunny_Hello.h"#ifdef __cplusplusextern "C" {#endif/*
 * Class:     com_sunny_Hello
 * Method:    getSum
 * Signature: (II)I
 */JNIEXPORT jint JNICALL Java_com_sunny_Hello_getSum
  (JNIEnv * env, jclass cls, jint a, jint b){      return a+b;
  }#ifdef __cplusplus}#endif

5,生成动态链接库

这里先补充前面的不同操作系统生成不同的名称。

Mac OS X : libHello.jnilib(又有前缀又有后缀)
Windows :Hello.dll(没有lib前缀)
Linux/Unix:libHello.so(是不是发现了android中常用的.so了)

Mac生成命令如下:

gcc -dynamiclib -o /Users/doc/Jni/libHello.jnilib Hello.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin

-dynamiclib表示生成动态链接库
-o表示生成动态链接库放置的位置于名字
-framework JavaVM -I:编译JNI需要用到JVM的头文件(jni.h),第一个目录是平台无关的,第二个目录是与操作系统平台相关的头文件,这里就是我们前面为什么要设置JAVA环境变量的原因。

Windows生成方式如下:

手头没有下载vs,因此从网上搜索了一下vs生成dll方式。开始菜单-->所有程序-->Microsoft Visual Studio 2012-->打开VS2012 X64本机工具命令提示,用cl命令编译成dll动态库:

cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD Hello.c -FeHello.dll

-I :和mac一样,包含编译JNI必要的头文件
-LD:标识将指定的文件编译成动态链接库
-Fe:指定编译后生成的动态链接库的路径及文件名

Linux生成命令如下:

Linux生成,记住该生成方式,因为后续Android底层是linux系统,因此最终生成使用的都是.so动态链接,生成的命令如下:

gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared Hello.c -o libHello.so

由于我本地下载的是mac版的jdk,编译会出现的错误,因为找不到对应linux平台的相关信息。

In file included from Hello.c:1:
In file included from ./com_sunny_Hello.h:2:
//Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include/jni.h:45:10: fatal error: ‘jni_md.h‘ file not found#include "jni_md.h"
         ^1 error generated.

这里我们为了演示怎么用命令行生成.so,因此把上述命令改成如下方式,后续会用AndroidStudio来生成.so动态链接库:

gcc -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin -fPIC -shared Hello.c -o libHello.so

-I 与-o与上述概念一致。
-fPIC:    编译成与位置无关的独立代码
-shared:同Fe与dynamiclib,编译成动态库

6,运行结果

这里可以采用Eclipse->Run As -> Java Application来运行了, 还要用命令行来运行。输入如下命令:

java -classpath bin com.sunny.Hello

输出结果如下:

/Users/doc/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
7

上述采用的是System.load("/Users/doc/Jni/jniHello.jnilib")方式,这里填入的是全路径,因此编译运行时,会从输入的路径去查找动态链接库,输出结果为:第一行是java.library.path扫描的路径,第二行7就是我们native返回的结果。

前面我们说了两种加载方式。上面编译的是全路径,不需要将当前的动态链接库放置到扫描路径下面。如果采用System.loadLibrary("Hello")方式,则需要将动态链接库放置到Java虚拟机能找到的地方。如果不进行设置则会抛出如下异常:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no Hello in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)
    at com.sunny.Hello.<clinit>(Hello.java:6)

怎么链接到动态链接库呐?可以采用如下三种方式进行设置:

方法1:则需要将刚才生成的动态链接库放置到java.library.path扫描到的任何一个路径下面。

方法2:项目邮件选择properties

之后选择Java Build Path - Libraries - JRE System Library - Native library location - edit

之后选择本地生成的动态链接库

方法3:给jvm添加“-Djava.library.path=动态链接库搜索目录”参数,命令如下:

 java -Djava.library.path=/Users/doc/Jni/ -classpath bin
 com.sunny.Hello

设置了动态链接库后,方法二,与方法三输出结果如下:

/Users/doc/Jni/
7

从结果看出,这里输出的java.library.path路径与全路径输出已经不相同了,输出从默认查找的路径变成了设置的路径。

总结

这里主要做了一个初步的入门,后续再进行深入学习。如果有错误请指出。

网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区

相关文章:
【推荐】 关于评审--从思想到落地

原文地址:https://www.cnblogs.com/163yun/p/9705772.html

时间: 2024-10-11 10:51:32

一步一步学习JNI的相关文章

一步一步学习androidNDK编程(java给c传递数据)

这篇已经是"一步一步学习androidNDK编程"的第四篇了,在这篇中,我们将会在java中传递代码给c代码. 首先,我们新建一个android工程"ndkdata", 第一步: 同样的首先声明native方法,如下: public native int add(int a,int b); public native String helloSir(String name); public native int[] intMethod(int[] intArray)

一步一步学习Vue(十一)

本篇继续学习vuex,还是以实例为主:我们以一步一步学Vue(四)中讲述的例子为基础,对其改造,基于vuex重构一遍,这是原始的代码: todolist.js ; (function () { var list = []; var Todo = (function () { var id = 1; return function (title, desc) { this.title = title; this.desc = desc; this.id = id++; } })(); /** *

一步一步跟我学习lucene(19)---lucene增量更新和NRT(near-real-time)Query近实时查询

这两天加班,不能兼顾博客的更新,请大家见谅. 有时候我们创建完索引之后,数据源可能有更新的内容,而我们又想像数据库那样能直接体现在查询中,这里就是我们所说的增量索引.对于这样的需求我们怎么来实现呢?lucene内部是没有提供这种增量索引的实现的: 这里我们一般可能会想到,将之前的索引全部删除,然后进行索引的重建.对于这种做法,如果数据源的条数不是特别大的情况下倒还可以,如果数据源的条数特别大的话,势必会造成查询数据耗时,同时索引的构建也是比较耗时的,几相叠加,势必可能造成查询的时候数据缺失的情况

一步一步跟我学习lucene(9)---lucene搜索之拼写检查和相似度查询提示(spellcheck)

suggest应用场景 用户的输入行为是不确定的,而我们在写程序的时候总是想让用户按照指定的内容或指定格式的内容进行搜索,这里就要进行人工干预用户输入的搜索条件了:我们在用百度谷歌等搜索引擎的时候经常会看到按键放下的时候直接会提示用户是否想搜索某些相关的内容,恰好lucene在开发的时候想到了这一点,lucene提供的suggest包正是用来解决上述问题的. suggest包联想词相关介绍 suggest包提供了lucene的自动补全或者拼写检查的支持: 拼写检查相关的类在org.apache.

一步一步学习IdentityServer3 (1)

学习之初: IdentityServer3我自己最开始了解到的就是做一个SSO单点登录,后面发现还有单独的认证服务功能,其实它还可以做APIs的访问控制,资源授权,另外还可以为提供第三方登录,其他的自由定制目前也在学习中. 网络飞速发展的今天,庞大的数据,庞大的用户,庞大的业务,都需要登录的支撑,登录授权完全看作一个单独的一门技术,Web端需要登录,App端需要登录,Api需要授权访问等等,除此以外大型业务系统,子系统之间其实也需要这样一个登陆授权,所以,登录不再是单一的一个功能点了,简单的查询

一步一步学习SignalR进行实时通信_8_案例2

原文:一步一步学习SignalR进行实时通信_8_案例2 一步一步学习SignalR进行实时通信\_8_案例2 SignalR 一步一步学习SignalR进行实时通信_8_案例2 前言 配置Hub 建立DrawingHub 页面 javascript 实现效果 结束语 参考文献 前言 这讲分析一个案例,在一个画板上画画实时在其他客户端上显示. 配置Hub 在Startup中进行配置: public void Configuration(IAppBuilder app) { app.MapSign

一步一步跟我学习lucene(13)---lucene搜索之自定义排序的实现原理和编写自己的自定义排序工具

自定义排序说明 我们在做lucene搜索的时候,可能会需要排序功能,虽然lucene内置了多种类型的排序,但是如果在需要先进行某些值的运算然后在排序的时候就有点显得无能为力了: 要做自定义查询,我们就要研究lucene已经实现的排序功能,lucene的所有排序都是要继承FieldComparator,然后重写内部实现,这里以IntComparator为例子来查看其实现: IntComparator相关实现 其类的声明为 public static class IntComparator exte

【转】朱兆祺带你一步一步学习嵌入式(连载)

原文网址:http://bbs.elecfans.com/jishu_357014_2_1.html#comment_top  从最初涉及嵌入式Linux开始到现在,深深的知道嵌入式的每一步学习都是举步维艰.从去年11月份开始,我就着手整理各种学习资料,希望推动嵌入式学习的前进贡献自己微不足道的一份力量.从去年到现在,将C语言的学习经验整理成<攻破C语言笔试与机试陷阱及难点>(现在仍在更新),这份资料已经在电子发烧友论坛的单片机论坛连载(http://bbs.elecfans.com/jish

Android 从硬件到应用:一步一步向上爬 4 -- 使用 JNI 方法调硬件驱动

Android下,java应用程序通过JNI方法调用硬件抽象层模块,在Android 从硬件到应用:一步一步向上爬 3 -- 硬件抽象层访问硬件驱动 中我们已经编译好了硬件抽象层模块,下面就要开始为HAL层编写JNI方法,为上层提供服务. cd到frameworks/base/services/jni目录中,新建一个com_android_server_GpioService.cpp文件: #include "jni.h" #include "JNIHelp.h"