NDK 入门指南

前言

众所周知,Android 的 SDK 基于 Java 实现,这意味着基于 Android SDK 进行开发的第三方应用都必须使用 Java 语言。但这并不等同于“第三方应用只能使用 Java ”。在 Android SDK 首次发布时,Google 就宣称其虚拟机 Dalvik 支持 JNI 编程方式,也就是第三方应用完全可以通过 JNI 调用自己的 C 动态库,即在 Android 平台上,“ Java+C ”的编程方式是一直都可以实现的。于是 NDK 就应运而生了,2009年6月26日,Google Android发布了NDK,下载链接

1 NDK 的基本概念

1.1 NDK 的定义

NDK 即 Native Development Kit,是 Android 中的一个开发工具包,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。NDK是我们实现 Java 与 Native 进行交互的一种方式。

1.2 NDK 的作用

可以快速开发 C、 C++ 的动态库,并自动将 so 和应用一起打包成 APK。即可通过 NDK使 Java与 Native 代码(如 C、C++)交互。

1.3 NDK 的优点

(1)允许程序开发人员直接使用 C/C++ 源代码,极大的提高了 Android 应用程序开发的灵活性。
(2)跨平台应用移植、使用第三方库。如:许多第三方库只有 C/C++ 语言的版本,而 Android 应用程序需要使用现有的第三方库,如 FFmpeg、OpenCV 等,则必须使用 NDK。
(3)采用C++代码来处理性能要求高的操作,提高了Android APP的性能。
(4)安全性高。因为 apk 的 Java 层代码很容易被反编译,而 C/C++库反汇编难度较大。

1.4 NDK 与 SDK 的关系

在 Android 开发中,最常用的是 SDK,那么 SDK 与 NDK 的关系是什么呢?

  • 在 SDK 中,我们使用 Java 来进行开发,而在 NDK 中,我们使用 C++来进行开发。
  • SDK 支持了 Android 开发中的大部分操作,如 UI 展示,用户与手机的交互等,主要是支持了 Android APP 开发的基础功能。NDK 支持了一些复杂的,比较高级的操作,如音视频的解析,大量数据的运算,提高 Android 游戏的运行速度等,主要是 Android APP 的一些高级功能。

所以: NDK 与 SDK 是并列关系,NDK 是 SDK 的有效补充

在这里插入图片描述

2 NDK 的组成部分

2.1 JNI

2.1.1 JNI 的定义

JNI 即 Java Native Interface,是一种编程框架,使得 Java 虚拟机中的 Java 程序可以调用本地应用或库,也可以被其他程序调用。 本地程序一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。当然,JNI 并非 Android 中提出的概念,而是在 Java 中本来提供的。
上文中,NDK 也是支持 Java 代码与 Native 代码的交互,那他们之间有什么区别呢?
实际上,JNI 是一个编程框架,是一个抽象的东西,NDK 是一个工具包。
所以:NDK 是在Android中实现 JNI 的一种方式

2.1.2 JNI 的优点

有些事情 Java 无法处理时,JNI 允许程序员用其他编程语言来解决,例如,Java 标准库不支持的平台相关功能或者程序库。也用于改造已存在的用其它语言写的程序,供 Java 程序调用。

2.1.3 使用 JNI 的步骤

(1)使用 native 关键字定义Java方法(即需要调用的 native 方法)
(2)使用 javac 编译上述 Java 源文件 (即 .java 文件)最终得到 .class文件
(3)通过 javah 命令编译 .class 文件,最终导出 JNI 的头文件(.h文件)
(4)使用 C/C++实现在 Java 中声明的 native 方法
(5)编译 .so 库文件
(6)通过 Java 代码加载动态库,然后调用 native 方法

实际上2、3、4、5步骤的目的就是生成 .so 文件。
所以上面的步骤可以归纳为三步:
(1)声明 native 方法
(2)实现 native 方法,生成 .so 文件
(3)加载 .so 文件,调用 native 方法

2.2 .so 和 .a 文件

上面我们已经说到,JNI 支持了Java代码和Native代码的互相调用。
但是 JNI 是直接调用 Java 代码和 Native 代码吗?实际上,JNI 是调用 Java 代码和 Native 代码编译后的 .so 和 .a 文件来实现了 Java 代码和 Native 代码的交互。
那么 .so 和 .a 文件是什么呢?下面列出了 .so 和 .a 文件的一些定义:

  • 动态链接库 (.so 后缀):运行时才动态加载这个库。动态链接库,也叫共享库,因为在 NDK 中用 shared 来表示是动态库。
  • 静态链接库 ( .a 后缀):在编译的时候, 就把静态库打包进 APK 中。
    • 缺点 : 使用静态库编译, 编译的时间比较长,同时也使得APK比较大。
    • 优点 : 只导出一个库, 可以隐藏自己调用的库。
  • .so 和 .a 本质上都是二进制文件。
  • 每个 CPU 系统只能使用相对应的二进制文件,即他们不像 jar 包一样,所有的 CPU 系统都可以使用一个jar包,.so 和 .a 每个系统必须使用自己的,不能使用别的,如 armeabi的 .so 文件,不能被应用到x86中。

2.3 ABI

ABI 即 Application Binary Interface。我们上面说了,每个CPU系统只能使用相对应的二进制文件,不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。简而言之:ABI 定义了二进制文件是怎么运行在对应的 CPU 中的

那么,有哪些 CPU 架构呢?

Android 平台,其支持的设备型号繁多,单单就设备的核心 CPU 而言,都有三大类:ARM、x86 和 MIPS。在 NDK r17 以后,NDK 不在支持32位、64位的 MIPS 和 ARM v5(armeabi)。而 ARM 和 x86 又各分为32位和64位两种,一共分为4种。

我们可以简单的认为:ARM 主要应用于手机中,x86 主要应用于 PC 中。Android 中使用x86主要是因为:PC 上的模拟器运行需要 x86 的。

现在我们大致了解了 Android 中常用的 CPU 架构,而且我们知道,ABI 定义了二进制文件是怎么在 CPU 中运行的,那么我们可以知道,每一个 CPU 架构必定有一个相对应的 ABI。
上面我们已经知道了有四种,那么 ABI 也有四种,他们分别是:armeabi-v7aarmeabi-v8ax86x86_64

ABI 对应的 CPU 架构 应用
armeabi-v7a ARM 32位 手机
armeabi-v8a ARM 64位 手机
X86 X86 32位 PC
X86_64 X86 64位 PC

NDK 编译实际上默认编译出所有系统的文件,但是有时我们只需要使用指定的系统,我们就可以指定编译什么系统,减少二进制文件,避免我们不会使用到的二进制文件被打包到 apk 中。我们可以使用下面的代码来指定我们要编译什么 CPU 架构的二进制文件:

//在module的build.gradle中
android {
    
    
    defaultConfig {
    
    
        ndk {
    
    
            abiFilters 'armeabi-v7a', 'x86'
        }
    }
}

2.4 编译工具

2.4.1 本机编译工具

我们已经知道,每个系统只能使用自己系统的二进制文件,本机编译工具正是编译出本机系统可以使用的二进制文件。在 Android 中可以使用的本机编译工具有两种:ndk-buildCMake。这两种方式与 Android 代码和 c/c++ 代码无关,只是不同的构建脚本和构建命令。

2.4.2 交叉编译工具

与本机编译对应的,有时我们需要编译出其他系统的二进制文件,如我们在 PC 上写 Android 文件,那么我们 PC中就需要编译出 Android 中可以运行的二进制文件。交叉编译工具,又叫交叉编译链( toolchain)。在 NDK 中,交叉编译工具主要有两种:clanggcc。在 NDK r17c 以后默认使用 clang。

猜你喜欢

转载自blog.csdn.net/hello_1995/article/details/108784375
ndk