4. C++基础与Android Native开发

Android Native Service 开发的核心语言是 C++,而 Android 平台对 C++ 的支持并非“标准 C++ 直接编译”那么简单。它涉及语言标准、JNI 桥接、构建系统与日志输出四个层面的协同。本章围绕 C++11/14/17 关键特性、JNI 基础、NDK 编译系统(CMake/Android.bp)以及 liblog 日志库使用展开,帮助读者建立从源码到可运行 Native 服务的完整认知。

一、C++11/14/17 关键特性:Native 开发的现代工具箱

Android 从 NDK r18 起默认使用 Clang,并支持 C++11 及以上标准。在 Native Service 中,以下特性使用频率最高:

  • 智能指针:std::unique_ptr 与 std::shared_ptr 管理 Binder 对象、回调句柄,避免手动 delete 导致的泄漏。
  • Lambda 表达式:常用于线程池任务、回调注册,例如 std::thread([this]{ ... }).detach()。
  • auto 与范围 for:简化迭代器代码,提升可读性。
  • 移动语义:std::move 减少大对象拷贝,在 Parcel 数据传递中尤为关键。
  • constexpr 与 nullptr:编译期常量与类型安全空指针。
  • C++17 结构化绑定:auto [key, value] = mapPair; 便于遍历容器。
  • C++17 std::optional:表达“可能无返回值”的接口语义。
Android 平台代码通常限制异常与 RTTI 的使用(-fno-exceptions -fno-rtti),因此智能指针与错误码返回是主流风格,而非 try/catch。

二、JNI 基础:Java 与 Native 的桥梁

JNI(Java Native Interface)是 Java 层调用 C/C++ 代码的规范。在 Native Service 中,JNI 常用于将系统服务能力暴露给上层 App,或让 Java 层通过 System.loadLibrary 加载 native 库。

JNI 的核心要素包括:

  • JNIEnv:线程相关的环境指针,提供调用 Java 方法、操作对象的能力。
  • jobject/jclass:Java 对象与类的 native 引用。
  • 方法注册:静态注册(Java_包名_类名_方法名)与动态注册(RegisterNatives)。
  • 局部/全局引用:局部引用在 native 方法返回后失效,跨线程需使用 NewGlobalRef。
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_NativeService_getVersion(JNIEnv* env, jobject /* this */) {
    std::string version = "NativeService-1.0";
    return env->NewStringUTF(version.c_str());
}

动态注册更适合 Native Service,因为它可以在 JNI_OnLoad 中一次性绑定多个方法,避免长函数名,也便于模块化管理。

三、NDK 编译系统:CMake 与 Android.bp

Android Native 开发存在两套构建体系:面向 App/NDK 的 CMake,以及面向 AOSP 源码树的 Android.bp(Soong)。理解二者差异是开发 Native Service 的前提。

对比项 CMake Android.bp
使用场景 NDK 独立项目、App 集成 AOSP 源码树内编译
配置文件 CMakeLists.txt Android.bp
依赖管理 find_library / target_link_libraries shared_libs / static_libs
日志库 链接 log 库 shared_libs: ["liblog"]
产物 .so 动态库 cc_library / cc_binary

CMake 示例:

cmake_minimum_required(VERSION 3.18)
project(native_service)
add_library(native_service SHARED native_service.cpp)
target_link_libraries(native_service log android)

Android.bp 示例:

cc_library_shared {
    name: "libnative_service",
    srcs: ["native_service.cpp"],
    shared_libs: ["liblog", "libbinder"],
    cflags: ["-Wall", "-Werror"],
}
在 AOSP 中不要混用 CMake 与 Android.bp 编译同一模块,否则会出现符号重复或依赖缺失。Native Service 通常使用 Android.bp,而 App 侧 JNI 使用 CMake。

四、liblog 日志库:Native 层的可观测性

Android Native 层不能直接使用 Java 的 Log.d,而是通过 liblog 输出日志。常用宏定义在 <log/log.h> 中:

  • ALOGV / ALOGD / ALOGI / ALOGW / ALOGE:对应 Verbose 到 Error 五个级别。
  • ALOG_ASSERT:条件不满足时输出日志并终止。
  • __android_log_print:底层函数,适合封装自定义日志宏。
#include <log/log.h>

void NativeService::start() {
    ALOGI("NativeService start, pid=%d", getpid());
    if (mCallback == nullptr) {
        ALOGE("callback is null, abort start");
        return;
    }
    ALOGD("callback registered: %p", mCallback.get());
}

日志可通过 logcat 查看:

adb logcat -s NativeService:V
liblog 的日志级别在编译期可通过 LOG_NDEBUG 控制,Release 版本通常关闭 VERBOSE 与 DEBUG,减少性能开销。

五、综合案例:一个最小 Native Service 的构建链路

假设我们要实现一个名为 NativeService 的 native 服务,它通过 JNI 暴露 getVersion() 给 Java 层,并使用 liblog 输出启动日志。其构建与调用链路如下:

Native Service 构建与调用链路 Java 层 System.loadLibrary JNI 桥接层 JNI_OnLoad / RegisterNatives Native Service (C++) C++17 / 智能指针 / Binder 构建系统 CMake / Android.bp liblog ALOGI / ALOGE logcat 输出 adb logcat -s NativeService:V 编译期:Android.bp 链接 liblog/libbinder → 运行期:JNI 调用 → 日志经 liblog 写入 logcat 关键点:C++17 特性 + JNI 动态注册 + Soong 构建 + liblog 可观测性

该链路体现了 Native Service 开发的四个关键点:语言层使用 C++17 现代特性保证代码质量;JNI 层完成 Java 与 C++ 的互操作;构建层通过 Android.bp 或 CMake 将源码编译为动态库;运行层通过 liblog 输出可观测日志。四者缺一不可。

六、常见误区与复盘

  • 误区一:在 JNI 中缓存 JNIEnv。JNIEnv 是线程相关的,跨线程使用会导致崩溃,应使用 JavaVM 并通过 AttachCurrentThread 获取当前线程的 JNIEnv。
  • 误区二:忘记 NewGlobalRef。局部引用在 native 方法返回后失效,若需长期持有 Java 对象,必须创建全局引用。
  • 误区三:Android.bp 中漏写 shared_libs。链接 liblog 时若未声明,编译期可能通过,运行期出现符号找不到。
  • 误区四:日志级别滥用。在热路径中大量使用 ALOGD 会拖慢性能,Release 版本应关闭 DEBUG 日志。
建议在 Native Service 初始化阶段统一封装日志宏,例如 #define LOG_TAG "NativeService",并在每个源文件顶部定义,避免日志标签混乱。