[ NDK ] 基本配置、跑通代码

1,394 阅读6分钟

NDK (Native development kit): 官方文档

原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 activity 和访问实体设备组件,例如传感器和触控输入。NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 开发应用。然而,如果您需要实现以下一个或多个目标,那么 NDK 就能派上用场:

  1. 进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用。
  2. 使用您自己或其他开发者的 C 或 C++ 库。

您可以在 Android Studio 2.2 或更高版本中使用 NDK 将 C 和 C++ 代码编译到原生库中,然后使用 Android Studio 的集成构建系统 Gradle 将原生库打包到 APK 中。Java 代码随后可以通过 Java 原生接口 (JNI) 框架调用原生库中的函数。如需详细了解 Gradle 和 Android 构建系统,请参阅配置您的版本

1. 新建项目

可选两种新建项目方式

第一种:选择NativeC++新建

image.png

第二种:添加C++Module

在已有的项目里选择要使用ndk开发的module,右键......

image.png

2. 跑起来

如果是第一种新建方式,那就直接运行即可

Tips: 不同的 Android Studio 版本可能会导致不同的结果,如果运行失败,可参考 3. 分析 来排查错误

如果是第二种,则需要稍微写点东西,你肯定听说过 Javanative 方法,在 Kotlin 里我们使用 external 这个关键字

我们要写的就是 external (native) 方法,以及加载 native lib 的代码,因为编译器总要知道你希望什么时候开始把 native lib 里面的东西,加载到内存里。

So,在 MainActivity 里添加

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val tv = findViewById<TextView>(R.id.hello)
    tv.text = hello()
  }


    // 这里时加载你自己所需的 native lib
    // 如果是 C、C++ 代码,应该可以在 CMakeLists.txt 找到库的名字
    companion object {
      init {
        System.loadLibrary("my_ndk_lib")
      }
    }

    // external 方法 (jni 中间层)
    external fun hello(): String

}

关于 System.loadLibrary("my_ndk_lib") 在 cpp 文件夹下里有一个自动生成的 .cpp 文件,应该是和你项目名一样,下面介绍 CMakeLists.txt 的时候会提到

在里面写

extern "C"
JNIEXPORT jstring JNICALL
Java_com_czb_ndk_1jni_1kotlin_MainActivity_hello(JNIEnv *env, jobject thiz) {
    std::string s = "Hello";

    jstring js = env->NewStringUTF(s.c_str());

    return js;
}

Java _ com_czb_ndk_1jni_1kotlin _ MainActivity _ hello ,你应该已经观察到了,Java代表语言,后面跟的是包名,再后面是类名,最后是方法名

Tips:方法名一定要和包名对应,我不建议手敲,因为这里要用 _ 来代替包名的 .

如果包名中含有 _ 则用 _1 来代替,如果包名里有 _1 ,则用 _11 来代替......

可以直接在 MainActivity 里,external fun hello(): String 这里点击 hello ,然后option + enter (alt + 回车),直接生成然后将方法体内容填写即可

Tips:jni 代码(extrnal、native 方法)里的方法没有实现(没有大括号{}),只有声明,具体实现在 C、C++ 代码里

3. 分析

ndk开发时 代码跑起来需要这几个东西

  1. cpp 文件夹以及内部的 CMakeLists.txt cpp 文件或者.so等编好的库文件 作为 native lib
  2. jni 代码来作为 Java、Koltin 等基于 Jvm 语言调用的中间层
  3. 将中间层,native lib,Java 代码 连接起来的配置文件,比如 build.gradle
  4. 加载库的必要代码,比如 System.loadLibrary("my_ndk_lib")

build.gradle 怎么连接 native lib

最新的 Android Studio 已经可以在你 添加C++ Module 的时候,直接帮你配置好build.gradle

如果你要自己写 如需手动配置 Gradle 以关联到您的原生库,您需要将 externalNativeBuild 块添加到模块里的 build.gradle 文件中,并使用 cmake 或 ndkBuild 块对其进行配置:

Groovy 版本

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  externalNativeBuild {

    cmake {
      // CMakeLists.txt的地址
      path "CMakeLists.txt"
    }
  }
}

Kotlin 版本

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  externalNativeBuild {

    cmake {
      // CMakeLists.txt的地址
      path = file("CMakeLists.txt")
    }
  }
}

注意:如果您要将 Gradle 关联到现有的 ndk-build 项目,请使用 ndkBuild 块(而不是 cmake 块),并提供指向 Android.mk 文件的相对路径。如果 Application.mk 文件与您的 Android.mk 文件位于同一目录下,Gradle 也会包含此文件。

CMakeLists.txt怎么写

一般写这5个就行

  1. cmake_minimum_required 这个是 cmake 要求的最低版本
  2. project 这个是项目名
  3. add_library 这个是添加 lib
  4. find_library 这个是搜索并添加 lib
  5. target_link_libraries 这个是连接 lib
# 有关将 CMake 与 Android Studio 配合使用的更多信息,
# 请阅读文档:https:d.android.comstudioprojectsadd-native-code.html 

# 设置构建本机库所需的最低 CMake 版本。
cmake_minimum_required(VERSION 3.18.1)

# 声明并命名项目。
project("ndk_jni_kotlin")

# 创建并命名库,将其设置为 STATIC 或 SHARED,并提供其源代码的相对路径。
# 您可以定义多个库,CMake 会为您构建它们。Gradle 会自动将共享库与您的 APK 打包。
add_library( # 设置库的名称。
        my_ndk_lib

        # 将库设置为共享库。
        SHARED

        # 提供源文件的相对路径。
        my_ndk_lib.cpp)

# 搜索指定的预生成库并将路径存储为变量。
# 由于 CMake 默认在搜索路径中包含系统库,
# 因此您只需指定要添加的公有 NDK 库的名称。 
# CMake 会在完成构建之前验证库是否存在。
find_library( # 设置路径变量的名称。
        log-lib

        # 指定您希望 CMake 查找的 NDK 库的名称。
        log)

# 指定 CMake 应链接到目标库的库。可以链接多个库,
# 例如在此生成脚本中定义的库、预生成的第三方库或系统库。
target_link_libraries( # 指定目标库。
        my_ndk_lib

        # 将目标库链接到 NDK 中包含的日志库。
        ${log-lib})

您可以使用 set_target_properties() 命令指定库的路径,具体命令如下所示。

某些库会针对特定的 CPU 架构或应用二进制接口 (ABI) 提供单独的软件包,并将其整理到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让您只使用所需的库版本。如需向 CMake 构建脚本添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,您可以使用 ANDROID_ABI 路径变量。此变量使用的是 NDK 支持的一组默认 ABI,或者您手动配置 Gradle 而让其使用的一组经过过滤的 ABI。

为了让 CMake 能够在编译时找到头文件,您可以使用 include_directories() 命令并包含相应头文件的路径

官方文档

一开始代码跑不起来的原因:

  1. cpp 方法名没写对
  2. cpp 方法实现没写对
  3. 忘记写 System.loadLibrary("my_ndk_lib")

如果你的代码跑起来了,不妨点个赞 🎉🎉🎉