Android NDK入门:C++ 基础知识

Android NDK(Native Development Kit)允许开发者使用 C/C++ 编写 Android 应用的原生代码,弥补 Java 在性能、底层操作(如音视频处理、游戏引擎、硬件交互)等场景的不足。掌握 C++ 核心基础是 NDK 开发的第一步,本文聚焦 NDK 开发中高频用到的 C++ 特性,结合 Android 场景讲解。

一、C++ 与 Java 的核心差异(NDK 视角)

NDK 开发本质是 “Java/ Kotlin 调用 C++”,先明确 C++ 与 Java 的核心区别,避免思维混淆:
特性 C++ Java NDK 开发影响
内存管理 手动管理(new/delete、malloc/free) 自动 GC 需手动释放内存,否则易内存泄漏
语言范式 面向过程 + 面向对象 + 泛型 + 模板 纯面向对象 C++ 可灵活选择范式,底层用过程式,业务用面向对象
编译方式 编译为机器码(平台相关) 编译为字节码(JVM 解释) NDK 需编译为 so 库(armeabi-v7a/arm64-v8a 等)
异常处理 可选(try/catch),无强制 强制异常处理(Checked Exception) C++ 异常不建议跨 JNI 层传递
数据类型 原生类型(int/char)大小平台相关 原生类型大小固定 需注意跨平台类型兼容性(如用 stdint.h)

二、NDK 开发必备 C++ 基础

1. 基础数据类型与跨平台兼容

C++ 原生类型(如 int、long)的大小在 32 位 / 64 位 Android 设备上可能不同,NDK 开发中必须用固定大小类型(来自<stdint.h>):
固定大小类型 说明 替代的原生类型
int8_t/uint8_t 8 位有符号 / 无符号整数 char
int16_t/uint16_t 16 位整数 short
int32_t/uint32_t 32 位整数 int
int64_t/uint64_t 64 位整数 long long
size_t 无符号,存储内存大小 -
示例
cpp
 
运行
 
#include <stdint.h>
// 正确:跨平台固定32位整数
int32_t android_api_level = 30;
// 错误:int在32位是4字节,64位仍为4字节(但long在32位是4字节,64位是8字节)
long wrong_platform_type = 100;
 

2. 指针与内存管理(NDK 核心)

NDK 中大量涉及 JNI 指针、内存拷贝、原生内存分配,必须掌握指针基础:

(1)指针基础

指针是存储内存地址的变量,NDK 中常见场景:
  • JNIEnv*:JNI 环境指针(核心,后续 JNI 开发必用)
  • 数组指针:处理 Java 数组(如 byte [])
  • 动态内存指针:new/delete、malloc/free
示例
cpp
 
运行
// 基础指针
int32_t value = 100;
int32_t* p_value = &value; // 取地址
*p_value = 200; // 解引用,修改原值

// 动态内存分配(堆内存)
// C++方式
int32_t* arr = new int32_t[10];
arr[0] = 1;
delete[] arr; // 释放数组,必须用delete[]

// C方式(NDK中更常见,兼容C)
char* buffer = (char*)malloc(1024);
if (buffer == nullptr) { // 检查分配是否成功
    return -1;
}
free(buffer); // 释放C风格内存
 

(2)内存泄漏注意点(NDK 高频坑)

  • JNI 层分配的内存(如 new/malloc),Java 层无法 GC,必须手动释放;
  • 避免在循环中频繁分配内存且不释放;
  • 跨线程传递指针时,确保内存生命周期可控。

3. 面向对象核心(NDK 封装常用)

C++ 的类、继承、多态是封装原生逻辑的核心,NDK 中常用类封装底层操作(如音频解码、硬件交互)。

(1)类与成员函数

cpp
 
运行
#include <string>
// 封装Android日志工具(NDK常见场景)
class AndroidLogger {
private:
    // 私有成员:日志标签
    std::string tag_;
public:
    // 构造函数
    AndroidLogger(const std::string& tag) : tag_(tag) {}
    
    // 成员函数:打印日志
    void log(const std::string& message);
    
    // 析构函数:释放资源(如文件句柄、内存)
    ~AndroidLogger() {}
};

// 成员函数实现
void AndroidLogger::log(const std::string& message) {
    // 实际会调用NDK的__android_log_print
    printf("[%s] %s\n", tag_.c_str(), message.c_str());
}

// 使用类
AndroidLogger logger("NDK_DEMO");
logger.log("C++ log message");
 

(2)静态成员与单例(NDK 全局管理)

NDK 中常通过单例封装全局资源(如全局配置、唯一硬件句柄):
cpp
 
运行
class GlobalConfig {
private:
    static GlobalConfig* instance_;
    int32_t api_level_;
    
    // 私有构造函数:禁止外部实例化
    GlobalConfig() : api_level_(0) {}
public:
    // 获取单例
    static GlobalConfig* getInstance() {
        if (instance_ == nullptr) {
            instance_ = new GlobalConfig();
        }
        return instance_;
    }
    
    // 设置/获取API等级
    void setApiLevel(int32_t level) { api_level_ = level; }
    int32_t getApiLevel() const { return api_level_; }
    
    // 释放单例
    static void destroy() {
        if (instance_ != nullptr) {
            delete instance_;
            instance_ = nullptr;
        }
    }
};

// 静态成员初始化
GlobalConfig* GlobalConfig::instance_ = nullptr;

// 使用单例
GlobalConfig::getInstance()->setApiLevel(30);
int32_t level = GlobalConfig::getInstance()->getApiLevel();
 

4. 字符串处理(JNI 交互核心)

Java 字符串(String)与 C++ 字符串的转换是 JNI 高频操作,需掌握 C++ string 类和 C 风格字符串(char*):

(1)std::string 基础

cpp
 
运行
#include <string>
#include <cstring>

// C++ string 与 C风格字符串转换
std::string cpp_str = "NDK String";
const char* c_str = cpp_str.c_str(); // 转为const char*(不可修改)
char* mutable_str = new char[cpp_str.length() + 1];
strcpy(mutable_str, cpp_str.c_str()); // 拷贝为可修改的char*

// 拼接、截取(NDK常用)
std::string log_str = "API-";
log_str += std::to_string(30); // 拼接:API-30
std::string sub_str = log_str.substr(0, 3); // 截取:API

// 释放内存
delete[] mutable_str;
 

(2)JNI 字符串转换(提前铺垫)

后续 JNI 开发中,Java String ↔ C++ char* 是核心,基础逻辑:
cpp
 
运行
// 伪代码(后续JNI章节详解)
// Java String → C++ char*
const char* jstr_to_cstr(JNIEnv* env, jstring jstr) {
    if (jstr == nullptr) return nullptr;
    return env->GetStringUTFChars(jstr, nullptr);
}

// C++ char* → Java String
jstring cstr_to_jstr(JNIEnv* env, const char* cstr) {
    if (cstr == nullptr) return nullptr;
    return env->NewStringUTF(cstr);
}
 

5. 容器(NDK 数据存储)

C++ STL 容器是 NDK 中存储数据的首选,替代原生数组,常用:
  • std::vector:动态数组(存储列表数据,如音频帧、文件列表);
  • std::map:键值对(存储配置、映射关系);
  • std::string:字符串(替代 char*)。
示例
cpp
 
运行
#include <vector>
#include <map>

// 存储Android设备信息
std::map<std::string, std::string> device_info;
device_info["model"] = "Pixel 6";
device_info["os"] = "Android 14";

// 遍历map
for (const auto& pair : device_info) {
    printf("%s: %s\n", pair.first.c_str(), pair.second.c_str());
}

// 动态数组存储整数
std::vector<int32_t> api_levels = {28, 29, 30, 31};
api_levels.push_back(32); // 添加元素
// 遍历vector
for (int32_t level : api_levels) {
    printf("API Level: %d\n", level);
}
 

6. 异常处理(NDK 谨慎使用)

C++ 支持 try/catch,但JNI 层不建议抛出异常到 Java 层(易导致崩溃),通常用返回值表示错误:
cpp
 
运行
// 错误示例:跨JNI抛异常(不推荐)
void riskyOperation() {
    try {
        int* p = nullptr;
        *p = 100; // 空指针访问
    } catch (...) {
        // JNI层捕获后,转为错误码返回
        throw "Null pointer exception"; // 不建议抛到Java
    }
}

// 正确示例:返回错误码(NDK推荐)
int32_t safeOperation() {
    int* p = nullptr;
    if (p == nullptr) {
        return -1; // 错误码:空指针
    }
    *p = 100;
    return 0; // 成功
}
 

三、NDK 中 C++ 的编译注意事项

  1. 标准版本:NDK 默认支持 C++11/14/17,需在CMakeLists.txt中指定:
    cmake
     
     
     
     
  2. STL 选择:NDK 提供多种 STL(如 c++_static、c++_shared、gnustl),优先用c++_shared(动态链接,减小 so 体积);
  3. 命名空间:NDK 代码建议用命名空间避免冲突:
    cpp
     
    运行
     
     
     
     
    namespace ndk_demo {
        class Logger { /* ... */ };
    }
    // 使用
    ndk_demo::Logger logger("TAG");
     
     

四、入门实践:编写第一个 NDK C++ 代码

1. 核心代码(logger.cpp)

cpp
 
运行
 
 
 
 
#include <stdint.h>
#include <string>
#include <android/log.h>

// 日志宏定义(NDK标准写法)
#define LOG_TAG "NDK_CPP_DEMO"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

namespace ndk_demo {
    class Calculator {
    private:
        int32_t result_;
    public:
        Calculator() : result_(0) {}
        
        int32_t add(int32_t a, int32_t b) {
            result_ = a + b;
            LOGD("计算结果:%d + %d = %d", a, b, result_);
            return result_;
        }
    };
}

// 对外暴露的C风格函数(JNI调用需C链接)
extern "C" {
    int32_t calculate_add(int32_t a, int32_t b) {
        ndk_demo::Calculator calc;
        return calc.add(a, b);
    }
}
 

2. 关键说明

  • __android_log_print:NDK 提供的日志函数,需链接log库;
  • extern "C":强制 C 链接(避免 C++ 名称修饰,JNI 能找到函数);
  • 命名空间:隔离代码,避免与其他原生库冲突。

五、总结

NDK 开发的 C++ 基础核心围绕 “内存管理跨平台兼容JNI 交互” 三大方向:
  1. 优先使用固定大小类型(stdint.h),避免平台兼容问题;
  2. 手动管理内存,杜绝泄漏(NDK 调试内存泄漏难度远高于 Java);
  3. 用类 / 单例封装原生逻辑,降低 JNI 调用复杂度;
  4. 字符串、容器是 JNI 数据交互的基础,需熟练掌握;
  5. 避免跨 JNI 层抛异常,用错误码替代。
posted @ 2026-01-04 11:01  C++大哥来也  阅读(19)  评论(0)    收藏  举报