NDK是一系列工具的集合,全称为Android Native Development Kit,用于帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的;
1. 方便不同平台之间的代码移植;
2. 方便进行库的重复使用;
3. 在某些情况下提性能,特别是像游戏这种计算密集型应用;
4. 使用第三方库,现在许多第三方库都是由C/C++库编写的,比如Ffmpeg这样库;
5. 不依赖于Dalvik Java虚拟机的设计;
6. 代码的保护,由于APK的Java层代码很容易被反编译,而C/C++库反编译难度大;
JNI负责Java与C/C++进行互相操作,NDK提供工具方便在Android平台使用JNI;
JNI提供一些列的接口,允许Java类与C/C++等本地编辑语言(在JNI中,这些语言被称为 本地语言)编写的应用 程序、模块 、库进行交互操作。比如,在Java类中使用C语言库中的函数或在C语言中使用 Java类库,都需要借助JNI;
Android NDK是一个开发工具集,提供一系列工具快速开发C/C++的动态库,并能自动将 .so/.dll 和 Java 应用一起打包到Apk;NDK提供工具可以方便JNI调用C/C++,而且提供了交叉编译器可以修改.mk文件生成特定CPU平台的动态库,并能将so和java应用一起打包到apk中;
接下来以Ubuntu虚拟机为平台,介绍如何安装NDK开发环境;
下载地址:NDK 下载 | Android NDK | Android Developers
下载完后解压
1. 打开终端,在当前home目录下执行 sudo gedit .bashrc,在打开的文件的最后位置输入如下内容,并保存退出;
# set NDK env
NDKROOT=/media/zhuziwen/code/Myfile/NDK/android-ndk-r23b-linux
export PATH=$NDKROOT:$PATH
2. 执行 source .bashrc,使配置的环境变量生效;
接下来将讲述如何通过NDK完成C语言动态链接库的生成,再通过JNI来调用该动态库返回运算结果;
主要工作是:编写Java代码,使用native声明需要本地实现的方法add;通过javah -jni命令生成带有JNI样式的头文件;使用C代码实现该函数并编写相应的编译规则文件Android.mk;最后由ndk-build命令生成动态链接库,并将其打包到应用程序。接下来将描述具体步骤:
源代码功能:定义整型a作为保存AddNum反馈的结果,并最终显示在TextView上;
1. 代码第16行的add方法在使用NDK开发的.so动态链接库中完成;
2. Java源代码中native声明add为本地方法,比如使用C/C++实现;
3. 一般使用static块加载动态库,该块中使用System.loadLibrary()加载库,myjni为动态库的名字;
Java代码如下:
1. com.android.ndkdemo.MainActivity.java
package com.android.ndkdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int a = add(5,4); //需要相加的两个数字
tv1 = findViewById(R.id.tv);
tv1.setText("a = " + a); //在屏幕上显示a的结果
}
public native int add(int a, int b);
static
{
//装载lib*.so文件
System.loadLibrary("NativeAdd");
}
}
1. 拷贝 jni.h 头文件
(1)生成头文件之前,需要从Linux系统安装的JDK目录下将 jni.h 文件拷贝到当前NDK根目录;
(2)先要查找Linux系统当前安装的JDK目录,查找方法参考如下文章;
查找JDK目录:在linux中查看jdk的版本以及安装路径-CSDN博客
(3)在你的JDK目录的include目录下有一个jni.h的文件,将其复制到NDK根目录下即可;
2. 生成 com_android_ndkdemo_MainActivity.h 头文件
进入NDK根目录下,输入命令javah -jni com.android.ndkdemo.MainActivity,生成JNI样式的头文件,即com_addnum_AddNum.h,该文件中含有JNI函数的声明(这里属于静态注册);
com_android_ndkdemo_MainActivity.h代码内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_android_ndkdemo_MainActivity */
#ifndef _Included_com_android_ndkdemo_MainActivity
#define _Included_com_android_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_android_ndkdemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
注意点:
(1)生成JNI头文件后,记得把其中的 #include
jint JNICALL Java_com_android_ndkdemo_MainActivity_add (JNIEnv *, jobject, jint, jint)即是本地方法中需要实现的方法,需要保持方法名一致,否则将导致错误。native对应的函数名要以Java_开头,后面分别跟着com_android_ndkdemo(包名)、MainActivity(类名)、add(函数名)。
关于JNI基本原理和函数结构,见博客:https://blog.csdn.net/qq_41739313/article/details/121947136
(1)在根目录下(即NDK目录)编写C代码AddNum.c,直接通过return a+b实现加法功能;
#include "com_android_ndkdemo_MainActivity.h"
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
return a + b;
}
(2)编写Android.mk,代码如下:
LOCAL_PATH := $(call my-dir) # 获取当前路径,并保存在LOCAL_PATH中
NDK_PROJECT_PATH := .
include $(CLEAR_VARS) # 由编译系统提供,GNU MAKEFILE清除LOCAL_XXX变量,除了LOCAL_PATH以外
LOCAL_MODULE := NativeAdd # 指定编译出的库名
LOCAL_SRC_FILES := AddNum.c # 指定编译源文件
include $(BUILD_SHARED_LIBRARY) # 指定编译成动态链接库,BUILD_STATIC_LIBRARY为静态库
说明:
参考:Android.mk 用法解析2_09的专栏-CSDN博客_call my-dir
(1)在NDK根目录下创建Application.mk文件,内容如下:
APP_BUILD_SCRIPT := Android.mk # 指明用于构建Android项目的mk文件的位置
APP_PLATFORM := android-17 # 设置使用的ndk版本,使其与本地下载的ndk版本保持一致
APP_ALLOW_MISSING_DEPS := true # 与APP_PLATFORM := android-17一起起作用
注意点:
不加Application.mk会出现如下报错 :
(1)不加APP_BUILD_SCRIPT := Android.mk,会导致找不到Android.mk,具体报错信息如下:
Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
/media/zhuziwen/code/Myfile/NDK/android-ndk-r23b-linux/android-ndk-r23b/build/core/add-application.mk:88: *** Android NDK: Aborting... . Stop.
(2)不加 APP_PLATFORM 和 APP_ALLOW_MISSING_DEPS ,会出现如下报错:
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
在终端,进入NDK根目录,输入 ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk 命令,即可生成动态链接库,该命令执行过程中,会根据Android.mk中规定的编译规则将AddNum.c编译成libNativeAdd.so。
如果编译成功,会在NDK根目录下生成libs和obj目录,在libs目录中有几个子目录,每个子目录对应一个平台,在其中都有一个libNativeAdd.so的动态链接库文件,将其复制到NDK根目录下;
打开AVD模拟器或者把apk安装到手机上,就可以看到通过调用本地方法实现加法功能,输出结果正确;
本节主要讲解在Linux环境下如何使用ndk-build构建工具来进行NDK开发,以及ndk-build构建工具在Android Stuido中的快捷工具配置。
关于其中涉及到的JNI的相关知识与原理,见博客:https://blog.csdn.net/qq_41739313/article/details/121947136
在SDK Tools中安装NDK开发环境(File > Settings > Appearance & Behavior > System Settings > Android SDK > SDK Tools):
(1)正常新建一个Android空项目;
(2)进入project显示模式,在src -> main下创建文件夹jni,用于编写原生代码;
在Android Studio中,可以将 javah -jni 命令和 ndk-build 命令配置成快捷键,博主未配置成功,感兴趣的同学可以参考Android NDK开发(二) 使用ndk-build构建工具进行NDK开发 - 简书
本文采取直接在Android Studio界面下方的Terminal中执行相关命令的方式,见后文;
在java目录下com.android.ndkdemo包下创建并编辑Java文件;
package com.android.ndkdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int a = add(5,4); //需要相加的两个数字
tv1 = findViewById(R.id.tv);
tv1.setText("a = " + a); //在屏幕上显示a的结果
}
public native int add(int a, int b);
static
{
//装载lib*.so文件
System.loadLibrary("NativeAdd");
}
}
(1)cd app/src/main/java
(2)javah -jni com.android.ndkdemo.MainActivity 该命令结束后,就在java目录下生成了头文件com_android_ndkdemo_MainActivity.h,内容如下,不用再修改jni.h了;
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_android_ndkdemo_MainActivity */
#ifndef _Included_com_android_ndkdemo_MainActivity
#define _Included_com_android_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_android_ndkdemo_MainActivity
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
(1)在app/src/main/jni目录下新建AddNum.c文件,内容如下,与上一节一致;
#include "com_android_ndkdemo_MainActivity.h"
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
return a + b;
}
(2)在app/src/main/jni目录下新建Android.mk文件(jni目录上鼠标右键,New -> C/C++ File),内容如下;
#include "com_android_ndkdemo_MainActivity.h"
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
return a + b;
}
(1)在app/src/main/jni目录下新建Application.mk,内容如下;
APP_BUILD_SCRIPT := Android.mk # 指明用于构建Android项目的mk文件的位置
APP_PLATFORM := android-17 # 设置使用的ndk版本,使其与本地下载的ndk版本保持一致
APP_ALLOW_MISSING_DEPS := true # 与APP_PLATFORM := android-17一起起作用
(1)将 com_android_ndkdemo_MainActivity.h 头文件拷贝到jni目录;
(2)cd ../jni
(3)ndk-build NDK_APPLICATION_MK=Application.mk
(4)ndk-build执行后会在jni所在的同级目录生成一个libs文件,其中每个子文件夹下都有一个libNativeAdd.so动态库文件,将其拷贝到java/com.android.ndkdemo目录下;
这个时候连接手机进行测试,就能看到 界面显示 "a=9"。
基础
| Android NDK开发之JNI基础篇
实践
| JNI与NDK编程知识基础详解
| NDK 编译的三种方式
| 在 NDK 开发中引入第三方静态库和动态库
| NDK开发中Native与Java交互
| Android NDK开发(二) 使用ndk-build构建工具进行NDK开发 - 简书
进阶与补充
| NDK开发中的几个重要知识点
| JNI与NDK编程函数注册与C++调用Java详解
| JNI与NDK编程常用方法史上最全总结和详解
| Android NDK开发基础之C语言的内存管理
文章链接总结
| JNI与NDK编程(基础到精通)最全总结