Android C语言log如何高效调试?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

在 Android 系统中,C/C++ 代码的日志输出与我们熟悉的 Java/Kotlin 的 Log 类不同,它使用的是一套源自 Linux 内核的、更底层的日志系统,这套系统通过一个名为 liblog 的库来实现,其中最核心的函数是 __android_log_write 和一系列封装好的宏。

android c语言log
(图片来源网络,侵删)

核心函数:__android_log_write

这是最基础、最核心的日志写入函数。

函数原型

#include <android/log.h>
int __android_log_write(int priority, const char* tag, const char* text);

参数说明

  • priority: 日志的优先级/级别,这是一个整数值,系统定义了几个宏来代表不同的级别。
  • tag: 日志标签,这是一个字符串,用于标识日志的来源(通常是你的模块名或类名),方便在 logcat 中过滤。强烈建议使用一个简短且唯一的标签
  • text: 日志的具体内容,这是一个字符串。

日志优先级 (Priority)

Android 定义了以下日志级别,按重要性从高到低排序:

宏定义 含义 描述
ANDROID_LOG_FATAL 6 致命 将导致进程终止的严重错误。
ANDROID_LOG_ERROR 3 错误 普通错误。
ANDROID_LOG_WARN 4 警告 潜在问题。
ANDROID_LOG_INFO 2 信息 一般性信息。
ANDROID_LOG_DEBUG 1 调试 调试信息,仅在调试版本中可用。
ANDROID_LOG_VERBOSE 5 详细 冗余的信息,非常多。
ANDROID_LOG_SILENT 0 静默 最低级别,什么都不会打印。

注意ANDROID_LOG_VERBOSE 的值是 5,比 WARN (4) 还高,这意味着它的日志级别更低,打印的内容更多,级别数值越小,日志越重要。


便捷宏:LOGV, LOGD, LOGI, LOGW, LOGE, LOGF

每次都写 __android_log_write 非常繁琐,Android 提供了一系列预定义的宏,它们会自动填充 tag 和优先级,让代码更简洁、更易读。

android c语言log
(图片来源网络,侵删)

这些宏通常这样定义:

#define LOG_TAG "MyNativeModule" // 定义一个全局的日志标签
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)

__android_log_print__android_log_write 的一个增强版,它支持 printf 风格的格式化字符串,非常实用。


完整实践步骤

下面我们通过一个完整的例子,展示如何在 C/C++ 代码中打印日志,并将其集成到 Android Studio 项目中。

步骤 1: 创建一个 Native C 模块

  1. 在 Android Studio 中,右键你的模块 -> New -> Folder -> JNI Folder
  2. app/src/main/jni 目录下,创建一个 C 源文件,native-log.c

步骤 2: 编写 C 代码

native-log.c 中,包含头文件并使用宏来打印日志。

android c语言log
(图片来源网络,侵删)
#include <jni.h>
#include <android/log.h>
// 定义一个全局的日志标签,方便统一管理
#define LOG_TAG "MyNativeLog"
// 定义日志宏
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// 这是一个 JNI 方法,我们在这里演示日志打印
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    // 使用不同的级别打印日志
    LOGD("This is a DEBUG message from C.");
    LOGD("The value of an integer is: %d", 42);
    LOGE("This is an ERROR message! Something might be wrong.");
    // 你也可以直接使用底层函数
    __android_log_write(ANDROID_LOG_INFO, LOG_TAG, "This is an INFO message using the low-level function.");
    return (*env)->NewStringUTF(env, "Hello from C with Logs!");
}

代码解释:

  • #include <android/log.h>: 包含日志功能头文件。
  • #define LOG_TAG "MyNativeLog": 定义一个标签,所有后续的日志都会带上这个标签。
  • LOGD(...)LOGE(...): 定义了两个便捷宏,分别对应 DEBUG 和 ERROR 级别。__VA_ARGS__ 是一个可变参数宏,会把所有传入的参数原封不动地传递给后面的函数。
  • __android_log_print: 用于格式化输出,就像 printf 一样。
  • __android_log_write: 直接输出字符串。

步骤 3: 配置 CMakeLists.txt

为了让编译器找到 <android/log.h> 并链接 log 库,你需要修改 app/CMakeLists.txt 文件。

# 在你的 CMakeLists.txt 中添加以下内容
# 查找并添加 log 库
find_library(log-lib log)
# 添加你的 native 源文件
add_library(
        native-lib          # 库名
        SHARED              # 共享库
        native-lib.cpp      # 你的 C++ 源文件
        native-log.c        # 添加你的 C 源文件
        )
# 链接 log 库到你的 native-lib
target_link_libraries(
        native-lib
        ${log-lib}
        )

关键点:

  • find_library(log-lib log): 查找 Android 系统提供的 log 库,并将其路径存入 log-lib 变量。
  • add_library(...): 确保将你的 .c 文件(native-log.c)添加到编译列表中。
  • target_link_libraries(...): 将 log-lib 链接到你的库上。这一步是必不可少的,否则链接时会报错。

步骤 4: 在 Java/Kotlin 代码中调用

在你的 MainActivity.java (或 .kt) 中,加载库并调用 JNI 方法。

package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
    // 加载库的名字要和 CMakeLists.txt 中定义的库名一致
    static {
        System.loadLibrary("native-lib");
    }
    // 声明一个 native 方法
    public native String stringFromJNI();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
}

步骤 5: 查看日志 (logcat)

  1. 在 Android Studio 底部,打开 Logcat 窗口。
  2. 在过滤器中,选择你的设备或模拟器。
  3. Log Tag 输入框中,输入你在 C 代码中定义的标签,即 MyNativeLog
  4. 你还可以在 Show only selected application 的下拉菜单中选择你的 App,以过滤掉其他应用的日志。

现在运行你的 App,你就能在 Logcat 中看到类似下面这样的输出:

// D/MyNativeLog: This is a DEBUG message from C.
// D/MyNativeLog: The value of an integer is: 42
// E/MyNativeLog: This is an ERROR message! Something might be wrong.
// I/MyNativeLog: This is an INFO message using the low-level function.

高级技巧与注意事项

条件编译 (Debug vs Release)

在 Release 版本中,通常我们不希望打印大量的调试日志,可以通过宏来控制。

CMakeLists.txt 中,你可以根据构建类型定义宏:

# 在 CMakeLists.txt 顶部添加
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")
endif()
# 在 add_library 之前添加
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    add_compile_definitions(LOG_DEBUG=1)
else()
    add_compile_definitions(LOG_DEBUG=0)
endif()

然后在你的 C 代码中这样使用:

#ifdef LOG_DEBUG
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#else
#define LOGD(...) // Do nothing
#endif
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// 现在在 Debug 模式下 LOGD 会打印,在 Release 模式下会被忽略
LOGD("This will only be printed in Debug builds.");
LOGE("This will always be printed.");

日志缓冲区

在高频率日志打印的场景下(如游戏循环),频繁调用 JNI 和日志系统可能会影响性能,一个优化技巧是先将日志信息写入一个内存缓冲区,然后定期(例如每帧)一次性将缓冲区的内容全部打印出来。

崩溃日志

当 C/C++ 代码发生段错误 (Segmentation Fault) 时,Java 层的 try-catch 是无法捕获的,这时,日志系统会打印出包含堆栈信息的 FATAL 级别日志,这对于分析崩溃原因至关重要,你可以通过 Android Studio 的 Analyze -> Inspect Code 或其他工具来捕获和分析这类崩溃日志。

功能 Java/Kotlin C/C++
核心类/函数 android.util.Log android/log.h
打印方法 Log.d(tag, msg) __android_log_print(...)LOGD(...)
日志级别 VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ..., ANDROID_LOG_FATAL
字符串,作为第一个参数 宏定义 #define LOG_TAG "..."
链接库 不需要 必须在 CMakeLists.txt 中链接 log

在 C/C++ 中使用日志,包含头文件定义标签宏使用便捷宏在 CMake 中链接 log 是四个关键步骤,熟练使用 C 日志将极大地方便你进行 NDK 开发和调试。

-- 展开阅读全文 --
头像
C语言parse error究竟是什么原因?
« 上一篇 01-24
织梦信息发布员为何取消审核?
下一篇 » 01-24

相关文章

取消
微信二维码
支付宝二维码

目录[+]