这是一个非常核心且强大的技术,主要用于性能优化、代码复用和访问硬件等场景。

(图片来源网络,侵删)
核心概念:JNI vs. NDK
在 Android 中,你通常会遇到两个与 C/C++ 相关的概念:JNI 和 NDK,它们紧密相关,但职责不同。
JNI (Java Native Interface)
- 是什么? 它是一套接口规范,定义了 Java 代码如何与 C/C++ 代码进行交互。
- 作用:
- 允许 Java 代码调用 C/C++ 的函数(称为 "Native Methods")。
- 允许 C/C++ 代码调用 Java 的对象和方法。
- 特点:
- 双向通信桥梁:它是连接 Java 虚拟机和本地代码的桥梁。
- 平台无关:规范本身不依赖于任何特定平台。
- 代码繁琐:直接使用 JNI 需要编写大量样板代码来处理数据类型转换、异常处理等,非常容易出错。
NDK (Native Development Kit)
- 是什么? 它是一套工具集,由 Google 提供。
- 作用:
- 提供了编译工具链(如 GCC, Clang),用于将你的 C/C++ 代码编译成能在 Android 设备上运行的原生库(
.so文件)。 - 提供了头文件(如
jni.h),这些头文件定义了 JNI 接口。 - 提供了构建系统(如 CMake, ndk-build),用于自动化编译过程。
- 提供了预构建的库(如 OpenGL ES, Vulkan, OpenSL ES)和 API。
- 提供了编译工具链(如 GCC, Clang),用于将你的 C/C++ 代码编译成能在 Android 设备上运行的原生库(
- 特点:
- 开发工具:它不是一个库,而是一个让你更容易开发 JNI 代码的工具包。
- 简化开发:现代 NDK 强烈推荐使用 CMake 和 C++,而不是纯粹的 C 语言和旧的
ndk-build,C++ 提供了更好的类型安全和更现代的特性。 - ABI 兼容性:帮助你为不同的 CPU 架构(如
armeabi-v7a,arm64-v8a,x86,x86_64)编译库。
简单比喻:
- JNI 就像一本“语法词典”,告诉你中文(Java)和英文(C/C++)之间如何互相翻译。
- NDK 就像一个“翻译工作室”,它提供了词典、翻译工具(编译器)、翻译流程(CMake)和专业的翻译人员(预构建库),让你能高效地完成翻译工作。
为什么要在 Android 中使用 C/C++ 库?(应用场景)
-
性能密集型任务:
- 游戏引擎、物理模拟、图像/视频处理:C/C++ 的执行效率远高于 Java,适合对 CPU 和内存要求极高的场景。
- 数据加密/解密:许多加密算法用 C/C++ 实现可以最大化性能。
-
代码复用:
(图片来源网络,侵删)- 跨平台库:你可能已经有现成的 C/C++ 库(用于网络、数据库、科学计算的库),通过 NDK 可以直接在 Android 上使用它们,而无需用 Java 重写。
-
访问硬件和底层系统:
- 传感器、驱动程序:Android 系统的底层驱动通常用 C/C++ 编写,通过 NDK 可以更直接地访问这些硬件功能。
- 自定义 ROM 开发:系统级修改离不开 C/C++。
-
保护核心算法:
将核心的、敏感的算法用 C/C++ 实现,可以比纯 Java 代码更难被反编译和窃取。
如何创建和使用 C/C++ 库?(以 Android Studio 和 CMake 为例)
这是目前官方推荐的标准流程。

(图片来源网络,侵删)
第 1 步:配置项目
-
创建或打开项目:确保你的项目支持 C++。
- 创建新项目时,勾选 "Include C++ Support"。
- 对于现有项目,可以通过
File->New->Import Module,然后选择Native C++模块来添加。
-
检查
build.gradle(Module: app): NDK 和 CMake 的配置会自动添加到你的app/build.gradle文件中。android { // ... defaultConfig { // ... externalNativeBuild { cmake { // 指定 C++ 标准 cppFlags "" // 可选:指定要支持的 ABI // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } } // ... externalNativeBuild { cmake { // 指定 CMakeLists.txt 文件的路径 path "src/main/cpp/CMakeLists.txt" version "3.18.1" // 使用 CMake 的版本 } } }
第 2 步:编写 C/C++ 代码
-
找到源文件:Android Studio 会在
app/src/main/cpp/目录下自动创建native-lib.cpp文件。 -
编写 C/C++ 代码:在这个文件中,你将实现你的原生逻辑。
// native-lib.cpp #include <jni.h> #include <string> #include <android/log.h> // 用于在 logcat 中打印日志 // 定义一个宏,方便日志打印 #define LOG_TAG "NativeLib" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapp_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { // C++ 逻辑 std::string hello = "Hello from C++"; LOGI("stringFromJNI is called!"); // 将 C++ string 转换为 Java String return env->NewStringUTF(hello.c_str()); }extern "C":告诉 C++ 编译器使用 C 语言的链接方式,这样 Java 才能找到这个函数。JNIEXPORT/JNICALL:JNI 函数必需的修饰符。Java_包名_类名_方法名:这是 JNI 函数的标准命名规则,Java 代码通过这个签名来调用它。JNIEnv*:指向 JNI 环境的指针,通过它可以调用 Java 的方法。jobject:代表调用这个 JNI 方法的 Java 对象(这里是MainActivity的实例)。jstring:JNI 中的字符串类型。
第 3 步:配置 CMake (CMakeLists.txt)
这个文件告诉 CMake 如何编译你的 C/C++ 代码。
# CMakeLists.txt
# 指定 CMake 最低版本要求
cmake_minimum_required(VERSION 3.18.1)
# 定义项目名称
project("myapp")
# 添加一个可执行库(通常是 JNI 库)
add_library( # 设置库名称
native-lib
# 设置库的类型
SHARED
# 提供源文件的相对路径
native-lib.cpp )
# 找到指定的日志库 (log)
find_library( # 设置路径变量名
log-lib
# 指定 NDK 中的库名称
log )
# 将 native-lib 库和 log-lib 链接起来
target_link_libraries( # 指定目标库
native-lib
# 链接上一步找到的 log-lib
${log-lib} )
第 4 步:在 Java/Kotlin 代码中调用
-
加载库:在你的
MainActivity的onCreate方法中,加载.so库。// MainActivity.kt package com.example.myapp import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.TextView import android.util.Log // 用于在 Java/Kotlin 端打印日志 class MainActivity : AppCompatActivity() { // 声明一个外部方法,名称与 JNI 函数的 Java 部分对应 external fun stringFromJNI(): String companion object { // 用来加载库名,必须与 CMakeLists.txt 中 add_library 的第一个参数一致 init { System.loadLibrary("native-lib") } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 调用原生方法 val tv: TextView = findViewById(R.id.sample_text) val fromJNI = stringFromJNI() Log.d("MainActivity", "Received from C++: $fromJNI") tv.text = fromJNI } }
第 5 步:构建和运行
点击 Android Studio 的 "Run" 按钮,Android Studio 会自动调用 CMake 来编译你的 C++ 代码,并将生成的 .so 文件打包到 APK 中,运行后,你就可以在界面上看到从 C++ 返回的字符串,同时在 logcat 中也能看到我们通过 __android_log_print 打印的日志。
C 语言 vs. C++ 语言的选择
| 特性 | C 语言 | C++ 语言 |
|---|---|---|
| 性能 | 极高,无额外开销 | 极高,但有少量面向对象模型的开销(通常可忽略) |
| 安全性 | 较低,手动管理内存,容易出错(内存泄漏、悬垂指针) | 较高,拥有 RAII(资源获取即初始化)、智能指针、异常处理等机制 |
| 开发效率 | 较低,需要手动管理一切,代码量大 | 较高,STL(标准模板库)提供了丰富的数据结构和算法,面向对象思想使代码更易组织和复用 |
| 与 Java 交互 | 直接通过 JNI,需要处理所有 JNI 的细节,代码繁琐且易错。 | 推荐使用 JNIHelper 或现代 C++ 封装,可以通过 C++ Wrapper 来简化 JNI 调用,或者使用 Google Gluegen 等工具生成绑定代码,C++ 的引用和异常处理与 Java 结合得更好。 |
| 推荐度 | 不推荐,除非你有一个无法用 C++ 重写的、巨大的纯 C 代码库需要集成。 | 强烈推荐,现代 Android NDK 开发的首选。 |
- JNI 是桥梁,定义了 Java 和 C/C++ 通信的规则。
- NDK 是工具箱,提供了编译、构建和管理原生代码所需的一切。
- 使用 C/C++ 库的主要目的是性能、复用和访问底层。
- 现代 Android 开发,即使你想用 C 语言,也强烈建议通过 C++ 的方式来集成(即使用 C++ 作为“胶水层”来调用你的 C 函数),这样可以利用 C++ 的安全性和开发效率优势,同时避免直接编写繁琐的 JNI 代码。
- CMake 是当前 Android Studio 中配置 NDK 项目的标准构建系统。
希望这份详细的指南能帮助你理解在 Android 中使用 C 语言库的全貌!
