版权说明:本文为 开开向前冲 原创文章,转载请注明出处;
注:限于作者水平有限,文中有不对的地方还请指教。
文章参考:JNI参考手册 ,Android官方NDK文档
一:概述
Android NDK 是一种允许将“本地代码”嵌入到 Android 应用中的一种工具,能在Android 应用中使用本地代码;
在Android 开发中我通常将Java 称为开发语言,而将C/C++ 语言称为本地语言,使用Java 能开发一个完整的APP,那使用纯C/C++ 或者Java+C/C++ 是否能开发APP 呢???
———— 答案是肯定的,实际中也是有很多APP 都是采用Java+C/C++ 的来实现;那我们是通过什么方式去实现的呢? ———— NDK
在学习NDK 之前我们我们需要了解JNI(Java Native Interface):JNI 是 Java 和 C++ 组件用以互相沟通的接口。很多和平台相关特性相关的库和很多优秀的库都是通过C/C++ 编写的,例如Android 系统中底层电源管理,FFMPEG ······;为了更好利用硬件特性和目前已经存在的库,我们就需要JNI 来实现Java 与C/C++通信;
JNI是什么?
JNI(Java Native Interface)意为Java本地接口,它允许Java代码和其他语言写的代码进行交互;
推荐一本JNI参考手册
NDK是什么?
Android NDK(Native Development Kit )是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。
为什么要用NDK?
1、安全性,现在逆向很风行,纯Java的apk容易被反汇编后拿到源代码,使用C/C++会好很多。
2、效率,目前很多优秀的库都是通过C/C++开发,我们可以复用这些库,减少重复工作量。
3、平台特性,很多和平台硬件相关的功能都是需要底层驱动来实现功能,而我们的Java 目前还没办法做到。
JNI和NDK的区别?
从工具上说,NDK其实多了一个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置。从编译库说,NDK开发C/C++只能能使用NDK自带的有限的头文件,而使用JNI则可以使用文件系统中带的头文件。编写方式一样。
JNI 开发步骤:
1、编写声明了native方法的Java类
2、将Java源代码编译成class字节码文件
3、用javah -jni命令生成.h头文件(javah是jdk自带的一个命令,-jni参数表示将class中用native声明的函数生成jni规则的函数)
4、用本地代码实现.h头文件中的函数
5、将本地代码编译成动态库(windows:***.dll**,linux/unix:***.so**,mac os x:***.jnilib**)
6、拷贝动态库至 java.library.path 本地库搜索目录下,并运行Java程序
下图是从《The Java™ Native Interface Programmer’s Guide and Specification》书中截取
下面将使用上述步骤编写一个HelloWorld程序:
1:编写声明了native 方法的Java类:HelloWorld.java
class HelloWorld {
private native void print();
public static void main(String[] args) {
new HelloWorld().print();
}
static {
System.loadLibrary("HelloWorld");
}
}
2:使用javac编译器编译Java源代码为class 文件:
javac HelloWorld.java;
3:使用javah工具将第二步生成的".class"文件生成JNI-Style的".h"文件:
javah -jni HelloWorld
该命令将生成HelloWorld.h文件,文件中声明的Java_HelloWorld_print方法就是HelloWorld.java 中print方法的本地声明;该文件中最重要的内容如下:
JNIEXPORT void JNICALL
Java_HelloWorld_print (JNIEnv *, jobject);
4:编写".C"程序实现上述".h" 文件:HelloWorld.c
#include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
5:将本地代码编译成动态库:
编译环境和工具可以参考一下编译环境配置,目前基本的IDE 都支持编译动态库,例如Android Studio
因为我是Win32操作系统,我的编译命令:
cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll
编译出的动态库文件需要放在一定的位置才能被程序识别,一般都是放到"PATH"环境变量中的某个目录,
只不过目前的JDK 版本都支持在运行时手动指定环境变量;如:
java -Djava.library.path=. HelloWorld
"-D"参数和"java.library.path=. "表示设置Java 虚拟机从当前目录搜索本地库文件;
6:运行程序;
java HelloWorld
你的控制台是否有显示 Hello World!
至此一个来自于C/C++的"HelloWorld"成功的在Java中代码中得到运行;
在JNI中需要清楚的概念:JNIEnv,jobject,java数据和JNI 数据映射,JNI数据与本地C/C++数据转换,java 方法的本地签名等
JNI参考手册中对JNIEnv 描述;
JNIEnv与JavaVM
JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
JNIEnv 与 JavaVM : 注意区分这两个概念;
-- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
-- JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
JNIEnv 作用 :
-- 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
-- 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
JNIEnv 体系结构
线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它
线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;
JNIEnv 不能跨线程 :
-- 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;
-- 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;
JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针, 指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组,
这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;
注意:JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,
传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。
jobject
即Java 对象在JNI 层的代表;
对于更多JNI 开发的知识,相信大家看完JNI参考手册 都会明白;
NDK
Android NDK 是一种允许将“本地代码”嵌入到 Android 应用中的一种工具,能在Android 应用中使用本地代码;
//关于ndk-build,CMake;
未完待续。。。。。。
参考资料:深入理解JNI