摘要
之前一直对NDK和JNI这两个玩意理解有点模糊,本文先分别介绍两者的相关概念,再介绍两者的使用的一般流程,最后介绍两者的联系。
本文主要介绍NDK开发时的一些环境配置,并提供了一个Java调用Native方法的简单实例。
JNI
简介
JNI,即Java Native Interface,是指Java本地接口。它定义Java与其他语言(如c、c++)之间的交互规范,依据这个规范就可以在Java代码中调用c、c++的方法,或者在c、c++代码中调用Java方法。
JNI是Java调用本地其他语言(如c、c++)的一种特性,是Java的概念,与Android无关。
为什么要有JNI
Java具有跨平台的特性,所以Java就面临着与当前平台(本地环境)交互能力弱的这个痛点,为了解决这一痛点,JNI就应运而生了,JNI出现的目的就是为了增强Java与本地交互的能力。
JNI开发的一般步骤
- 在Java中声明native方法(即需要调用的本地方法)
- 通过
javac
命令编译java源文件(得到.class文件) - 通过
javah
命令到处JNI的头文件(得到.h文件) - 利用c、c++根据3中得到的.h文件生成.c(或.cpp)文件,即native实现;
- 编译生成.so库文件;
- 在Java文件中加载so库文件,并调用native方法。
后面会给出具体的Demo
参考文档
NDK
简介
NDK,即Native Development Kit,是Android提供的一个原生开发工具包,NDK是Android开发中的一个概念,与Java并无直接关系。
为什么要有NDK
通过JNI介绍可以看出,如果Android开发中想调用本地方法,步骤比较繁琐,NDK的出现,解决了这一问题,可以快速开发c、c++动态库,并将生成的so文件一起打包到apk中(可以理解为一种半自动化工具)
特点
关于NDK的特点,用一张图来表示。
关于NDK,还有一些需要注意的地方:
- NDK可以直接将so文件打包至apk中,而JNI需要先生成so文件,然后将so文件放在指定的位置;
- NDK提供的库优先,不要滥用,一般用于算法效率问题和安全敏感问题;
- NDK提供了交叉编译其,用于生成不同CPU平台的动态库;
NDK开发的一般步骤
- 配置Android NDK开发环境;
- 创建Android项目,并与NDK进行关联;
- 在Android相关的类(Activity)中声明需要调用的native方法;
- 生成native的.h头文件;
- 依据头文件,编写其具体实现;
- 实现代码写好后,使用ndk build来编译生成so文件;
- 编译Android项目,实现Android调用本地代码。
参考文档
NDK和JNI的关系
NDK是Android平台上快速实现JNI的一种手段,JNI是Java实现的目的,即完成Java中调用本地代码。
JNI和NDK开发demo示例
由于NDK是达到JNI目的的一种方式,所以二者实现方式大体相同,区别就是生成.h文件以及so库的处理方式不同,所以这里主要给出NDK开发示例。
先说下我的环境配置
- Mac系统
- Android Studio 3.5
- JDK 1.8.1,已将JDK 的bin目录加入到PATH;
NDK开发demo示例
具体步骤
- 配置NDK开发环境
下载NDK,打开Android Studio ->Preferences->Appearance & Behavior->Android SDK,切换到SDK Tools,下载NDK开发工具;
创建Android项目,新建一个NdkDemo 项目,在src/main下新建目录jin和jinLibs,并在jni目录下新建JniTest.java文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11package cn.rainonth.ndkdemo;
/**
* @author 张豪成
* @date 2019-10-21 20:36
*/
public class JniTest {
public native static String get();
public native static int add(int a, int b);
}根据JniTest.java 生成jni头文件,有两种方案:
方法1:点击Build->Make Project,生成JniTest.class文件,然后找到生成的.class文件,利用javah命令生成;
方法2:利用Android External tools来自动化上面的命令。具体方法如下:
- Preferences->Tools->Exteranl tools,新建external tools,分别按下面的内容配置:
1
2
3
4
5Name:Generate Jni Header(可以随便填)
Description:生成JNI头文件(可以随便填)
Program:javah
Arguments:-v -jni -d $ModuleFileDir$/src/main/jni $FileClass$
Working Directory:$SourcepathEntry$- 配置好后保存;
- 找到刚新建的JniTest文件,右键->External tools->Generate Jni Header,即可在jin目录生成头文件了。
上面生成的头文件如下(
cn_rainmonth_ndkdemo_JniTest.h
):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class cn_rainmonth_ndkdemo_JniTest */
extern "C" {
/*
* Class: cn_rainmonth_ndkdemo_JniTest
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_rainmonth_ndkdemo_JniTest_get
(JNIEnv *, jobject);
/*
* Class: cn_rainmonth_ndkdemo_JniTest
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_cn_rainmonth_ndkdemo_JniTest_add
(JNIEnv *, jobject, jint, jint);
}编写JniTest.cpp文件
在jin目录新建JniTest.cpp文件,引入上面生成的头文件,开始编写c++文件内容,如下:1
2
3
4
5//
// Created by Randy on 2019-10-22.
//
/*
- Class: cn_rainonth_ndkdemo_JniTest
- Method: get
Signature: ()Ljava/lang/String;
*/
extern “C” JNIEXPORT jstring JNICALL Java_cn_rainmonth_ndkdemo_JniTest_get(JNIEnv *env, jobject clazz) {
return env->NewStringUTF(“Hello World from C”);
}
/*
Class: cn_rainonth_ndkdemo_JniTest
Method: add
Signature: (II)I
*/
extern “C” JNIEXPORT jint JNICALL Java_cn_rainmonth_ndkdemo_JniTest_add(JNIEnv *, jobject clazz, jint a, jint b) {
return a + b;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20我在编写的时候,发现没有代码补全,这里介绍下如何让代码补全,后面会介绍Android Studio 支持C++代码提示的方法,参见后面的补充。
5. 使用ndk build来编译项目,并生成so库
1. 编写Android.mk文件
Android.mk文件中定义了c++项目编译的一些配置,具体配置及代码如下:
```makefile
# 当前路径
LOCAL_PATH := $(call my-dir)
# 清除LOCAL_XXX变量
include $(CLEAR_VARS)
# 原生库名称
LOCAL_MODULE := jnitest-lib
# 原生代码文件
LOCAL_SRC_FILES =: JniTest.cpp
# 编译动态库
include $(BUILD_SHARED_LIBRARY)编写Application.mk
Application.mk用来指定生成的.so库的名称,以及支持的ABI类型,代码如下:
1
2
3
4
5# 原生库名称
APP_MODULES := jnitest-lib
# 指定机器指令集,armeabi mips mips64不再支持了
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64生成so库
在jni目录上右键,执行External Tools的ndk-build,就可以在main下面生成libs和obj目录了,libs目录下就是我们想要的so文件内容- 使用
项目的MainActivity如下:
1 | package cn.rainmonth.ndkdemo; |
补充
java 方法和Native方法关联方法
有静态关联和动态关联两种方法,详细内容参考:Android JNI初步Java方法和native方法关联
Android Studio开启c++代码补全
- 先在jni目录编写Android.mk文件,内容同上面的
Android.mk
; - 然后Android Studio,File->Link C++ Project with Gradle,在弹出的配置框中,Build System选择ndkBuild,Project Path 选择刚才新建的Android.mk文件即可。这样就把C++代码和Gradle连接起来了。
- 选择NDK编译,通过Android Studio的 Build菜单下的MakeProject即可完成C++和Java的关联;
- 打包so文件方式,切换到jni目录,直接运行命令:ndk-build
注意:上面的操作其实相当于在app moudle的build.gradle的android闭包中添加如下代码:
1 | // 相当于执行Link C++ Project with Gradle |
总结
这只是NDK开发的第一步,其中External Tools相关的操作能帮我们省去不少时间,后面讲通过实际项目来进行NDK开发的实践。