JNI的基础概念

Java 程序可以通过 JNI(Java Native Interface,Java 本地调用)访问本地的动态链接库,从而扩展其功能、保护关键代码、提高运行效率。可以通过NDK工具生成so库

1. 什么情况下需要使用 JNI

  • Java 本机接口(Java Native Interface (JNI))是一个本机编程接口,它是 Java 软件开发工具箱(Java SoftwareDevelopment Kit (SDK))的一部分,JNI 它提供了若干的 API,实现了和 Java 和其他语言的通信(主要是 C&C++)。
  • JNI 允许 Java 代码使用以其它语言(譬如 C 和 C++)编写的代码和代码库。注意本使用都是 C 的代码和库。
  • Invocation API(JNI 的一部分)可以用来将 Java 虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用 Java 代码。
  • 我们知道 Java 是一种平台无关性的语言,平台对于上层的 java 代码来说是透明的,所以在 Android 上层应用中,大部分情况是不需要 JNI 的。但是在嵌入式 Android 开发中,以下两种情况肯定需要用到 JNI。
  • 第一种情况,用户需要直接调用开发板底层中的驱动实现特定功能,那么怎么实现?因为底层 Kernel 中都是用 C 编写的。Java 是没有办法直接调用底层的,那么这种情况下就需要用到 JNI。
  • 第二种情况,用户有现成的应用,但是都是基于 C 写的,那么想要尽快的完成任务,那么就需要用到 JNI。将 C 的程序移植到 JNI 中,然后制作好 API 接口,供上层 Java 的应用程序调用。
    当然,还有其它很多情况下都需要用到 JNI,例如你有 C 写的算法,以及一些 C 的动态库想直接调用等等,但是那不是属于纯粹的嵌入式 Android 了

2. 与 JNI 相关的文件

  • 如果需要使用 JNI 功能和 Kernel 通信,那么就需要有以下几个文件:

jni.h→com.topeet.ledtest.h
jni.c→com.topeet.ledtest.c
lib.so→libled.so
Android.mk→Android.mk
在这里插入图片描述

3. Java 程序调用 JNI 的方法和步骤

  • Java 应用调用库
    如下图所示,找到“MainActivity.java”文件,找到代码“System.loadLibrary(“led”);”这个代码就是包含了 led 库,也就是 C 的库。
    在这里插入图片描述
  • led.java
    led 类中包含了 Open、Close 以及 Ioctl,这里对应对底层字符驱动的打开、关闭以及输入数据的操作。
    在这里插入图片描述
    上面是属于本机文件的处理,下面介绍一下和 linux 相关的文件。
  • com.topeet.ledtest_led.h
    首先需要包含 jni.h 文件,实现 JNI功能必须包含这个头文件。然后就是大的红色方框中,这里需要通知 Java 程序,这里是 C 的程序,用户可以模仿者写。然后就是上图中 Open、Close 以及 Ioctl 的定义,这里也是有特定的格式要求。
    在这里插入图片描述
  • com.topeet.ledtest_led.c
    头文件"com_topeet_ledtest_led.h"上面已经分析过了,必须按照 jni.h 中定义的语法来写。包含了头文件“android/log.h”,这样我们在和开发板上调试的时候就可以使用 android 的 printk,方便调试。
    剩下的头文件全部是 linux 中标准的库文件,包括字符处理、报错、数学库以及调用设备节点需要的头文件等等。
    定义的三个 Open、Close 以及 Iotcl
    JNI 中的“Java_com_topeet_ledtest_led_Open”,主要功能是调用“/dev/leds”设备节点。
    JNI 中的“Java_com_topeet_ledtest_led_Close”,主要功能是释放“/dev/leds”设备节点。
    JNI 中的“Java_com_topeet_ledtest_led_Ioctl”,主要功能是向底层输入数据。

3.Android.MK 文件

源码还需要编译成 lib.so 库才能使用,编译成库就涉及到 Makefile 文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := led
LOCAL_SRC_FILES := com_topeet_ledtest_led.c
LOCAL_LDLIBS += -llog
LOCAL_LDLIBS +=-lm
include $(BUILD_SHARED_LIBRARY)
  • 解析一下这几行代码

1、LOCAL_PATH := $(call my-dir)

一个 Android.mk file 首先必须定义好 LOCAL_PATH 变量。它用于查找源文件。在这个例子中,宏函数‘my-dir’,由编译系统提供,用于返回当前路径(即包含 Android.mk file文件的目录)。

2、include $(CLEAR_VARS)

CLEAR_VARS 由编译系统提供((可以在 android 安装目录下的/build/core/config.mk文件看到其定义,为 CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk)),指定让 GNUMAKEFILE 为你清除许多 OCAL_XXX 变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES, 等等…),除 OCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个 GNU MAKE 执行环境中,所有的变量都是全局的。

3、LOCAL_MODULE := led

LOCAL_MODULE 变量必须定义,以标识你在 Android.mk 文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为’led’的共享库模块,将会生成“libled.so”文件(也可以直接使用 libled 命名好)。

4、LOCAL_SRC_FILES := com_topeet_ledtest_led.c

LOCAL_SRC_FILES 变量必须包含将要编译打包进模块中的 C 或 C++源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。

5、LOCAL_LDLIBS += -llog
6、LOCAL_LDLIBS +=-lm

编译模块时要使用的附加的链接器选项。使用“-l”前缀传递指定库的名字是有用的。
LOCAL_LDLIBS := -llog 表示告诉链接器生成的模块要在加载时刻链接到“/system/lib/liblog.so”
可查看 docs/STABLE-APIS.TXT 获取使用 NDK 发行版能链接到的开放的系统库列表。
这里表示生成的是动态库。

7、include $(BUILD_SHARED_LIBRARY)

会生成依赖关系,当库不存在时会去编译这个库。
这里主要介绍的是基于 C 的 Makefile 文件的编写,如果用户使用的是 C++代码,Makefile 文件中的变量以及宏定义就会有所不同

4. 安装 NDK 编译器以及编译 JNI 库文件

如何生成基于 Linux 的 libxx.so 库。如下图所示,最后用户需要放在“libs/armeabi”中的库文件。
在这里插入图片描述

  • 安装 NDK 编译器
  • 编译 Android 动态链接库
发布了29 篇原创文章 · 获赞 9 · 访问量 1889

猜你喜欢

转载自blog.csdn.net/m0_46291920/article/details/104510506
JNI