13. NDK API与系统库:log库的使用、Android系统库列表、链接系统库、__android_log_print()详解
各位同学,欢迎来到第13章。今天咱们聊聊NDK开发里最基础、也最离不开的东西——系统库和日志打印。
说实话,我刚开始做NDK开发那会儿,最头疼的就是调试。Java层有Logcat,C/C++层呢?啥也没有。代码崩了,连个错误信息都看不到。后来才知道,原来Android系统给NDK开发者准备了一套完整的系统库,其中就包括log库。
这一章,我会把Android系统库的体系、怎么链接它们、以及最常用的日志API讲清楚。嗯,内容有点多,但都是干货。
13.1 Android系统库列表
Android系统库,说白了就是系统预编译好的.so文件。它们放在设备的/system/lib或/system/lib64目录下。NDK开发时,我们通过头文件和链接器来使用它们。
我整理了一份最常用的系统库列表,你写代码时大概率都会碰到:
| 库名 | 头文件 | 功能说明 |
|---|---|---|
| liblog.so | android/log.h | 日志输出到Logcat |
| libz.so | zlib.h | 数据压缩/解压缩 |
| libdl.so | dlfcn.h | 动态加载.so文件 |
| libm.so | math.h | 数学函数库 |
| libc.so | 标准C头文件 | C标准库(Bionic实现) |
| libc++.so | C++标准头文件 | C++标准库 |
| libEGL.so | EGL/egl.h | OpenGL ES窗口管理 |
| libGLESv2.so | GLES2/gl2.h | OpenGL ES 2.0图形库 |
| libOpenSLES.so | SLES/OpenSLES.h | 音频处理 |
| libjnigraphics.so | android/bitmap.h | 直接操作Java Bitmap像素 |
这里有个细节要注意:Android的libc不是glibc,而是Bionic。Bionic是Google专门为Android定制的C标准库,体积更小、性能更好。但有些glibc特有的函数,Bionic里没有。我在移植一个Linux库到Android时,就踩过这个坑——代码里用了glibc的某些扩展函数,编译直接报错。
13.2 链接系统库的两种方式
在CMake里链接系统库,其实就两种方式。我分别说说。
13.2.1 使用find_library + target_link_libraries
这种方式最规范,也是Google官方推荐的。CMake会去NDK的sysroot里找对应的库文件。
# 在CMakeLists.txt中
find_library(log-lib log)
find_library(z-lib z)
target_link_libraries(
my_native_lib
${log-lib}
${z-lib}
)
我个人习惯用这种方式。为什么呢?因为CMake会自动处理库的路径和ABI适配。你换不同架构编译时,它自动找对应的.so,省心。
13.2.2 直接指定库名
如果你确定库名不会变,也可以直接写:
target_link_libraries(
my_native_lib
log
z
)
CMake会自动在前面加lib、后面加.so。说白了就是语法糖。但我不建议新手这么写——万一哪天库名改了,你找错都不好找。
13.3 log库的使用
好了,重点来了。log库是NDK开发者的「眼睛」。没有它,你连程序跑没跑起来都不知道。
13.3.1 引入头文件
使用log库前,先包含头文件:
#include <android/log.h>
这个头文件里定义了日志相关的宏和函数。嗯,其实核心就一个函数——__android_log_print。
13.3.2 __android_log_print() 详解
这个函数的签名如下:
int __android_log_print(
int prio, // 日志优先级
const char* tag, // 日志标签
const char* fmt, // 格式化字符串,类似printf
... // 可变参数
);
返回值是写入的字节数,失败返回负数。不过说实话,我很少检查这个返回值——日志打印失败的概率极低。
prio参数有以下几个可选值,定义在android/log.h中:
| 优先级常量 | 对应Logcat级别 | 使用场景 |
|---|---|---|
| ANDROID_LOG_VERBOSE | V | 最详细的调试信息,发布时通常关闭 |
| ANDROID_LOG_DEBUG | D | 开发调试用,发布时建议关闭 |
| ANDROID_LOG_INFO | I | 普通信息,比如模块初始化完成 |
| ANDROID_LOG_WARN | W | 警告,程序还能跑但可能有隐患 |
| ANDROID_LOG_ERROR | E | 错误,功能不可用 |
| ANDROID_LOG_FATAL | F | 致命错误,程序即将崩溃 |
看个完整示例:
#include <android/log.h>
#define LOG_TAG "MyNativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
void process_data(int value) {
LOGD("process_data called with value = %d", value);
if (value < 0) {
LOGE("Invalid value: %d, must be non-negative", value);
return;
}
// 正常处理逻辑
LOGD("Processing value %d successfully", value);
}
这里我用了宏定义来简化调用。LOGD和LOGE分别对应调试和错误日志。你想想看,如果每次都要写ANDROID_LOG_DEBUG和LOG_TAG,多累啊。
#ifdef NDEBUG
#define LOGD(...) ((void)0)
#else
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#endif
这样Release构建时,LOGD就变成了空操作,不会产生任何性能开销。
13.4 知识体系结构图
下面这张图,我把本章的核心知识点串起来了。你看一遍,心里就有谱了。
13.5 实战:在CMake中集成log库
光说不练假把式。咱们写一个完整的CMakeLists.txt,把log库用起来。
cmake_minimum_required(VERSION 3.18)
project("logdemo")
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
# 查找系统库
find_library(log-lib log)
find_library(z-lib z)
# 添加自己的库
add_library(
native-log
SHARED
src/main/cpp/native_log.cpp
)
# 链接系统库
target_link_libraries(
native-log
${log-lib}
${z-lib}
)
# 指定头文件路径(如果有自己的头文件)
target_include_directories(
native-log
PRIVATE
src/main/cpp/include
)
对应的C++代码:
// native_log.cpp
#include <jni.h>
#include <android/log.h>
#define TAG "NativeLog"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
extern "C" JNIEXPORT void JNICALL
Java_com_example_logdemo_MainActivity_nativeInit(JNIEnv* env, jobject thiz) {
LOGI("Native initialization started");
// 模拟一些操作
int result = 42;
LOGI("Computation result: %d", result);
// 模拟错误
if (result < 0) {
LOGE("Unexpected negative result: %d", result);
}
LOGI("Native initialization completed");
}
这段代码编译后,你在Logcat里过滤"NativeLog"标签,就能看到输出的日志了。
- 系统库是Android系统预编译的.so,通过NDK头文件调用
- CMake链接系统库推荐用find_library + target_link_libraries
- __android_log_print是NDK日志的核心API,支持printf风格的格式化
- 用宏封装日志调用,可以统一管理tag和优先级
- 发布版本务必去掉DEBUG日志,避免性能损耗和信息泄露
好了,这一章的内容就到这儿。系统库这块儿,说白了就是「知道有哪些、知道怎么用、知道怎么链接」。你把这些搞清楚了,后面遇到OpenGL、音频处理、动态加载这些高级话题时,就不会觉得陌生了。