美文网首页
NDK 浅析

NDK 浅析

作者: Parallel_Lines | 来源:发表于2020-04-22 12:19 被阅读0次

一、NDK 配置

开发环境

MacOS 10.15.3
AndroidStudio 3.6.2

准备

从 SDK Manager - SDK Tools 下载 LLDB、CMake 和 NDK。

下载后如果为方便开发可以配置 ndk 的环境变量,也可以不配置。

Hello World

1.声明 Native 方法

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("helloJni");
    }

    public native String get();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv);
        tv.setText(get());
    }
}

2.实现 Native 方法

1.创建 jni 文件夹。

main 目录右键 -- New -- Folder -- JNI Folder

2.jni 下创建 3 个文件 hello.cpp、Android.mk、Application.mk

hello.cpp

#include <jni.h>
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

// 替换为你项目的包名
jstring Java_com_dixon_ndkdemo_MainActivity_get(JNIEnv *env, jobject thiz) {
    printf("invoke get in c++\n");
    return env->NewStringUTF("Hello from JNI in helloJni.so !");
}

#ifdef __cplusplus
}
#endif

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := helloJni
LOCAL_SRC_FILES := hello.cpp

include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := all

3.编译 so 库

1.工程 build.gradle 添加下载的 ndk 版本。

android {
    ...

    defaultConfig {
        ...
        // 你的ndk版本
        ndkVersion "21.0.6113669"
        ...

2.Terminal 切换到 jni 目录,执行 ndk-build 命令。因为我没有配置环境变量,所以是如下调用的:

~/Library/Android/sdk/ndk/21.0.6113669/ndk-build

ndk 编译完成,main 目录下生成 libs 目录,libs 文件夹内容如下。

目录.png

4.关联 Gradle

模块右键 -- Link C++ Project with Gradle,弹出的下拉菜单中选择 ndk-build,Project Path 选择 Android.mk 脚本文件。

此操作将在模块 build.gradle 中生成如下代码:

android {
    ...
    externalNativeBuild {
        ndkBuild {
            path file('src/main/jni/Android.mk')
        }
    }
}

完成配置,编译运行即可。

二、ndk-build 详解

使用 NDK 编译项目共有三种方式。

  1. 基于 Make 的 ndk-build;(上文即是,本文将重点解析这种方式)
  2. CMake
  3. 独立工具链

本文重点讲解上例中的 ndk-build 编译方式。

ndk-build 可以分为三个文件,分别是 ndk-build 脚本、Android.mk、Application.mk 文件,下面一一说明。

ndk-build 脚本

ndk-build 脚本使用 NDK 的基于 Make 的编译系统 构建项目

使用 ndk-build <option> 执行特殊命令,其中 <option> 包含如下几种参数:

option mean
V=1 启动编译,并显示编译命令
-B 强制执行完整的重新编译
-C <project> 编译位于 <project> 的项目路径的原生代码。如果不想在终端通过 cd 切换到该路径,此选项会非常有用
NDK_APPLICATION_MK=<file> 使用 NDK_APPLICATION_MK 变量指向的特定 Application.mk 文件进行编译
NDK_DEBUG=1 强制执行可调试的编译
NDK_DEBUG=0 强制执行发布版编译
APP_ABI="armeabi armeabi-v7a x86" 编译多平台动态库
TARGET_PLATFORM=android-21 指定sdk版本

Android.mk

1. 概述

Android.mk 文件用于向编译系统描述源文件和共享库。
Android.mk 文件用于定义 Application.mk、编译系统和环境变量所未定义的项目范围设置。它还可替换特定模块的项目范围设置。

2. 基础知识

本部分将解释 Hello world 例子中 Android.mk 文件每一行的作用(语法)。

<1.必须先 定义 LOCAL_PATH 变量:

LOCAL_PATH := $(call my-dir)

此变量表示源文件的路径。在这行代码中,编译系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件所在目录)的路径。

声明路径

<2.下一行 声明 CLEAR_VARS 变量,其值由编译系统提供:

include $(CLEAR_VARS)

该变量用于清除许多 LOCAL_XXX 变量,如 LOCAL_MODULELOCAL_SRC_FILESLOCAL_STATIC_LIBRARIES 等,但是不会清除 LOCAL_PATH。在描述每个模块之前,必须重新声明此变量。

清空变量

清空变量的原因是,NDK 可能会多次解析 Android.mk 文件,每次使用其中某些变量的不同定义,所以需要清空变量然后按需重定义。

<3.定义 LOCAL_MODULE,该变量存储要编译的模块的名称,应用的每个模块仅能使用一次。

LOCAL_MODULE := helloJni

上面的例子会生成 libhelloJni.so 库。

每个模块名称必须唯一,且不含任何空格。

定义 so 库唯一名称

<4.定义 LOCAL_SRC_FILES, 该变量必须包含要编译到模块中的 C 或 C++ 源文件列表。

LOCAL_SRC_FILES := hello.cpp myfun.cpp

上面的例子会把 hello.cpp、myfun.cpp 中的代码都编译到同一个 so 库中。多个文件以空格分割。

定义源文件列表

<5.声明 BUILD_SHARED_LIBRARY,该变量帮助系统将所有内容连接到一起。

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY 变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要编译的内容以及编译方式。

收集变量

3. 变量和宏

编译系统提供了许多可在 Android.mk 文件中使用的变量。其中许多变量已预先赋值,另一些变量需要自己赋值。

除了这些变量之外,还可以自己定义任意变量。在定义变量时请注意,NDK 编译系统保留了下列变量名称:

LOCAL_ 开头的名称,例如 LOCAL_MODULE
PRIVATE_NDK_APP 开头的名称。编译系统在内部使用这些变量名。
小写名称,例如 my-dir。编译系统也是在内部使用这些变量名。

为了方便 需要在 Android.mk 文件中定义自己的变量,建议在名称前附加 MY_

4. include 变量

NDK 定义了一些 include 变量,使用语法为 include $(CLEAR_VARS)

1.CLEAR_VARS

清除变量。

2.BUILD_EXECUTABLE

收集变量,并确认如何根据源文件编译目标可执行文件。

3.BUILD_SHARED_LIBRARY

收集变量,并确认如何根据源文件编译目标共享库。

其它变量详见 官方文档-NDK 定义的 include 变量

5. 目标信息变量

1.TARGET_ARCH

编译系统解析 Android.mk 文件时面向的 CPU 系列,此变量是 arm、arm64、x86 或 x86_64 之一。

示例用法:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
    LOCAL_MODULE    := helloJni
    LOCAL_SRC_FILES := hello.cpp myfun.cpp
    endif

include $(BUILD_SHARED_LIBRARY)

当编译命令为 ~/Library/Android/sdk/ndk/21.0.6113669/ndk-build TARGET_ARCH_ABI=armeabi-v7a,编译通过,libs 下仅生成 ameabi-v7a 目录(目录下生成 libhelloJni.so)。

2.TARGET_PLATFORM

编译系统解析 Android.mk 文件时面向的 Android API。

ifeq ($(TARGET_PLATFORM),android-22)
        # ... do something ...
    endif
ndk-build TARGET_PLATFORM=android-22

3.TARGET_ARCH_ABI

编译系统解析 Android.mk 文件时面向的 ABI。

ABI 和 CPU 架构 的关系如下:

CPU 架构 设置
ARMv7 armeabi-v7a
ARMv8 AArch64 arm64-v8a
i686 x86
x86-64 x86_64
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
      # ... do something ...
    endif

其它目标信息变量详见 官方文档-目标信息变量

6.模块描述变量

每个模块描述都应遵守以下基本流程:

  1. 使用 CLEAR_VARS 变量初始化或取消定义与模块相关的变量。
  2. 为用于描述模块的变量赋值(LOCAL_XXX)。
  3. 使用 BUILD_XXX 变量设置 NDK 编译系统,使其将适当的编译脚本用于该模块。

模块描述变量除了上述提到的 LOCAL_PATHLOCAL_MODULELOCAL_SRC_FILES 外还有很多,可以查询 官方文档-模块描述变量,按需调用。

7.函数宏

使用 $(call <function>) 可以对这些宏进行求值;它们将返回文本信息。

1.my-dir

这个宏返回最后包含的 makefile 的路径,通常是当前 Android.mk 的目录。my-dir 可用于在 Android.mk 文件的开头定义 LOCAL_PATH。例如:

LOCAL_PATH := $(call my-dir)

函数宏不是本文重点,更多函数宏参考 官方文档-函数宏

Application.mk

Application.mk 用于指定 ndk-build 的项目范围设置。

1.APP_ABI

不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。

APP_ABI 用于生成不同指令集下的动态库。他们的对应关系如下:

指令集 命令
32 位 ARMv7 APP_ABI := armeabi-v7a
64 位 ARMv8 (AArch64) APP_ABI := arm64-v8a
x86 APP_ABI := x86
x86-64 APP_ABI := x86_64
所有支持的 ABI(默认) APP_ABI := all

多个值使用空格分割:

APP_ABI := armeabi-v7a arm64-v8a x86

2.APP_BUILD_SCRIPT

默认情况下,ndk-build 假定 Android.mk 文件位于项目根目录的相对路径 jni/Android.mk 中。
要从其他位置加载 Android.mk 文件,可将 APP_BUILD_SCRIPT 设置为 Android.mk 文件的绝对路径。

3.APP_PROJECT_PATH

项目根目录的绝对路径。

更多变量参考 官方文档-Application.mk 变量

三、小结

本文主要讲解了如何配置简单的 NDK 项目,以及配置所用到的语法说明,后续将继续更新。

[TOC]

相关文章

网友评论

      本文标题:NDK 浅析

      本文链接:https://www.haomeiwen.com/subject/tztiihtx.html