当前位置: 首页 > news >正文

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 — 应用层日志(源码分析)

// frameworks/base/core/java/android/util/Log.javapublicfinalclassLog{publicstaticfinalintVERBOSE=2;publicstaticfinalintDEBUG=3;publicstaticfinalintINFO=4;publicstaticfinalintWARN=5;publicstaticfinalintERROR=6;publicstaticfinalintASSERT=7;// 核心方法:所有 Log.d/v/i/w/e 最终调用这里publicstaticintprintln_native(intbufID,intpriority,Stringtag,Stringmsg){// Native 方法,实现在 android_util_Log.cpp 中}// ── d/v/i/w/e 快捷方法 ──publicstaticintd(Stringtag,Stringmsg){returnprintln_native(LOG_ID_MAIN,DEBUG,tag,msg);}publicstaticintv(Stringtag,Stringmsg){returnprintln_native(LOG_ID_MAIN,VERBOSE,tag,msg);}publicstaticinti(Stringtag,Stringmsg){returnprintln_native(LOG_ID_MAIN,INFO,tag,msg);}publicstaticintw(Stringtag,Stringmsg){returnprintln_native(LOG_ID_MAIN,WARN,tag,msg);}publicstaticinte(Stringtag,Stringmsg){returnprintln_native(LOG_ID_MAIN,ERROR,tag,msg);}// ── wtf (What a Terrible Failure) ──publicstaticintwtf(Stringtag,Stringmsg){returnprintln_native(LOG_ID_MAIN,ASSERT,tag,msg);}// ── 带异常参数的版本 ──publicstaticintd(Stringtag,Stringmsg,Throwabletr){returnprintln_native(LOG_ID_MAIN,DEBUG,tag,msg+'\n'+getStackTraceString(tr));}// ── 日志开关检测 ──publicstaticbooleanisLoggable(Stringtag,intlevel){// 读取系统属性 log.tag.<TAGNAME>// 例如:adb shell setprop log.tag.MyTag DEBUG// 返回 true 时才打印,用于性能优化}}

关键常量

// 缓冲区 IDstaticfinalintLOG_ID_MAIN=0;staticfinalintLOG_ID_RADIO=1;staticfinalintLOG_ID_EVENTS=2;staticfinalintLOG_ID_SYSTEM=3;staticfinalintLOG_ID_CRASH=4;

三、android_util_Log.cpp — JNI 桥接层

// frameworks/base/core/jni/android_util_Log.cpp// println_native 的 JNI 实现staticjintandroid_util_Log_println_native(JNIEnv*env,jobject clazz,jint bufID,jint priority,jstring tagObj,jstring msgObj){// ── 步骤1:从 Java 字符串获取 C 字符串 ──constchar*tag=env->GetStringUTFChars(tagObj,NULL);constchar*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 核心函数 ──intres=__android_log_buf_write(bufID,(android_LogPriority)priority,tag,msg);// ── 步骤4:释放 C 字符串 ──env->ReleaseStringUTFChars(tagObj,tag);env->ReleaseStringUTFChars(msgObj,msg);returnres;}// JNI 方法注册表staticJNINativeMethod 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 — 系统框架日志

// frameworks/base/core/java/android/util/Slog.javapublicfinalclassSlog{privateSlog(){}// 不可实例化publicstaticintd(Stringtag,Stringmsg){returnLog.println_native(Log.LOG_ID_SYSTEM,Log.DEBUG,tag,msg);}publicstaticinte(Stringtag,Stringmsg){returnLog.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 命名通常使用服务名缩写:

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.javastaticfinalStringTAG="ActivityManager";Slog.d(TAG,"Some system message");// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.javastaticfinalStringTAG="WindowManager";

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

// frameworks/base/core/java/android/util/EventLog.javapublicclassEventLog{// event-log-tags 文件中定义的 tag → 编号映射// 例如:42 am_proc_start (User|1|5),(PID|1|5)...publicstaticintwriteEvent(inttag,Object...values){returnwriteEvent(tag,Arrays.asList(values));}publicstaticintwriteEvent(inttag,List<Object>list){// ── 步骤1:将事件编码为二进制格式 ──// 每个 value 按照其类型编码:// Integer → 4 字节小端// Long → 8 字节小端// String → 4 字节长度 + UTF-8 字符串// Float → 4 字节 IEEE 754byte[]bytes=encodeEvents(list);// ── 步骤2:写入 events 缓冲区 ──// 底层仍然调用 println_native(LOG_ID_EVENTS, ...)returnLog.println_native(LOG_ID_EVENTS,ANDROID_LOG_INFO,Integer.toString(tag),newString(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 — 无线通信日志

// 与 Log 完全相同的实现,仅 bufID 不同publicstaticintv(Stringtag,Stringmsg){returnLog.println_native(Log.LOG_ID_RADIO,Log.VERBOSE,tag,msg);}

七、Java 层四大接口对比

接口缓冲区bufID格式典型用途
Log.d()main0纯文本应用调试
Slog.d()system3纯文本系统服务日志
EventLog.writeEvent()events2二进制系统关键事件
Rlog.d()radio1纯文本无线通信调试

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

八、Native 层日志宏 — ALOGD 系列

// system/core/include/log/log.h// ── 日志宏定义 ──#ifndefLOG_TAG#defineLOG_TAGNULL// 必须在 include 前定义#endif// ── 带 isLoggable 检查的宏(条件编译优化) ──#ifLOG_NDEBUG#defineALOGV(...)((void)0)// 发布版直接编译为空#else#defineALOGV(...)__android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)#endif#defineALOGD(...)__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)#defineALOGI(...)__android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)#defineALOGW(...)__android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)#defineALOGE(...)__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)// ── 条件编译的 DEBUG 和非 DEBUG 版本 ──#ifndefLOG_NDEBUG#defineIF_ALOGVif(1)#else#defineIF_ALOGVif(0)#endif

ALOGV 的特殊控制

// 使用方式:// 文件开头:#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 的最后一公里

// 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(intbufID,intprio,constchar*tag,constchar*fmt,...){va_list ap;charbuf[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()0main任何进程
ALOGD/V/I/W/E/F()0main任何进程
Slog.d/v/i/w/e()3system系统进程 (UID=1000)
EventLog.writeEvent()2events系统服务
Rlog.d()1radiorild 及无线相关进程
(内部)4crash系统崩溃处理

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

十一、性能优化建议

使用 isLoggable() 前置判断

// ❌ 不好的写法 — 即使不输出,字符串拼接仍会执行Log.d(TAG,"Processing item: "+heavyToString(obj));// ✅ 好的写法 — 不满足条件时跳过字符串拼接if(Log.isLoggable(TAG,Log.DEBUG)){Log.d(TAG,"Processing item: "+heavyToString(obj));}

Native 层使用 IF_ALOGV/RLOGV

// ❌ 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 读取、过滤和格式化日志。

http://www.jsqmd.com/news/1106039/

相关文章:

  • Codex 插件生态全景:从官方工具到社区神器
  • 工程化应用基础设施:可观测性要覆盖 提示词、检索和执行
  • HBM Predictor安装与配置教程:简单5步搭建预测环境
  • Visa、Stripe等140余家机构联合推出Open USD稳定币,剑指Tether
  • 第92题 IGBT模块封装用高可靠铝线键合与铜线键合
  • 2026手机证件照制作工具实操指南:免费无水印软件梳理与收费坑避雷
  • Windows安卓应用安装神器:APK Installer完全指南 - 3分钟掌握跨平台应用管理
  • 年入100亿压缩机龙头IPO!1.66亿诉讼案未决,应收账款质量恶化
  • 让大模型跑在小芯片上:工程挑战比口号更硬
  • 番茄小说下载器终极指南:三分钟打造个人离线图书馆的完整教程
  • 记录:2026.7.1
  • 告别复杂配置!Claude Code完整安装指南,小白也能10分钟上手(Linux/WSL2)
  • 从 Hermes Agent 到 Harness 工程:AI Agent 落地,靠的不只是大模型
  • 单帧像素推演三维空间,SpaceOS联动Pixel2Geo打通单画面实景重建全链路
  • YOLOv11 改进 - C2PSA C2PSA融合EDFFN高效判别频域前馈网络(CVPR 2025):频域筛选机制增强细节感知,优化复杂场景目标检测
  • novel-downloader:三步搞定网络小说永久保存的终极指南
  • ChatGPT Plus / Pro 付款后没看到结果,先查这几步
  • 原生Signals正式落地、管道操作符终结“嵌套地狱”、WebNN调用NPU算力——4个让前端代码“减重”50%的ES2026特性
  • 孩子确诊自闭症/多动症后该找谁?一份给迷茫家长的专业参考指南
  • 软件设计周期
  • 卡梅德生物科普:CD70(TNFSF7)的免疫共刺激机制与研究应用
  • 基于SpringBoot+Vue的日常办公用品直售推荐系统管理系统设计与实现【Java+MySQL+MyBatis完整源码】
  • 类成员变量的初始化 _
  • M4Markets的长期使用感受顺不顺手?
  • AI服装设计工作流拆解:为什么说下一站不是“AI画图工具”,而是“垂直AI设计平台”
  • 核心数据结构设计
  • 检索增强从零落地:检索增强系统的索引、召回与评测
  • ·系统建模与UML应用
  • 功能极简取舍:每个按钮都要为用户承担重量
  • AI 辅助:Node.js 与 Go 后端选型:高并发不是唯一判断标准