Android 7系统日志(四)日志写入接口—Java层与Native层

系列目录第一篇:全景图与架构概览 | 第二篇:logd守护进程---启动、初始化与Socket通信 | 第三篇:liblog库---日志写入的完整链路 | 第四篇:日志写入接口---Java层与Native层 | 第五篇:日志读取---logcat源码深度分析 | 第六篇:日志缓冲区管理---容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析


本篇面向开发者,讲清各种日志接口的源码实现与使用场景。所有接口最终都收敛到 __android_log_buf_write()

一、写入接口全景图

复制代码
Java层                          Native层 (liblog)
─────────────────────────────────────────────────
Log.d(TAG, msg)      ────┐
Slog.d(TAG, msg)     ────┤
EventLog.writeEvent()────┤    __android_log_buf_write(bufID, prio, tag, msg)
Rlog.d(TAG, msg)     ────┤              │
                          │              ▼
ALOGD("msg")         ────┘    write_to_log() → sendmsg(logdw)
ALOGI/ALOGW/ALOGE    ────┐
                          │

二、android.util.Log --- 应用层日志(源码分析)

java 复制代码
// frameworks/base/core/java/android/util/Log.java

public final class Log {
    public static final int VERBOSE = 2;
    public static final int DEBUG = 3;
    public static final int INFO = 4;
    public static final int WARN = 5;
    public static final int ERROR = 6;
    public static final int ASSERT = 7;

    // 核心方法:所有 Log.d/v/i/w/e 最终调用这里
    public static int println_native(int bufID, int priority,
                                     String tag, String msg) {
        // Native 方法,实现在 android_util_Log.cpp 中
    }

    // ── d/v/i/w/e 快捷方法 ──

    public static int d(String tag, String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }
    public static int v(String tag, String msg) {
        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
    }
    public static int i(String tag, String msg) {
        return println_native(LOG_ID_MAIN, INFO, tag, msg);
    }
    public static int w(String tag, String msg) {
        return println_native(LOG_ID_MAIN, WARN, tag, msg);
    }
    public static int e(String tag, String msg) {
        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
    }

    // ── wtf (What a Terrible Failure) ──
    public static int wtf(String tag, String msg) {
        return println_native(LOG_ID_MAIN, ASSERT, tag, msg);
    }

    // ── 带异常参数的版本 ──
    public static int d(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, DEBUG, tag,
                              msg + '\n' + getStackTraceString(tr));
    }

    // ── 日志开关检测 ──
    public static boolean isLoggable(String tag, int level) {
        // 读取系统属性 log.tag.<TAGNAME>
        // 例如:adb shell setprop log.tag.MyTag DEBUG
        // 返回 true 时才打印,用于性能优化
    }
}

关键常量

java 复制代码
// 缓冲区 ID
static final int LOG_ID_MAIN   = 0;
static final int LOG_ID_RADIO  = 1;
static final int LOG_ID_EVENTS = 2;
static final int LOG_ID_SYSTEM = 3;
static final int LOG_ID_CRASH  = 4;

三、android_util_Log.cpp --- JNI 桥接层

cpp 复制代码
// frameworks/base/core/jni/android_util_Log.cpp

// println_native 的 JNI 实现
static jint android_util_Log_println_native(
    JNIEnv* env, jobject clazz,
    jint bufID, jint priority,
    jstring tagObj, jstring msgObj) {

    // ── 步骤1:从 Java 字符串获取 C 字符串 ──
    const char* tag = env->GetStringUTFChars(tagObj, NULL);
    const char* msg = env->GetStringUTFChars(msgObj, NULL);

    // ── 步骤2:参数处理 ──
    // 注意:priority 需要映射!
    // Java 层 VERBOSE=2, DEBUG=3, INFO=4, WARN=5, ERROR=6, ASSERT=7
    // Native 层 ANDROID_LOG_VERBOSE=2, ANDROID_LOG_DEBUG=3, ...
    // 两者恰好一致,无需转换

    // ── 步骤3:调用 liblog 核心函数 ──
    int res = __android_log_buf_write(bufID, (android_LogPriority)priority,
                                       tag, msg);

    // ── 步骤4:释放 C 字符串 ──
    env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

// JNI 方法注册表
static JNINativeMethod gMethods[] = {
    { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I",
      (void*)android_util_Log_println_native },
    // ...
};

JNI 层极简 :Java 层的所有重载(d/v/i/w/e + Throwable)在进入 JNI 之前就已展开为 tag+msg 字符串,JNI 层只需透传参数到 __android_log_buf_write()

四、android.util.Slog --- 系统框架日志

java 复制代码
// frameworks/base/core/java/android/util/Slog.java

public final class Slog {
    private Slog() {}  // 不可实例化

    public static int d(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
    }
    public static int e(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
    }
    // ... 与 Log.java 完全相同的 API,唯一区别:bufID = LOG_ID_SYSTEM
}

Slog 与 Log 的唯一区别bufID 参数不同。

  • Log.d()LOG_ID_MAIN(0) → logd 的 main 缓冲区
  • Slog.d()LOG_ID_SYSTEM(3) → logd 的 system 缓冲区

两者共享 Log.javaprintln_native() JNI 方法。

日志命名约定

Android 7 源码中系统服务的 TAG 命名通常使用服务名缩写:

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
static final String TAG = "ActivityManager";
Slog.d(TAG, "Some system message");

// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
static final String TAG = "WindowManager";

五、android.util.EventLog --- 事件日志

java 复制代码
// frameworks/base/core/java/android/util/EventLog.java

public class EventLog {
    // event-log-tags 文件中定义的 tag → 编号映射
    // 例如:42 am_proc_start (User|1|5),(PID|1|5)...

    public static int writeEvent(int tag, Object... values) {
        return writeEvent(tag, Arrays.asList(values));
    }

    public static int writeEvent(int tag, List<Object> list) {
        // ── 步骤1:将事件编码为二进制格式 ──
        // 每个 value 按照其类型编码:
        // Integer → 4 字节小端
        // Long    → 8 字节小端
        // String  → 4 字节长度 + UTF-8 字符串
        // Float   → 4 字节 IEEE 754

        byte[] bytes = encodeEvents(list);

        // ── 步骤2:写入 events 缓冲区 ──
        // 底层仍然调用 println_native(LOG_ID_EVENTS, ...)
        return Log.println_native(LOG_ID_EVENTS,
                                   ANDROID_LOG_INFO,
                                   Integer.toString(tag),
                                   new String(bytes, "ISO-8859-1"));
    }
}

EventLog 的特殊之处

  • 写入 LOG_ID_EVENTS(2) 缓冲区
  • 日志内容为二进制编码(非纯文本字符串)
  • event-log-tags 文件定义 tag 编号与字段类型的映射
  • logcat -b events 读取时会根据映射表解码为可读文本

event-log-tags 文件示例

复制代码
# system/core/logcat/event.logtags
42  am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
43  am_proc_bound (User|1|5),(PID|1|5),(Process Name|3)
44  am_anr        (User|1|5),(PID|1|5),(Package Name|3),(Flags|1|5)
...

格式:<tag编号> <tag名称> (<字段1名称>|<类型>|<字节数>),...

六、android.util.Rlog --- 无线通信日志

java 复制代码
// 与 Log 完全相同的实现,仅 bufID 不同
public static int v(String tag, String msg) {
    return Log.println_native(Log.LOG_ID_RADIO, Log.VERBOSE, tag, msg);
}

七、Java 层四大接口对比

接口 缓冲区 bufID 格式 典型用途
Log.d() main 0 纯文本 应用调试
Slog.d() system 3 纯文本 系统服务日志
EventLog.writeEvent() events 2 二进制 系统关键事件
Rlog.d() radio 1 纯文本 无线通信调试

所有接口都收敛到同一个 JNI 方法Log.println_native(bufID, priority, tag, msg)

八、Native 层日志宏 --- ALOGD 系列

cpp 复制代码
// system/core/include/log/log.h

// ── 日志宏定义 ──
#ifndef LOG_TAG
#define LOG_TAG NULL  // 必须在 include 前定义
#endif

// ── 带 isLoggable 检查的宏(条件编译优化) ──
#if LOG_NDEBUG
#define ALOGV(...)   ((void)0)  // 发布版直接编译为空
#else
#define ALOGV(...)   __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#endif

#define ALOGD(...)   __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGI(...)   __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...)   __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define ALOGE(...)   __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// ── 条件编译的 DEBUG 和非 DEBUG 版本 ──
#ifndef LOG_NDEBUG
#define IF_ALOGV if (1)
#else
#define IF_ALOGV if (0)
#endif

ALOGV 的特殊控制

cpp 复制代码
// 使用方式:
// 文件开头:#define LOG_NDEBUG 0  (启用 verbose)
// 或通过 Android.mk:LOCAL_CFLAGS += -DLOG_NDEBUG=0

// 构建系统默认行为:
// userdebug/eng 版本:LOG_NDEBUG = 0(输出 ALOGV)
// user 版本:LOG_NDEBUG = 1(ALOGV 编译为空)

九、__android_log_print() 宏展开 --- 通往 liblog 的最后一公里

cpp 复制代码
// system/core/include/log/log_main.h

#define __android_log_print(prio, tag, fmt...) \
    __android_log_buf_print(LOG_ID_MAIN, prio, tag, fmt)

// __android_log_buf_print 内部实现:
int __android_log_buf_print(int bufID, int prio,
                            const char* tag, const char* fmt, ...) {
    va_list ap;
    char buf[LOG_BUF_SIZE];  // 栈上缓冲区,通常 1024 字节

    va_start(ap, fmt);
    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);  // 格式化字符串
    va_end(ap);

    return __android_log_buf_write(bufID, prio, tag, buf);
}

完整的宏展开链路

复制代码
ALOGD("value=%d", x)
    │  展开
    ▼
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "value=%d", x)
    │  展开
    ▼
__android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_DEBUG, LOG_TAG, "value=%d", x)
    │  内部调用
    ▼
vsnprintf → 格式化字符串
__android_log_buf_write(LOG_ID_MAIN, ANDROID_LOG_DEBUG, LOG_TAG, "value=42")

十、各接口的缓冲区映射总表

接口 bufID 缓冲区 权限要求
Log.d/v/i/w/e/wtf() 0 main 任何进程
ALOGD/V/I/W/E/F() 0 main 任何进程
Slog.d/v/i/w/e() 3 system 系统进程 (UID=1000)
EventLog.writeEvent() 2 events 系统服务
Rlog.d() 1 radio rild 及无线相关进程
(内部) 4 crash 系统崩溃处理

普通应用只能写入 main 缓冲区,这是 Android 安全模型的一部分。尝试写 system/events/radio 会被 SELinux 或 UID 权限检查阻止。

十一、性能优化建议

使用 isLoggable() 前置判断

java 复制代码
// ❌ 不好的写法 --- 即使不输出,字符串拼接仍会执行
Log.d(TAG, "Processing item: " + heavyToString(obj));

// ✅ 好的写法 --- 不满足条件时跳过字符串拼接
if (Log.isLoggable(TAG, Log.DEBUG)) {
    Log.d(TAG, "Processing item: " + heavyToString(obj));
}

Native 层使用 IF_ALOGV/RLOGV

cpp 复制代码
// ❌ ALOGV 内部的 vsnprintf 即使最终不输出也会执行
ALOGV("complex data: %s", expensive_format(data));

// ✅ 使用 IF_ALOGV 保护
IF_ALOGV {
    ALOGV("complex data: %s", expensive_format(data));
}

日志级别的选择策略

级别 何时使用
VERBOSE 详细调试信息,不应出现在发布版中
DEBUG 开发调试信息,user 版本可关闭
INFO 值得记录的正常事件(如启动完成、连接成功)
WARN 异常但可恢复的情况
ERROR 错误、失败的操作
ASSERT/wtf "不可能发生"的情况,应视为 bug

十二、本篇总结

  • Java 层四个接口(Log/Slog/EventLog/Rlog)的唯一区别是 bufID 参数
  • JNI 层极简透传,不做额外处理
  • Native 层 ALOGD 宏通过 vsnprintf 格式化后同样调用 __android_log_buf_write()
  • LOG_NDEBUG 控制 verbose 日志的编译时开关
  • 所有调用最终汇聚到同一个函数:__android_log_buf_write()

下一篇将分析 logcat 的源码,看它如何从 logd 读取、过滤和格式化日志。