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:表达“可能无返回值”的接口语义。
-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"],
}
四、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
LOG_NDEBUG 控制,Release 版本通常关闭 VERBOSE 与 DEBUG,减少性能开销。五、综合案例:一个最小 Native Service 的构建链路
假设我们要实现一个名为 NativeService 的 native 服务,它通过 JNI 暴露 getVersion() 给 Java 层,并使用 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 日志。
#define LOG_TAG "NativeService",并在每个源文件顶部定义,避免日志标签混乱。