平时在开发中,或多或少都会用到JNI方面的技术,比如我们项目中,消息的加密和解密就是通过C来实现的,然后打包为.so动态库,并提供Java接口供应用层调用,这么做的目的主要就是为了提供应用的安全性,防止被反编译后被分析加密的逻辑。
接下来就要介绍JNI和NDK的区别,怎样创建一个项目开发JNI。
[TOC]
JNI与NDK
JNI
Java Native Interface
,即Java本地接口,使用JNI可以使得Java与本地其他类型语言(如C、C++)交互。
JNI是 Java调用Native语言的一种特性,JNI是属于Java的,与 Android无直接关系。
NDK
Native Development Kit
,是Android的一个工具开发包。
NDK是属于Android的,与Java并无直接关系,只是通过NDK可以快速方便的使用JNI,开发C、C++动态库。
-
JNI与NDK的区别,具体可以参考该文章
开发JNI
开发JNI,一般都有两种途径:
- Java项目直接引用生成好的动态库,动态库的具体C、C++代码在其他地方编写变生成动态库(常见的为在Window平台上的VS编写,在Mac平台上的Xcode编写,或者直接用命令行编写),引用第三方库大多就是采用这种方式
- Android Studio项目中通过NDK实现JNI
Java项目调用Xcode编译动态库
创建Xcode项目,选择Library,选择为C++库
先删除多余文件和代码,引入
jni.h
头文件设置
jni.h
头文件的路径,即Java环境的配置路径分别添加进include目录和include/darwin目录
编译运行就不会报找不到头文件的错误了,同时,可以看到生成了动态库libStudyNdk.dylib,记录动态库的路径
/Users/guidongyuan/Library/Developer/Xcode/DerivedData/StudyNdk-bwbgrzjykrevnaeyglsvxjelvnth/Build/Products/Debug/libStudyNdk.dylib
,接下来会使用到。编写Java代码调用native代码
调用native代码,可以直接使用Test包中的测试类来进行测试,只要是Java类就可以了,可以在Android Studio中创建Java目录。上面介绍jni也说到,jni是与Java有关的,与Android没关系,只要是Java项目都可以使用到。
-
可以参考该文章创建Java项目
在Android Studio创建Java项目
jnilib
,修改MyClass.java
类,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.guidongyuan.jnilib;
public class MyClass {
static {
// 加载动态库
System.load("/Users/guidongyuan/Library/Developer/Xcode/DerivedData/StudyNdk-bwbgrzjykrevnaeyglsvxjelvnth/Build/Products/Debug/libStudyNdk.dylib");
}
public static void main(String[] args){
new MyClass().test();
}
/**
* native方法
*/
native void test();
}System.load(String path)
加载动态库,传入动态库的绝对路径,另外还有
System.loadLibrary()
方法,传入为动态库的名字,不需要动态库的后缀。
-
在XCode中完成native代码
方法名格式为
Java_包名_类名_方法名(JNIEnv,jobject){}
,编译运行生成动态库1
2
3
4
5
6
7
8#StudyNdk.cpp
extern "C"
void Java_com_guidongyuan_jnilib_MyClass_test(JNIEnv *env, jobject){
printf("接收到Java的调用");
}方法太长的话,也可以通过
javah
命令生成.h
文件,要注意当前所处路径,否则会报找不到类的错误1
2
3
4
5
6
7在包名的上一个路径
➜ java pwd
/Users/guidongyuan/code/Android/ndk/StudyNdk/jnilib/src/main/java
➜ java javah -o MyClass.h com.guidongyuan.jnilib.MyClass
可以看到多了一个.h文件
➜ java ls
MyClass.h com打开
MyClass.h
文件,则可以拷贝其方法名字1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class com_guidongyuan_jnilib_MyClass */
extern "C" {
/*
* Class: com_guidongyuan_jnilib_MyClass
* Method: test
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_guidongyuan_jnilib_MyClass_test
(JNIEnv *, jobject);
}运行Java代码
可以看到终端的输出,成功调用到native的代码
dylib动态库
Windows系统的动态库是DLL文件,Linux系统是so文件,macOS系统的动态库则使用dylib文件作为动态库。
可以通过file 文件
查看生成的文件属性
1 | ➜ file libStudyNdk.dylib |
要注意的是,Mac平台上的动态库,和Android平台上使用的.so
平台上不一样的,就算是在Mac平台上,编译为后缀名为.so
,不过不是生成arm架构编译的话,直接放在apk包中去调用,同样会出现异常,接下来的文章会讲到。
Android Studio编写NDK代码
向Android Studio的项目添加 C 和 C++ 代码实现JNI,需要先下载 NDK 和构建工具,然后,再创建支持C/C++的项目。
如果是新创建的项目,非常简单,直接勾选Include C++ Support就可以了;
旧项目的话,步骤比较麻烦,需要以下三个步骤
向您的项目添加 C 和 C++ 代码详细步骤可以参考官方的链接
- 创建新的原生源文件
- 创建 CMake 构建脚本
- 将 Gradle 关联到您的原生库
创建支持 C/C++ 的新项目
创建新项目后,编译运行,可以看到代码结构如下,多了两个文件,如果想添加新的jni方法,就可以在native-lib.cpp中增加
编译运行,界面如下,第一个hello world就完成了
通过解压应用包也可以看到,导入了libnative-lib.so库
目录介绍
CMakeList.txt
在该文件中,采用Cmake语法规则,自动生成相关的makefile文件,并编译出动态库或者静态库。接下来的文章会具体介绍到。
builde.gradle
查看gradle文件可以看到多出来externalNativeBuild
方法,第一个为设置编译的一些属性,另外一个设置CmakeLists.txt配置文件的路径。
1 | android { |
连接模拟器(x86架构)
连接小米4(armeabi-v7a架构)
连接小米6(arm64-v8a架构)