热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[置顶]Android本地接口JNI的使用分析

本地接口JNI的使用分析我们知道,在AndroidFramework中,其是由基于Java语言的Java层和基于CC++语言的CC++层所组成,那么他们之间是怎样

本地接口JNI的使用分析

我们知道,在Android Framework中,其是由基于Java语言的Java层和基于C/C++语言的C/C++层所组成,那么他们之间是怎样沟通的那?在Android中,其提供了一种中间的媒介或是桥梁,能很好的将Java层和C/C++层联系起来,使得他们互相协调,共同完成功能任务。在这两层之间充当桥梁的即是JNI了,我们称之为“Java本地接口”(Java Native Interface),它允许Java代码编写的应用程序能够与使用C/C++编写生成的库(.so/.dll)进行交互操作。

这就是本篇文章要介绍的JNI本地接口了,同时,在这里我会介绍下由谷歌提供的NDK工具,它是用来快速编写基于JNI机制的应用的一套工具集。好了,下面逐一介绍下。

JNI的原理:

 

如上图所示,JNI的工作机制是这样的,当我们准备好了Java的本地方法和对应的函数原型之后,Java虚拟机在加载动态库的时候先检查动态库中是否含有与本地方法同签名的方法,如果有的话,其会将Java本地方法和对应的函数原型进行映射,这样我们就可以在JNI中调用本地方法来访问底层驱动了。如果没有的话,则会抛出异常,停止工作。

JNI的使用:

接下来,介绍下JNI的使用,它的使用分为以下几个步骤:

1、编写一个Java本地方法

2、编译上面Java本地方法

A、使用Java编译器 即使用命令javac来编译 生成对应的class文件

B、运行当前的项目 会自动生成对应的class文件 

依个人兴趣 选择即可 我使用的第二种 因为方便

3、生成C/C++头文件

在这里使用Java编译器命令javah来生成对应上面的class文件的C/C++头文件

我的系统是windows 所以可以cd到class文件的目录 使用javah编译生成需求的头文件

4、编写C/C++代码

A、将C/C++头文件代码复制到对应名称的.c文件(这里使用C编写)

B、按照头文件中的规则编写实现文件即可

5、生成动态共享库(.so)

A、使用C/C++开发工具 如visual c++ 或visual studio等工具来生成库文件

B、我们也可以使用辅助工具Cygwin来编译生成库文件

6、运行Android程序

在这里,我会举个简单的例子来详细的介绍JNI的使用流程,具体如下所示,我的项目目录:

 

运行结果如下:

当我点击界面上的按钮,会调用Java本地方法,然后通过JNI将其与JNI的本地函数进行沟通打印LOG日志:

 

 

好了,接下来开始详细的介绍。

首先,先编写Java的本地方法,这个本地方法主要是为JNI的本地函数生成提供原型,我的代码:

public class VerifyJNIMethod {

static {

System.loadLibrary("hello_jni");

}

public static native String helloJni();

}

代码是很简单的,就是简单的打印LOG日志,在这里使用了静态块加载方式static,用意是当Android系统启动时就将本地的.so库加载到Java虚拟机中,以便调用及时不致发生错误。

另外,对于Java本地方法的方法是只有声明没有实现的,而且必须使用关键字native来声明,否则编译器会报错,这个关键字主要是告诉编译器当前修饰的方法为只有声明的方法,并且是有C/C++语言来实现的。

其次,我们要编译上面的Java本地方法,可以使用编译器命令javac来编译(前提注意JDK的环境变量是否设定正确),当然也可以直接运行项目程序(这是的本地方法不要调用JNI,因为其不识别),会自动生成本地方法的class文件。

接下来,就是编译.class文件生成对应的C/C++头文件了,我们只需要使用编译器命令javah即可自动生成头文件,具体如下:

因为我的电脑是windows的,所以打开命令窗口,cd到我们的项目目录下,执行命令:javah -classpath bin/classes -d jni com.demo.verifyjni.jni.VerifyJNIMethod在项目根目录下新建一个jni目录来存放生成的头文件,当然后面编写的实现文件和Android.mk文件也放在这里吧!这是自动生成的头文件为com_demo_verifyjni_jni_VerifyJNIMethod.h(命名规范:包名_类名),这个名字有点过长,我们将其修改为VerifyJNIMethod.h使其与下面的实现文件名称一致,便于理解。

 

我的头文件如下:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include 

/* Header for class com_demo_verifyjni_jni_VerifyJNIMethod */

 

#ifndef _Included_com_demo_verifyjni_jni_VerifyJNIMethod

#define _Included_com_demo_verifyjni_jni_VerifyJNIMethod

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     com_demo_verifyjni_jni_VerifyJNIMethod

 * Method:    helloJni

 * Signature: ()Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_com_demo_verifyjni_jni_VerifyJNIMethod_helloJni

  (JNIEnv *, jclass);

 

#ifdef __cplusplus

}

#endif

#endif

如果是第一次看到这个文件,会认为很复杂特别是对C不熟悉的读者,但静下心来看其实很简单,分析如下:

在这里我们只需要关注函数原型部分,也就是JNIEXPORT jstring JNICALL Java_com_demo_verifyjni_jni_VerifyJNIMethod_helloJni

  (JNIEnv *, jclass);

部分,这里的函数原型的规则是这样的:JNIEXPORT+JNI数据类型+JNICALL+Java_包名_类名_方法名,里面的JNICALL、JNIEXPORT都是JNI的关键字,表示此函数要被JNI调用的,另外方法里面的参数为共同参数,其中的JNIEnv *参数是JNI接口的指针,用来调用JNI表中各种JNI函数(非JNI的本地函数),来创建或调用Java对象用的指针类型。第二个参数jclass则为如果Java的本地方法为静态方法的话,那么就会出现这个参数,否则参数类型为jobject,jclass代表着JNI中的类与Java中的类的对应类型,而jobject则为对象类型。

 

好了,头文件已经编译成功了,接下来就是头文件的实现文件的编写了。实现文件的编写推荐根据头文件规范编写,具体如下:

#include 

#include "VerifyJNIMethod.h"

 

JNIEXPORT jstring JNICALL Java_com_demo_verifyjni_jni_VerifyJNIMethod_helloJni

  (JNIEnv *env, jclass obj)

{

return (*env)->NewStringUTF(env,"Hello-Jni!"); 

}

这个文件当中使用了函数NewStringUTF(),它则代表着JNI函数,用来将C的字符创转换为Java的字符串类型并返回。返回之后,就会在Android前段接收返回的内容并打印LOG日志。

注意:JNI提供了多种JNI函数,用来处理C/C++与Java之间的转化操作,具体看查看地址:http://java.sun.com/docs/books/jni

到这里,上面已经准备好了Java本地方法、对应的C/C++的头文件和实现文件,那么就剩下了怎么生成C的共享库文件了。这个也比较简单,我们可以使用辅助工具Cygwin(下载地址:http://www.cygwin.com)来编译生成库文件,但必须注意一点必须编写Android.mk文件并配置,这样才会变异通过,我的mk文件如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := hello_jni

LOCAL_SRC_FILES := VerifyJNIMethod.c

include $(BUILD_SHARED_LIBRARY)

对于这个文件会在下面的NDK中介绍。依我的习惯是将头文件、实现文件及mk文件放到项目外的jni目录下,然后打开Cygwin软件cd到目录:/cygdrive/jni/jni,执行命令$NDK/ndk-build回车即可生成需要的.so文件了,我的如下:

 

好了,最后就剩下了怎么在前端使用Java调用JNI的方法来打印LOG了,我的实现代码如下:

public class VerifyJNIActivity extends Activity {

private static String TAG = "VerifyJNIActivity";

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_verify_jni);

}

public void helloJNI(View v) {

String result = VerifyJNIMethod.helloJni();

log(result);

}

private void log(String log) {

Log.d(TAG, log);

}

}

代码简单至极,调用VerifyJNIMethod.helloJni()打印LOG日志即可,没当点击下按钮就会打印”Hello-Jni!”了,效果在上面已经列出了。接下来来介绍下谷歌提供的NDK工具来快捷开发基于JNI的C/C++的代码与Java交互了。

注意:在最后为了减小APK包的大小,我们会将根目录下的jni目录删除掉,它只是为演示而已。

 

 

 

 

/**

* 欢迎加入技术群:179914858

* 如果有任何问题请在评论中进行讨论学习

*/

 



推荐阅读
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • Qt QTableView 内嵌控件的实现方法
    本文详细介绍了在 Qt QTableView 中嵌入控件的多种方法,包括使用 QItemDelegate、setIndexWidget 和 setIndexWidget 结合布局管理器。每种方法都有其适用场景和优缺点。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 利用jstack进行死锁检测与线程堆栈分析
    本文介绍了如何使用jstack工具进行Java应用中的死锁检测及高CPU使用率线程的堆栈分析,帮助开发者快速定位并解决性能瓶颈。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 探讨 HDU 1536 题目,即 S-Nim 游戏的博弈策略。通过 SG 函数分析游戏胜负的关键,并介绍如何编程实现解决方案。 ... [详细]
  • 版本控制工具——Git常用操作(下)
    本文由云+社区发表作者:工程师小熊摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 本文介绍了如何通过Java代码计算一个整数的位数,并展示了多个基础编程示例,包括求和、平均分计算、条件判断等。 ... [详细]
  • 本题要求在一组数中反复取出两个数相加,并将结果放回数组中,最终求出最小的总加法代价。这是一个经典的哈夫曼编码问题,利用贪心算法可以有效地解决。 ... [详细]
  • QNX 微内核(procnto-instr)的监测版本内置了高级跟踪与分析工具,能够实现实时系统监控。该模块适用于单处理器及多处理器系统。 ... [详细]
  • 题目描述:给定一个N*M的网格,初始时网格中有k个芯片,每个芯片的位置已知。玩家可以在每一步操作中将所有芯片沿同一方向移动一格。如果芯片到达边界,则保持不动。目标是通过一系列操作,使每个芯片依次访问指定的目标位置。 ... [详细]
  • 本文介绍如何在Spring Boot项目中集成Redis,并通过具体案例展示其配置和使用方法。包括添加依赖、配置连接信息、自定义序列化方式以及实现仓储接口。 ... [详细]
author-avatar
Li_pengwei
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有