Android NDK开发

参考:

Android JNI(一)——NDK与JNI基础

NDK 入门指南

Android NDK开发(一)  入门

向您的项目添加 C 和 C++ 代码

https://developer.android.com/studio/projects/add-native-code#configure-gradle

一、概述

原生开发工具包 (NDK:Native Develop Kit) 是一组可让您在 Android 应用中利用 C 和 C++ 代码的工具。 可用以从您自己的源代码构建,或者利用现有的预构建库。

NDK 不适用于大多数初学的 Android 编程者,对许多类型的 Android 应用没什么价值。 因为它不可避免地会增加开发过程的复杂性,所以通常不值得使用。 但如果您需要执行以下操作,它可能很有用:

  • 从设备获取卓越性能以用于计算密集型应用,例如游戏或物理模拟。
  • 重复使用您自己或其他开发者的 C 或 C++ 库。

二、JNI

  • JNI:全称为Java Native Interface,即Java本地接口。Java调用C++的规范,Java语言中本身就存在的。
  • 为什么需要JNI:通过JNI可以实现java代码与C/C++代码的交互。
  • JNI的三个角色:C/C++代码,本地方法接口类,Java层中具体业务类,如下图:

  • JNI调用流程如下图:

  • JNI实现步骤:
  1. 在java中声明native方法
  2. 编译Java源文件javac得到.class文件
  3. 通过javah -jni命令导出JNI的.h头文件
  4. 使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)
  5. 将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib)
  6. 通过Java命令执行Java程序,最终实现Java调用本地代码。

三、so文件

从上图这个Android系统框架来看,我们上层通过JNI来调用NDK层的,使用这个工具可以很方便的编写和调试JNI的代码。因为C语言的不跨平台,在Mac系统的下使用NDK编译在Linux下能执行的函数库——so文件。其本质就是一堆C、C++的头文件和实现文件打包成一个库。目前Android系统支持以下七种不用的CPU架构,每一种对应着各自的应用程序二进制接口ABI:(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。对应关系如下:

  • ARMv5——armeabi
  • ARMv7 ——armeabi-v7a
  • ARMv8——arm64- v8a
  • x86——x86
  • MIPS ——mips
  • MIPS64——mips64
  • x86_64——x86_64

四、NDK的实现方式

  1. ndk-build 形式:Android Studio 2.2之前的模式
  2. CMake 形式:CLion C/C++编辑器; AS2.2之后整合了CLion代码, AS就支持了CMake形式的NDK开发

Android Studio 用于构建原生库的默认工具是 CMake。由于很多现有项目都使用构建工具包编译其原生代码,所以Android Studio 还支持 ndk-build。应该可以说是两种不同的编译工具而已,具体的使用方法都是类似的,如果是新建项目建议使用CMake方式。

CMake介绍:

  1. CMakeList.txt 是脚本文件,需要指定包含哪些源代码
  2. 可以写一些条件语句,实现不同的代码包含
  3. 内部说明:add_library 表示编译一个代码库, 内部包含了代码库的名称, 以及源代码有哪些

这两种方式具体的实现原理还没弄清楚,之后有研究会再来补充,具体的实现demo可以看下面。

五、demo编写

环境配置

下载NDK和CMake,直接在SDK中进行勾选;

ndk-build方式实现

1. java中声明native方法

2. java文件编译获取 .class文件,再编译获取 .h头文件

一种方式是settings中配置external Tools工具,配置完后使用很方便,但是尴尬的是我这里没有配置成功。。。

另一种方式直接命令行里面进行编译,就是自动和手动的区别

1. 先编译成class文件,直接在文件根目录中运行 javac 文件名

2. 编译class文件获取头文件,回到java目录下,输入命令javah -classpath . -jni com.wangsu.huangm2.testexception.native_error.NDKTools

3. 新建JNI文件夹,将刚才的编译成功的 .h文件移到该目录下

3. 实现具体的native方法

在jni目录下新建C/C++文件,C/C++不了解,代码也是参考他人实现的。

实现java中的native方法,具体的方法名称可以复制 .h文件中声明的。

.h文件中的方法名称如下:

这里有一点需要注意的是其实并不一定要编译成头文件,只要有实现具体的native方法即可并且方法名要对应,例如

JNIEXPORT jstring JNICALL Java_com_wangsu_huangm2_testexception_NDKTools_getStringFromNDK
  • jstring:返回值类型,代表java 的String类型
  • Java_com_wangsu_huangm2_testexception:是包名
  • NDKTools:类名
  • getStringFromNDK:Java中的native方法名

4. 新建Android.mk文件

同样在jni目录下,添加一个Android.mk文件,内容如下:

具体参数含义:

  • LOCAL_PATH := $(call my-dir):每个Android.mk文件必须以定义开始。它用于在开发tree中查找源文件。宏my-dir则由Build System 提供。返回包含Android.mk目录路径。
  • include $(CLEAR_VARS)CLEAR_VARS变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx。例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等。但不是清理LOCAL_PATH。这个清理是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能便面相互影响。
  • LOCAL_MODULE := ndkdemotest-jni:LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System 会自动添加适当的前缀和后缀。例如,demo,要生成动态库,则生成libdemo.so。但请注意:如果模块名字被定义为libabd,则生成libabc.so。不再添加前缀。
  • LOCAL_SRC_FILES := ndkdemotest.c:这行代码表示将要打包的C/C++源码。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++ 源码的扩展名为.cpp。
  • include $(BUILD_SHARED_LIBRARY)BUILD_SHARED_LIBRARY是Build System提供的一个变量,指向一个GUN Makefile Script。它负责收集自从上次调用include $(CLEAR_VARS)后的所有LOCAL_xxxxinx。并决定编译什么类型
    • BUILD_STATIC_LIBRARY:编译为静态库
    • BUILD_SHARED_LIBRARY:编译为动态库
    • BUILD_EXECUTABLE:编译为Native C 可执行程序
    • BUILD_PREBUILT:该模块已经预先编译

5. 修改build文件

  • 如果有‘armeabi’类型,编译可能会报错,是因为NDK r17已经移除了 ARMv5 (armeabi), MIPS(mips), 和 MIPS64(mips64)这三种类型。
  • 如果是CMake编译工具,则将ndkBuild换成cmake同时将Android.mk文件换成CMakeLists.txt文件
  • 还记得一开始java文件中的 System.loadLibrary("myjni"); 这句话不能忘记,否则无法调用成功,其中的”myjni“就是我们在Android.mk文件中设置的native库的名称。

6. 运行结果

7. 运行后生成的so文件

在build/intermediates文件夹目录下

 

CMake方式进行实现

1. 创建项目

勾选Include C++ Support复选框

在向导的 Customize C++ Support 部分,您可以使用下列选项自定义项目:

  • C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。
  • Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
  • Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

2. 项目结构介绍

与平常我们新建的安卓项目不太一样的是,它已经事先添加了一个简单的例子,多了一个cpp目录和CMakeLists.txt文件,具体目录如下:

  1. 在 cpp 组中,您可以找到属于项目的所有原生源文件、标头和预构建库。对于新项目,Android Studio 会创建一个示例 C++ 源文件 native-lib.cpp,并将其置于应用模块的 src/main/cpp/ 目录中。本示例代码提供了一个简单的 C++ 函数 stringFromJNI(),此函数可以返回字符串“Hello from C++”。要了解如何向项目添加其他源文件,请参阅介绍如何创建新的原生源文件的部分。
  2. 在 External Build Files 组中,您可以找到 CMake 或 ndk-build 的构建脚本。与 build.gradle 文件指示 Gradle 如何构建应用一样,CMake 和 ndk-build 需要一个构建脚本来了解如何构建您的原生库。对于新项目,Android Studio 会创建一个 CMake 构建脚本 CMakeLists.txt,并将其置于模块的根目录中。要详细了解此构建脚本的内容,请参阅介绍如何创建 Cmake 构建脚本的部分。

3. 直接运行项目

运行后会在界面上显示“Hello from C++”的字样。下面简单说明构建的步骤:

  1. Gradle 调用您的外部构建脚本 CMakeLists.txt
  2. CMake 按照构建脚本中的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so,Gradle 随后会将其打包到 APK 中。
  3. 运行时,应用的 MainActivity 会使用 System.loadLibrary() 加载原生库。现在,应用可以使用库的原生函数 stringFromJNI()
  4. MainActivity.onCreate() 调用 stringFromJNI(),这将返回“Hello from C++”并使用这些文字进行显示。

如果想要验证 Gradle 是否已将原生库打包到 APK 中,可以使用 APK 分析器

  1. 选择 Build > Analyze APK
  2. 从 app/build/outputs/apk/ 目录中选择 APK 并点击 OK
  3. 如下图所示,会在 APK 分析器窗口的 lib/<ABI>/ 下看到 libnative-lib.so

生成的so文件如下:

4. 自己添加C/C++代码

具体步骤如下:

  • Java中声明native方法

  • C/C++实现该方法

  • 创建CMake构建脚本,即CMakeLists.txt文件
add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp
             src/main/cpp/com_wangsu_huangm2_testndk_NativeHelper.c
             )

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
  1. 使用 add_library() 向您的 CMake 构建脚本添加源文件或库
  2. CMake 使用  lib库名称.so  来为库文件命名,例如,如果您在构建脚本中指定“native-lib”作为共享库的名称,CMake 将创建一个名称为 libnative-lib.so 的文件。
  3. 添加NDK API,将 find_library() 命令添加到您的 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。然后为了确保您的原生库可以在 log 库中调用函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令关联库。
  • 修改gradle文件,关联我们的原生库

defaultConfig {} 块中配置另一个 externalNativeBuild {} 块,为 CMake 或 ndk-build 指定可选参数和标志。与 defaultConfig {} 块中的其他属性类似,您也可以在构建配置中为每个产品风味重写这些属性。这个是官网的说明,应该是配置不同打包变体的,我这里也还没有尝试。

官网还提供了另外一种关联的方法,直接操作Android Studio,可以参考Link Gradle to your native library,直接右键app项目选择Link C++ Project with Gradle即可,但是很奇怪我的Android Studio中并没有这个选项。

  • 运行结果

 

六、总结

知道了NDK的一些概念和基本实现,包括ndkbuild和cmake两种构建方式。具体构建原理还不是很清楚,而且配置文件中的属性也不甚理解,这个需要后续再进行深入了解。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值