技术标签: android 8.1 android源码学习 log 源码 Android
提供了一些格式化输出、美观
// 添加依赖
implementation 'com.orhanobut:logger:2.2.0'
// 初始化
Logger.addLogAdapter(new AndroidLogAdapter());
// 使用
Logger.d("hello,Android");
基于原生Log
类的小型可扩展的log框架
使用注解形式的调试版本log框架
可扩展,支持多种数据格式,支持线程和调用栈信息
支持多种数据结构,支持系统对象,支持高性能写入文件(mmap)
基于mmap内存映射,最大化保证日志完整性
Android 系统 Java 层 Log 定义在 /frameworks/base/core/java/android/util/Log.java
Log 提供了六种日志级别,并定义了一系列静态方法:
public static int v/d/i/w/e/wtf(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int v/d/i/w/e/wtf(String tag, String msg, Throwable tr) {
return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}
下面这种调用带异常记录,会调用 Log 类内部类 ImediateLogWriter
来写入日志消息,最终也会调用 println_native
。
说明:Android 中不同的 log 会指定不同的缓冲区然后被写入到不同的设备中,包括 system(系统相关)、radio(无线/电话相关)、event(事件相关)、main(主缓冲区,默认)
本地实现定义在 /frameworks/base/core/jni/android_util_Log.cpp
static const JNINativeMethod gMethods[] = {
{
"isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{
"println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
{
"logger_entry_max_payload_native", "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};
android_util_Log_isLoggable
isLoggable
判断
__android_log_is_loggable
获取 logLevel【/system/core/liblog/properties.c,编译成 liblog.so】android_util_Log_println_native
__android_log_buf_write
【/system/core/liblog/logger_write.c,编译到 liblog.so】Native 层通过定义一系列宏的方式提供 log 功能,全部是调用了 __android_log_print
LIBLOG_ABI_PUBLIC int __android_log_print(int prio, const char* tag,
const char* fmt, ...) {
va_list ap;
char buf[LOG_BUF_SIZE];
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
return __android_log_write(prio, tag, buf);
}
这个过程使用了可变参数,va 就是 variable-argument,相关宏定义在 stdarg.h 中
最后函数会调用 __android_log_write
LIBLOG_ABI_PUBLIC int __android_log_write(int prio, const char* tag,
const char* msg) {
return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
}
这里 Java/C++ 层就走到同一个函数,在这个函数中会实现写设备文件
liblog.so 会被所有需要日志操作的进程加载,负责处理打印和读取日志的流程
主要代码及逻辑都在 /system/core/liblog/logger_write.c 中,__android_log_buf_write
里面做了下面这几件事:
定义 iovec 结构数组 vec[3],三个元素分别存放 prio、tag、msg
struct iovec {
void* iov_base;
size_t iov_len;
};
iovec 结构体包含一个指向缓冲区的指针和读/写的长度
判断 bufID 修改 tag(bufID 这个变量表示不同的 log 缓冲区,或者理解成写到不同的文件里)
最后调用 write_to_log
write_to_log
是一个函数指针,初始设置指向 __write_to_log_init
,进入 __write_to_log_init
后,首先会去调用 __write_to_log_initialize
,然后将 write_to_log
设置为指向 __write_to_log_daemon
,然后又调用一次 write_to_log
static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init;
static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
__android_log_lock();
if (write_to_log == __write_to_log_init) {
int ret;
ret = __write_to_log_initialize();
if (ret < 0) {
__android_log_unlock();
if (!list_empty(&__android_log_persist_write)) {
__write_to_log_daemon(log_id, vec, nr);
}
return ret;
}
write_to_log = __write_to_log_daemon;
}
__android_log_unlock();
return write_to_log(log_id, vec, nr);
}
__write_to_log_initialize
和 __write_to_log_daemon
实现太复杂了,总结下来就是 initialize 基于默认配置构造结构体链表,daemon 从链表中取出节点,就是 android_log_transport_write
,节点的结构体的定义在liblog/logger.h 中
struct android_log_transport_write {
struct listnode node;
const char* name; /* human name to describe the transport */
unsigned logMask; /* mask cache of available() success */
union android_log_context context; /* Initialized by static allocation */
int (*available)(log_id_t logId); /* Does not cause resources to be taken */
int (*open)(); /* can be called multiple times, reusing current resources */
void (*close)(); /* free up resources */
int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr); /* write log to transport, returns number of bytes propagated, or -errno */
};
初始化 的时候会调用 __android_log_config_write
会基于不同的场景定义不同的结构去写日志,包括 localLoggerWrite、logdLoggerWrite、pmsgLoggerWrite、fakeLoggerWrite、stderrLoggerWrite,调用 retval = (*node->write)(log_id, &ts, vec, nr) 进行 write 操作
LIBLOG_HIDDEN void __android_log_config_write() {
if (__android_log_transport & LOGGER_LOCAL) {
extern struct android_log_transport_write localLoggerWrite;
__android_log_add_transport(&__android_log_transport_write,
&localLoggerWrite);
}
if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
#if (FAKE_LOG_DEVICE == 0)
extern struct android_log_transport_write logdLoggerWrite;
extern struct android_log_transport_write pmsgLoggerWrite;
__android_log_add_transport(&__android_log_transport_write, &logdLoggerWrite);
__android_log_add_transport(&__android_log_persist_write, &pmsgLoggerWrite);
#else
extern struct android_log_transport_write fakeLoggerWrite;
__android_log_add_transport(&__android_log_transport_write, &fakeLoggerWrite);
#endif
}
if (__android_log_transport & LOGGER_STDERR) {
extern struct android_log_transport_write stderrLoggerWrite;
if (list_empty(&__android_log_transport_write)) {
__android_log_add_transport(&__android_log_transport_write, &stderrLoggerWrite);
} else {
struct android_log_transport_write* transp;
write_transport_for_each(transp, &__android_log_transport_write) {
if (transp == &stderrLoggerWrite) {
return;
}
}
__android_log_add_transport(&__android_log_persist_write, &stderrLoggerWrite);
}
}
}
看其中比较重要的几个,logdLoggerWrite
的定义在 /system/core/liblog/logd_writer.c
LIBLOG_HIDDEN struct android_log_transport_write logdLoggerWrite = {
.node = {
&logdLoggerWrite.node, &logdLoggerWrite.node },
.context.sock = -EBADF,
.name = "logd",
.available = logdAvailable,
.open = logdOpen,
.close = logdClose,
.write = logdWrite,
};
其中,logdOpen 方法创建 sockaddr_un 结构体并将 “/dev/socket/logdw” 写入 sun_path 成员变量中,然后调用 connect 去建立连接,并将套接字标识放到 logdLoggerWrite.context.sock 中;write 函数指针指向 logdWrite
方法,会调用 writev
非阻塞地写入日志信息
同理,pmsgLoggerWrite 打开的是 “/dev/pmsg0” 的 socket
所以,liblog.so 的一个主要工作就是写入日志
使用 file 命令查看 /dev/socket/logdw 发现是一个 socket,这个 socket 是由 logd 创建的,见 /system/corelogd/logd.rc
service logd /system/bin/logd
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram+passcred 0222 logd logd
file /proc/kmsg r
file /dev/kmsg w
user logd
group logd system package_info readproc
writepid /dev/cpuset/system-background/tasks
logd 是 C 层的守护进程,由 init 进程创建(创建 servicemanager 时同时创建),可以看到启动 logd 后会创建三个 socket,分别为 logd 用来监听命令、logdr 用于读日志、logdw 用于写日志,还打开了两个文件,修改了 user id 和 group id,并把 pid 写文件
logd 启动后会从 main 函数开始执行,见 /system/core/logd/main.cpp
第一步、打开 /dev/kmsg 用于写内核 log 的,在 logd 还未启动或出错时,只能写到内核日志中
int main(int argc, char* argv[]) {
......
static const char dev_kmsg[] = "/dev/kmsg";
fdDmesg = android_get_control_file(dev_kmsg);
if (fdDmesg < 0) {
fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
}
......
}
android_get_control_file 里面先调用 __android_get_control_from_env
拼接 Android 文件前缀 ANDROID_FILE_ 和路径 /dev/kmsg,然后做符号转换得到 ANDROID_FILE__dev_kmsg
,接下来通过 getenv 获取这个环境变量的值,最后通过 strtol 将这个值转换成 long 类型就是文件描述符,还要通过 fcntl 去验证下文件是不是开着
这里用了三种方法去验证:
__android_get_control_from_env
返回文件描述符后,还会调用 /proc/self/fd/fd_num 验证一次
第二步、打开 /proc/kmsg 用于读内核日志
int main(int argc, char* argv[]) {
......
bool klogd = __android_logger_property_get_bool(
"logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
if (klogd) {
static const char proc_kmsg[] = "/proc/kmsg";
fdPmesg = android_get_control_file(proc_kmsg);
if (fdPmesg < 0) {
fdPmesg = TEMP_FAILURE_RETRY(
open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
}
if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg);
}
......
}
第三步、启动 reinit 线程,处理 --reinit 命令,此外这个线程还会完成 uid 转 name
第四步、设置运行时优先级和权限【略】
第五步、启动 log 监听
看一下 logd 的线程,可以看到 logd 进程 pid = 574,ppid = 1,即 init 进程孵化,reinit_thread_start
函数启动线程“logd.daemon”,LogReader 启动线程“logd.reader”监听 /dev/socket/logdr,LogListener 启动线程“logd.writer”监听 /dev/socket/logdw,CommandListener 启动线程“logd.control”监听 /dev/socket/logd,LogAudit 启动线程“logd.auditd”,LogKlog 启动线程“logd.klogd”,LogTimeEntry 启动线程“logd.reader.per”
logd 574 574 1 32428 4912 SyS_rt_sigsuspend 753d7b1634 S logd
logd 574 577 1 32428 4912 futex_wait_queue_me 753d7644b0 S logd.daemon
logd 574 578 1 32428 4912 do_select 753d7b15a4 S logd.reader
logd 574 579 1 32428 4912 do_select 753d7b15a4 S logd.writer
logd 574 580 1 32428 4912 do_select 753d7b15a4 S logd.control
logd 574 582 1 32428 4912 do_select 753d7b15a4 S logd.auditd
logd 574 14402 1 32428 4912 futex_wait_queue_me 753d7644b0 S logd.reader.per
LogBuffer 继承自 LogBufferInterface,类内部定义了很多成员变量和一些函数,包括:
LogBufferElementCollection 是一个 LogBufferElement 指针类型的 list
LogBufferElement 中存放 LogBuffer 元素的相关信息,包括 uint32_t 类型的 uid/pid/tid,还有 realTime、消息内容 msg,logId 等;
LastLogTimes 是一个 LogTimeEntry 指针类型的 list,两者都定义在 LogTime 中,LogTimeEntry 里面包
文章浏览阅读1.5k次。电脑店超级U盘装系统-设置U盘启动(V5.0 UD+ISO二合一版 )2012-11-30 21:02 点击进入电脑店超级U盘装系统-程序下载和运行(V5.0 UD+ISO二合一版 ) 点击进入电脑店超级U盘装系统-个性化设置和升级安装(V5.0 UD+ISO二合一版 ) 点击进入电脑店超级U盘装系统-设置U盘启动(V5.0 UD_电脑店u盘装系统有捆绑吗
文章浏览阅读4.7k次,点赞9次,收藏25次。同一物体同时开启NavMeshAgent和Rigidbody时,经常会发生一些意想不到的受力问题,经实测,NavMeshAgent在自动导航时,并不是直接改变物体的位移,会赋予物体一定的速度。关于组件:参考:Rigidbody、CharacterController和NavMeshAgent的区别Rigidbody是用来模拟真实物理效果的,它可以设置重力,可以为对象施加外力。注意它和..._navmeshagent不能和物理效果同时生效吗
文章浏览阅读275次。临近国庆,又回过头来鼓捣docker,因为从事php开发,所以还是先从环境入手。本来考虑搭建php+mysql+nginx+redis全部,但是由于使用的都是公司的mysql和redis,故只搭建php+nginx,因为我的操作系统是win10,一下操作都是在win下完成的。首先先拉取镜像,当然你也可以自己编写dockerfile去构建自己的镜像。这里先拉取nginx镜像:docker pu..._dockerfile 安装php+nginx
文章浏览阅读1.2k次,点赞41次,收藏16次。计算机毕业设计吊打导师Hadoop+Hive+PySpark旅游景点推荐 旅游推荐系统 景区游客满意度预测与优化 Apriori算法 景区客流量预测 旅游大数据 景点规划 知识图谱 机器学习 深度学习 人工智能
文章浏览阅读1.2k次。阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android本篇文章主要介绍开发中我们没有源码的GMS Crash崩溃后的解决方案,通过阅读本篇文章,您将收获以下内容:一、gms.ui Service not registered CrashGMS(GoogleMobile Service)包是出口国外手机中Google限制必须要预制的,如果不预置无法过Google CTS认证,会导致手...
文章浏览阅读10w+次,点赞880次,收藏3.9k次。1、什么是进程?什么是线程?进程是:一个应用程序(1个进程是一个软件)。线程是:一个进程中的执行场景/执行单元。注意:一个进程可以启动多个线程。eg.对于java程序来说,当在DOS命令窗口中输入:java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。3、进程和线程是什么关系?_java多线程
文章浏览阅读2.8k次。BodyIBM i安全邮件配置和常见故障排除方法简介:电子邮件是现在普遍使用的一种通信方式,为了提高通信过程中的安全并且保护邮件内容不被泄露,IBM i SMTP增加了对TLS的支持, 通过此技术保障了邮件通信过程中的安全和数据的不被篡改。本文提供了IBM i V7R2及以上版本安全邮件的配置方法和常见故障排除方法。术语缩写:SMTP:Simple Mail Transfer ProtocolTL..._邮件dcm
文章浏览阅读402次。创建VLAN的指令:VLAN +ID号(取值范围1-4094,1是默认存在)用来连接终端设备的(pc,打印机,监控摄像头)片面认为,access接口下面接的一定是终端。交换网络本身的工作原理是什么?交换机是基于MAC地址表转发的。hybrid(华为特有)_2019vlan
文章浏览阅读4.8k次,点赞3次,收藏44次。静态手势识别总体方案0.说明1.实现目标2.实现步骤1)总体思路2)每部分效果基于高斯肤色模型和动态阈值的手势分割基于Canny算法的轮廓提取基于Hu矩的量化基于傅里叶描述子的量化分类融合特征分类其他尝试0.说明静态手势识别是2019年四五月份做的一次设计,实验平台是Matlab。主要针对静态手势,采用肤色模型分离手部区域,提取手势的轮廓信息,采用不同的描述方式进行量化,最后采用BP神经网络和..._手势识别有哪些方案
文章浏览阅读1.4w次。##机器学习(Machine Learning)&深度学习(Deep Learning)资料(Chapter 1)---#####注:机器学习资料[篇目一](https://github.com/ty4z2008/Qix/blob/master/dl.md)共500条,[篇目二](https://github.com/ty4z2008/Qix/blob/master/dl2.md)开始更新...
文章浏览阅读9.7k次。GTX650刷入UEFI模块gtx650黑苹果需要关闭csm模式,但是微软规定csm模式关闭必须得所有pcie设备开启uefi模式,但是老的gtx650中vbios没有uefi模块,所以需要先刷vbios,刷入方法在我的资源解压后的Inno3D UEFI Updater中的readme中,前提需要开启主板uefi模式,最好用gpuz备份好原来的bios。下面是简单的步骤,仅供参考,目前是实现了华硕GTX650-FMLII-1GD5的UEFI刷入,其他的显卡并没有实物卡测试。打开主板UEFI模式:._gtx650 uefi
文章浏览阅读2.6w次,点赞26次,收藏269次。拆分输出字符串求n阶方阵里所有数的和合法的三角形个数整型数组求整数对最小和机器人走迷宫【2022 Q1 Q2 |200分】数格子两个超大整型数相加字符串格式化输出【2022 Q1 Q2 |100分】树形目录操作【2022 Q1 Q2 |200分】整型数组按个位值排序奥运会奖牌榜的排名【2022 Q1 | 100分】无重复字符的最长子串最长回文子串两个字符串的最长公共子串括号生成字符串处理一个正整数到 Excel 编号之间的转换字符串压缩搜索矩阵免单统计数组的转换藏宝_华为机试垂直矩阵