一个简单的例子带你了解jni流程



1、前言

jni是java调用原生语言来进行开发的一座桥梁,原生语言一般是指c,c++语言,即jni机制可以让java语言调用c,c++语言,也可以让c,c++语言调用java语言。这样的相互调用,互相结合,主要是出现在对性能要求较高的应用上。在android中,由于它的开发语言也是java,所以也可以利用原生语言进行开发,对jni的了解和使用有助于我们在做应用的时候,对于时间性能要求较高的代码段,可以用原生语言来开发,然后java通过jni机制来调用它,就可以达到很好的效果。

总的来说,jni是一种机制,一种可以然java和原生语言互相调用的机制。

学习jni开发需要有c++或者c基础。


2、基本环境要求

利用jni机制开发,对于开发环境是有特别要求的,因为c,c++语言的开发也需要环境的支持。

单纯的android开发:jdk,sdk,eclipse,adt。

如果要在eclipse上进行jni开发,那么需要:ndk,cygwin,ant。

本文接下来的例子都是使用eclipse的方法进行开发,使用android studio的朋友,由于编译器自身的集成,利用jni机制进行开发会更加方便,但是为了更深入的了解jni机制,本文使用的是eclipse。


对于android的基本开发环境的安装就不提了,jni需要的开发环境这里就提一下,方便完全没基础的朋友安装环境。

2.1 ant的安装

ant是一个命令行构建工具,它主要是驱动目标进行任务的进行。在jni中一般是用来驱动命令。它的安装可以去 ant下载官网下载。一般下载zip压缩包即可。然后解压安装到我们指定的目录(自由的目录位置)。最关键的一步是添加环境变量。

假如你的ant根目录是E:\apache-ant-1.9.7。那么你只需要复制这个文件夹下的bin路径,添加到高级环境变量的path变量中。注意与path变量本身存在的值必须添加分号进行分割。

比如:



随后我们用命令行工具输入ant -version命令。如果安装成功就会输出ant的版本号。



2.2cygwin的安装


由于android原生语言开发环境包(NDK)是基于类unix系统运行的,它包含了许多shell脚本,而它们又是不可以直接在window系统运行的,因此需要安装cygwin模拟类unix系统的环境。 cygwin的下载地址

由于笔者的安装环境以及完成,所以并不能图文并茂德截图讲解。为了方便大家的安装,笔者决定直接使用参考资料的截图。


















读者只需要按照笔者的截图顺序去做就可以安装成功了,cygwin一定要安装成功,否则会导致ndk运行的失败,这也是为什么笔者花这么多图的原因。


2.3 NDK的安装

NDK即android原生语言开发环境包。下载地址: NDK的下载

我们下载好压缩包解压到我们指定的目录,然后需要添加环境变量,比如笔者的NDK安装在:E:\android-ndk-r11b。那么只需要在path变量里面添加此路径即可,记得用分号分隔。




随后我们在命令行窗口输入ndk-build命令。如果有以下输出说明安装成功。





2.4 在eclipse里面配置jni开发环境


需要指定NDK的目录路径。笔者的如下:




3、实践过程进行总结

实践才是检验真理的唯一标准,通过上面的环境配置,我们就可以搭建一个可以开发jni的android环境了,接下来就一步一步引导大家去开发一个jni实例。


首先新建一个空白的android项目。

我们新建一个类,专门处理native函数。笔者命名为CppUtils。内容如下:

public class CppUtils {

	static {
		System.loadLibrary("cppUtils");
	}

	/**
	 * 从CPP获取字符串
	 * 
	 * @return
	 */
	public native String getStringFromCPP();

	

}

这里是简单的从c++原生代码中获取字符串。


3.1 System.loadLibrary

java在java.lang.System包提供了两个静态方法用于加载共享库(一种含原生语言实现的可供android程序调用的库),分别是load,loadLibrary两个方法,由于我们在程序启动的时候就需要加载共享库,因此放在静态代码块中加载。这两个方法的参数是共享库名称。注意共享库为了跨平台使用,它的文件名称会包含一些前缀,而共享库文件的后缀是so。比如我们加载cppUtils共享库,其实他的全名是:libcppUtils.so。


3.2 native标签

native标签用于告诉java编辑器,它的方法是由原生语言实现的,因此不需要去实现它。native方法用分号结束。即如果你希望一个方法用原生语言实现,那么你就给它声明为native方法。


3.3 生成原生语言头文件

由于原生语言头文件需要根据字节码文件来进行分析,所以,在生成头文件之前,我们必须对项目进行build。之后打开我们项目的bin/classes的文件夹,笔者的文件夹如下图:



接着我们就要针对CppUtils.class进行分析生成头文件,在我们对编写原生语言头文件的时候,最好借助工具生成,而不是手写,这样出错的概率才会更低,否则很容易发生jni桥无法将java函数与原生方法联系起来的错误。生成头文件的方法,就是使用命令行工具。比如笔者这里就是,先进入自己项目要分析的java文件的目录下,然后生成头文件。生成头文件的命令如下:
javah -classpath bin/classes com.example.jnibolg.CppUtils

完整的操作过程看下图:




然后回到eclipse中,刷新下我们的项目,我们会发现多了一个以h结尾的文件,这个就是机器生成的头文件。




关于原声函数的实现以及祥光头文件,我们需要放在jni文件夹中,因此,我们接下来需要在项目中建立jni文件夹,并将相关文件放进去。




这样我们的原生文件就生成了。


3.4 分析头文件

接下来我们需要分析头文件的内容,有助于帮助我们了解整个实现过程。首先我们看头文件的代码:

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

#ifndef _Included_com_example_jnibolg_CppUtils
#define _Included_com_example_jnibolg_CppUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jnibolg_CppUtils
 * Method:    getStringFromCPP
 * Signature: ()Ljava/lang/String;
 */
<pre name="code" class="cpp">JNIEXPORT jstring JNICALL Java_com_example_jnibolg_CppUtils_getStringFromCPP
  (JNIEnv *, jobject);

#ifdef __cplusplus}#endif#endif
 
 
首先我们可以看到jni.h头文件被包含了,这个头文件包含了jni机制为了实现从java对象到原生语言的映射的规则。因此,我们一切java调用原生函数,或者原声函数调用java,都必须通过它来实现。
其次我们关注这个函数声明:
JNIEXPORT jstring JNICALL Java_com_example_jnibolg_CppUtils_getStringFromCPP
  (JNIEnv *, jobject);

这个函数声明说明了,它实现的是jnibolg包下的CppUtils类的getStringFromCPP方法,返回的是jstring类型,这是一个jni类型,映射到java的string类型。这里不详细解析jni类型映射,以免变得复杂,主要以实现一个例子了解整个流程为主。

JNIEnv 是一个指针,指向jni对象。通过它,就可以调用jni.h头文件包含的所有函数。即,它就是一个指向jni对象的指针。

jobject表示当前函数所实现方法所属的java对象,这里指的是CppUtils的一个实例。


有了头文件,那么接下来,我们需要用到NDK了,这就需要获取NDK的支持。


3.5 获取NDK的支持

右击我们的项目,找到android tools,点击add native support,这样就可以获取NDK开发包的支持了。点击之后,会需要你填写一个名称,这个名称将会用作共享库的名称,同时这个名字也是我们CppUtils中System.loadLibrary所包含的文件名称。



确定之后,我们看jni文件夹,会生成NDK支持的文件。




其中cppUtils.cpp是我们要进行实现的C++文件。Android.mk是NDK的makefile文件,通过他,可以将原生语言的实现生成为共享库。我们之前安装的cygwin目的就是支持NDK的系统构建。我们打开我们NDK的目录,笔者的如下:


可以发现NDK有很多makefile文件,这些文件都是用于帮助构成共享库的。


3.6 分析mk文件的内容


我们打开Android.mk文件,内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := cppUtils
LOCAL_SRC_FILES := cppUtils.cpp

include $(BUILD_SHARED_LIBRARY)


LOCAL_PATH,这是一个用于定位源文件的宏,在Android.mk文件中,它必须是第一个变量。
include $(CLEAR_VARS),作用是清除命名冲突,因为Android构建系统在单次执行中会构建多个文件和模块,为了避免LOCAL_<NAME>模式的变量名冲突,必须包含这条指令。
LOCAL_MODULE,指的是生成的共享库的名称,为了适应不同的架构,生成的共享库会含有lib前缀。
LOCAL_SRC_FILES,指的是生成共享库的源文件,多个源文件之间用空格隔开。
include $(BUILD_SHARED_LIBRARY)指令表示生成一个共享库。

关于共享库的生成,可以有更复杂的组织,比如多个共享库依赖某个静态库.....这里不详细讲解,只为了让大家理解整个流程。我们要知道的就是,基本的生成流程都是按照上面这几条指令顺序来的。


3.7实现原生代码 


我们打开cppUtils.cpp文件,会发现是空的。如果包含了jni.h头文件指令,我们把他删掉,因为我们即将要实现的头文件已经包含了jni,h头文件,所以我们的源文件无需再次包含。

接着我们将头文件需要实现的函数声明复制过来,为了避免出错,强烈建议复制过来。然后修改成下面这个样子。

#include "com_example_jniblog_CppUtils.h"

JNIEXPORT jstring JNICALL Java_com_example_jniblog_CppUtils_getStringFromCpp
  (JNIEnv * env, jobject jthis)
{
   return env->NewStringUTF("来自C++");
}


3.8调用native函数

做完了上面这些,就可以函数调用了。调用和正常的java调用没有差别的。比如这里就是:

private TextView text;
	CppUtils cppUtils = new CppUtils();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		text = (TextView) findViewById(R.id.text);
		text.setText("从C++获取字符串:" + cppUtils.getStringFromCpp());
	}




3.9 总结


从整个流程下来,我们可以清晰的知道每一步要怎么做为什么这么做这么做的意义,虽然并没有深入讲解,但是对于整个jni流程来说,是一个很好的了解。



---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767-----------


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值