Android系统在应用程序框架层中定义了三个Java日志写入接口,它们分别是android.util.Log、android.util.Slog和android.util.EventLog,写入的日志记录类型分别为main、system和events。这三个Java日志写入接口是通过JNI方法来调用日志库liblog提供的函数来实现日志记录的写入功能的。在本节内容中,我们就分别分析这三个Java日志写入接口的实现。
**android.util.Log**
**frameworks/base/core/java/android/util/Log.java**
~~~
public final class Log {
......
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;
......
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, 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);
}
......
/** @hide */ public static native int LOG_ID_MAIN = 0;
/** @hide */ public static native int LOG_ID_RADIO = 1;
/** @hide */ public static native int LOG_ID_EVENTS = 2;
/** @hide */ public static native int LOG_ID_SYSTEM = 3;
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
}
~~~
接口android.util.Log提供的日志记录写入成员函数比较多,不过我们只关注常用的成员函数v、d、i、w和e。这些成员函数写入的日志记录的类型都是main,而对应的日志记录的优先级分别为VERBOSE、DEBUG、INFO、WARN和ERROR。它们是通过调用JNI方法println_native来实现日志记录写入功能的,如下所示。
**frameworks/base/core/jni/android_util_Log.cpp**
~~~
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
if (msgObj == NULL) {
jclass npeClazz;
npeClazz = env->FindClass("java/lang/NullPointerException");
assert(npeClazz != NULL);
env->ThrowNew(npeClazz, "println needs a message");
return -1;
}
if (bufID < 0 || bufID >= LOG_ID_MAX) {
jclass npeClazz;
npeClazz = env->FindClass("java/lang/NullPointerException");
assert(npeClazz != NULL);
env->ThrowNew(npeClazz, "bad bufID");
return -1;
}
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);
return res;
}
~~~
在JNI函数android_util_Log_println_native中,第11行到19行代码检查写入的日志记录的内容msgObj是否为null,接着第21行到29行代码检查写入的日志记录的类型值是否位于0和LOG_ID_MAX之间,其中,0、1、2和3四个值表示的日志记录的类型分别为main、radio、events和system。通过这两个合法性检查之后,最后第35行就调用日志库liblog提供的函数__android_log_buf_write来往Logger日志驱动程序中写入日志记录。函数__android_log_buf_write的实现可以参考前面4.3小节的内容,这里不再详述。
**android.util.Slog**
**frameworks/base/core/java/android/util/Slog.java**
~~~
/**
* @hide
*/
public final class Slog {
......
public static int v(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
}
public static int d(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
}
public static int i(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
}
public static int w(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
}
public static int e(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
}
......
}
~~~
> 在接口android.util.Slog定义前面的注释中有一个“@hide”关键字,表示这是一个隐藏接口,只在系统内部使用,应用程序一般不应该使用该接口来写入日志记录。
接口android.util.Slog写入的日志记录的类型为system,它常用的成员函数有v、d、i、w和e,对应的日志记录的优先级分别为VERBOSE、DEBUG、INFO、WARN和ERROR,并且它们都是通过调用接口android.util.Log的JNI方法println_native来实现的。JNI方法println_native的实现已经在前面分析过了,这里不再详述。
**android.util.EventLog**
**frameworks/base/core/java/android/util/EventLog.java**
~~~
public class EventLog {
......
/**
* Record an event log message.
* @param tag The event type tag code
* @param value A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, int value);
/**
* Record an event log message.
* @param tag The event type tag code
* @param value A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, long value);
/**
* Record an event log message.
* @param tag The event type tag code
* @param str A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, String str);
/**
* Record an event log message.
* @param tag The event type tag code
* @param list A list of values to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, Object... list);
......
}
~~~
接口android.util.EventLog提供了四个重载版本的JNI方法writeEvent向Logger日志驱动程序中写入类型为events的日志记录,这些日志记录的内容分别为整数、长整数、字符串和列表。
我们首先分析写入整数和长整数类型日志记录的JNI方法writeEvent的实现。
**frameworks/base/core/jni/android_util_EventLog.cpp**
~~~
/*
* In class android.util.EventLog:
* static native int writeEvent(int tag, int value)
*/
static jint android_util_EventLog_writeEvent_Integer(JNIEnv* env, jobject clazz,
jint tag, jint value)
{
return android_btWriteLog(tag, EVENT_TYPE_INT, &value, sizeof(value));
}
/*
* In class android.util.EventLog:
* static native int writeEvent(long tag, long value)
*/
static jint android_util_EventLog_writeEvent_Long(JNIEnv* env, jobject clazz,
jint tag, jlong value)
{
return android_btWriteLog(tag, EVENT_TYPE_LONG, &value, sizeof(value));
}
~~~
它们都是通过调用宏android_btWriteLog向Logger日志驱动程序中写入日志记录的。前者在调用宏android_btWriteLog时,指定第二个参数为EVENT_TYPE_INT,表示要写入的日志记录的内容为一个整数,它的内存布局如图4-8所示。后者在调用宏android_btWriteLog时,指定第二个参数为EVENT_TYPE_LONG,表示要写入的日志记录的内容为一个长整数,它的内存布局如图4-9所示。
内容为整数的日志记录的内存布局
![内容为整数的日志记录的内存布局](https://box.kancloud.cn/e9c6be1c13d19e69c270056c692334fd_796x43.jpg =796x43)
内容为长整数的日志记录的内存布局
![内容为长整数的日志记录的内存布局](https://box.kancloud.cn/320a6a0859aa620fec081f1a60ba33e8_796x42.jpg =796x42)
宏android_btWriteLog的定义如下所示。
**system/core/include/cutils/log.h**
~~~
#define android_btWriteLog(tag, type, payload, len) \
__android_log_btwrite(tag, type, payload, len)
~~~
它指向了日志库liblog提供的函数__android_log_btwrite。函数__android_log_btwrite的实现可以参考前面4.3小节的内容,这里不再详述。
接下来,我们分析写入字符串类型日志记录的JNI方法writeEvent的实现。
**frameworks/base/core/jni/android_util_EventLog.cpp**
~~~
/*
* In class android.util.EventLog:
* static native int writeEvent(int tag, String value)
*/
static jint android_util_EventLog_writeEvent_String(JNIEnv* env, jobject clazz,
jint tag, jstring value) {
uint8_t buf[MAX_EVENT_PAYLOAD];
// Don't throw NPE -- I feel like it's sort of mean for a logging function
// to be all crashy if you pass in NULL -- but make the NULL value explicit.
const char *str = value != NULL ? env->GetStringUTFChars(value, NULL) : "NULL";
jint len = strlen(str);
const int max = sizeof(buf) - sizeof(len) - 2; // Type byte, final newline
if (len > max) len = max;
buf[0] = EVENT_TYPE_STRING;
memcpy(&buf[1], &len, sizeof(len));
memcpy(&buf[1 + sizeof(len)], str, len);
buf[1 + sizeof(len) + len] = '\n';
if (value != NULL) env->ReleaseStringUTFChars(value, str);
return android_bWriteLog(tag, buf, 2 + sizeof(len) + len);
}
~~~
内容为字符串的日志记录的内存布局如图4-10所示。
![内容为字符串的日志记录的内存布局](https://box.kancloud.cn/98642825e80297828b58dd746b05e02d_810x43.jpg =810x43)
第一个字段记录日志记录内容的类型为字符串,第二个字段描述该字符串的长度,第三个字段保存的是字符串内容,第四个字段使用特殊字符‘\n’来结束该日志记录。结合这个内存布局图,就不难理解函数android_util_EventLog_writeEvent_String的实现了。
第22行使用宏android_bWriteLog将日志记录写入到Logger日志驱动程序中,它的定义如下所示。
**system/core/include/cutils/log.h**
~~~
#define android_bWriteLog(tag, payload, len) \
__android_log_bwrite(tag, payload, len)
~~~
它指向了日志库liblog提供的函数__android_log_bwrite。函数__android_log_bwrite的实现可以参考前面4.3小节的内容,这里不再详述。
最后,我们分析写入列表类型日志记录的JNI方法writeEvent的实现。
**frameworks/base/core/jni/android_util_EventLog.cpp**
~~~
/*
* In class android.util.EventLog:
* static native int writeEvent(long tag, Object... value)
*/
static jint android_util_EventLog_writeEvent_Array(JNIEnv* env, jobject clazz,
jint tag, jobjectArray value) {
if (value == NULL) {
return android_util_EventLog_writeEvent_String(env, clazz, tag, NULL);
}
uint8_t buf[MAX_EVENT_PAYLOAD];
const size_t max = sizeof(buf) - 1; // leave room for final newline
size_t pos = 2; // Save room for type tag & array count
jsize copied = 0, num = env->GetArrayLength(value);
for (; copied < num && copied < 255; ++copied) {
jobject item = env->GetObjectArrayElement(value, copied);
if (item == NULL || env->IsInstanceOf(item, gStringClass)) {
if (pos + 1 + sizeof(jint) > max) break;
const char *str = item != NULL ? env->GetStringUTFChars((jstring) item, NULL) : "NULL";
jint len = strlen(str);
if (pos + 1 + sizeof(len) + len > max) len = max - pos - 1 - sizeof(len);
buf[pos++] = EVENT_TYPE_STRING;
memcpy(&buf[pos], &len, sizeof(len));
memcpy(&buf[pos + sizeof(len)], str, len);
pos += sizeof(len) + len;
if (item != NULL) env->ReleaseStringUTFChars((jstring) item, str);
} else if (env->IsInstanceOf(item, gIntegerClass)) {
jint intVal = env->GetIntField(item, gIntegerValueID);
if (pos + 1 + sizeof(intVal) > max) break;
buf[pos++] = EVENT_TYPE_INT;
memcpy(&buf[pos], &intVal, sizeof(intVal));
pos += sizeof(intVal);
} else if (env->IsInstanceOf(item, gLongClass)) {
jlong longVal = env->GetLongField(item, gLongValueID);
if (pos + 1 + sizeof(longVal) > max) break;
buf[pos++] = EVENT_TYPE_LONG;
memcpy(&buf[pos], &longVal, sizeof(longVal));
pos += sizeof(longVal);
} else {
jniThrowException(env,
"java/lang/IllegalArgumentException",
"Invalid payload item type");
return -1;
}
env->DeleteLocalRef(item);
}
buf[0] = EVENT_TYPE_LIST;
buf[1] = copied;
buf[pos++] = '\n';
return android_bWriteLog(tag, buf, pos);
}
~~~
位于列表中的元素的值类型只能为整数、长整数或者字符串;否则,函数就会在第41行到第43行抛出一个异常。一个列表中最多可以包含255个元素,如果超过这个数值,多余的元素就会被忽略。在函数第16行到第47行的for循环中,依次取出列表中的元素,并且根据它们的值类型来组织缓冲区buf的内存布局。如果值类型为整数,那么写入到缓冲区buf的第一个字节设置为一个EVENT_TYPE_INT值,再加上一个整数值;如果值类型为长整数,那么写入到缓冲区buf的内容就为一个EVENT_TYPE_LONG值,再加上一个长整数值;如果值类型为字符串,那么写入到缓冲区buf的内容就为一个EVENT_TYPE_STRING值和一个字符串长度值,再加上字符串的内容。
将列表中的元素都写入到缓冲区buf之后,函数第49行将缓冲区buf的第一个字节设置为EVENT_TYPE_LIST,表示这是一个列表类型的日志记录;接着第50行将缓冲区buf的第二个字节设置为变量copied的值,表示在缓冲区buf中的列表元素个数;最后第51行将缓冲区buf的最后一个字节的内容设置为特殊字符‘\n’,用作该日志记录的结束标志。这时候,缓冲区buf的内存布局就类似于图4-11。
![内容为列表的日志记录的内存布局](https://box.kancloud.cn/51a78e4f6831bb32e1bc4a5e3726a145_923x66.jpg =923x66)
函数第52行使用了宏android_bWriteLog将日志记录写入到Logger日志驱动程序中。前面提到,宏android_bWriteLog指向的是日志库liblog提供的函数__android_log_bwrite,它的实现可以参考前面4.3小节的内容,这里不再详述。
至此,我们就介绍完了Android系统的日志写入接口。接下来,我们继续分析如何读取并且显示Logger日志驱动程序中的日志记录。
- 文章概述
- 下载Android源码以及查看源码
- win10 平台通过VMware Workstation安装Ubuntu
- Linux系统安装Ubuntu编译Android源码
- Eclipse快捷键大全
- 前言
- 第一篇 初识Android系统
- 第一章 准备知识
- 1.1 Linux内核参考书籍
- 1.2 Android应用程序参考书籍
- 1.3 下载、编译和运行Android源代码
- 1.3.1 下载Android源代码
- 1.3.2 编译Android源代码
- 1.3.3 运行Android模拟器
- 1.4 下载、编译和运行Android内核源代码
- 1.4.1 下载Android内核源代码
- 1.4.2 编译Android内核源代码
- 1.4.3 运行Android模拟器
- 1.5 开发第一个Android应用程序
- 1.6 单独编译和打包Android应用程序模块
- 1.6.1 导入单独编译模块的mmm命令
- 1.6.2 单独编译Android应用程序模块
- 1.6.3 重新打包Android系统镜像文件
- 第二章 硬件抽象层
- 2.1 开发Android硬件驱动程序
- 2.1.1 实现内核驱动程序模块
- 2.1.2 修改内核Kconfig文件
- 2.1.3 修改内核Makefile文件
- 2.1.4 编译内核驱动程序模块
- 2.1.5 验证内核驱动程序模块
- 2.2 开发C可执行程序验证Android硬件驱动程序
- 2.3 开发Android硬件抽象层模块
- 2.3.1 硬件抽象层模块编写规范
- 2.3.1.1 硬件抽象层模块文件命名规范
- 2.3.1.2 硬件抽象层模块结构体定义规范
- 2.3.2 编写硬件抽象层模块接口
- 2.3.3 硬件抽象层模块的加载过程
- 2.3.4 处理硬件设备访问权限问题
- 2.4 开发Android硬件访问服务
- 2.4.1 定义硬件访问服务接口
- 2.4.2 实现硬件访问服务
- 2.4.3 实现硬件访问服务的JNI方法
- 2.4.4 启动硬件访问服务
- 2.5 开发Android应用程序来使用硬件访问服务
- 第三章 智能指针
- 3.1 轻量级指针
- 3.1.1 实现原理分析
- 3.1.2 使用实例分析
- 3.2 强指针和弱指针
- 3.2.1 强指针的实现原理分析
- 3.2.2 弱指针的实现原理分析
- 3.2.3 应用实例分析
- 第二篇 Android专用驱动系统
- 第四章 Logger日志系统
- 4.1 Logger日志格式
- 4.2 Logger日志驱动程序
- 4.2.1 基础数据结构
- 4.2.2 日志设备的初始化过程
- 4.2.3 日志设备文件的打开过程
- 4.2.4 日志记录的读取过程
- 4.2.5 日志记录的写入过程
- 4.3 运行时库层日志库
- 4.4 C/C++日志写入接口
- 4.5 Java日志写入接口
- 4.6 Logcat工具分析
- 4.6.1 基础数据结构
- 4.6.2 初始化过程
- 4.6.3 日志记录的读取过程
- 4.6.4 日志记录的输出过程
- 第五章 Binder进程间通信系统
- 5.1 Binder驱动程序
- 5.1.1 基础数据结构
- 5.1.2 Binder设备的初始化过程
- 5.1.3 Binder设备文件的打开过程
- 5.1.4 设备文件内存映射过程
- 5.1.5 内核缓冲区管理
- 5.1.5.1 分配内核缓冲区
- 5.1.5.2 释放内核缓冲区
- 5.1.5.3 查询内核缓冲区
- 5.2 Binder进程间通信库
- 5.3 Binder进程间通信应用实例
- 5.4 Binder对象引用计数技术
- 5.4.1 Binder本地对象的生命周期
- 5.4.2 Binder实体对象的生命周期
- 5.4.3 Binder引用对象的生命周期
- 5.4.4 Binder代理对象的生命周期
- 5.5 Binder对象死亡通知机制
- 5.5.1 注册死亡接收通知
- 5.5.2 发送死亡接收通知
- 5.5.3 注销死亡接收通知
- 5.6 Service Manager的启动过程
- 5.6.1 打开和映射Binder设备文件
- 5.6.2 注册成为Binder上下文管理者
- 5.6.3 循环等待Client进程请求
- 5.7 Service Manager代理对象接口的获取过程
- 5.8 Service的启动过程
- 5.8.1 注册Service组件
- 5.8.1.1 封装通信数据为Parcel对象
- 5.8.1.2 发送和处理BC_TRANSACTION命令协议
- 5.8.1.3 发送和处理BR_TRANSACTION返回协议
- 5.8.1.4 发送和处理BC_REPLY命令协议
- 5.8.1.5 发送和处理BR_REPLY返回协议
- 5.8.2 循环等待Client进程请求
- 5.9 Service代理对象接口的获取过程
- 5.10 Binder进程间通信机制的Java实现接口
- 5.10.1 获取Service Manager的Java代理对象接口
- 5.10.2 AIDL服务接口解析
- 5.10.3 Java服务的启动过程
- 5.10.4 获取Java服务的代理对象接口
- 5.10.5 Java服务的调用过程
- 第六章 Ashmem匿名共享内存系统
- 6.1 Ashmem驱动程序
- 6.1.1 相关数据结构
- 6.1.2 设备初始化过程
- 6.1.3 设备文件打开过程
- 6.1.4 设备文件内存映射过程
- 6.1.5 内存块的锁定和解锁过程
- 6.1.6 解锁状态内存块的回收过程
- 6.2 运行时库cutils的匿名共享内存接口
- 6.3 匿名共享内存的C++访问接口
- 6.3.1 MemoryHeapBase
- 6.3.1.1 Server端的实现
- 6.3.1.2 Client端的实现
- 6.3.2 MemoryBase
- 6.3.2.1 Server端的实现
- 6.3.2.2 Client端的实现
- 6.3.3 应用实例
- 6.4 匿名共享内存的Java访问接口
- 6.4.1 MemoryFile
- 6.4.2 应用实例
- 6.5 匿名共享内存的共享原理分析
- 第三篇 Android应用程序框架篇
- 第七章 Activity组件的启动过程
- 7.1 Activity组件应用实例
- 7.2 根Activity的启动过程
- 7.3 Activity在进程内的启动过程
- 7.4 Activity在新进程中的启动过程
- 第八章 Service组件的启动过程
- 8.1 Service组件应用实例
- 8.2 Service在新进程中的启动过程
- 8.3 Service在进程内的绑定过程
- 第九章 Android系统广播机制
- 9.1 广播应用实例
- 9.2 广播接收者的注册过程
- 9.3 广播的发送过程
- 第十章 Content Provider组件的实现原理
- 10.1 Content Provider组件应用实例
- 10.1.1 ArticlesProvider
- 10.1.2 Article
- 10.2 Content Provider组件的启动过程
- 10.3 Content Provider组件的数据共享原理
- 10.4 Content Provider组件的数据更新通知机制
- 10.4.1 内容观察者的注册过程
- 10.4.2 数据更新的通知过程
- 第十一章 Zygote和System进程的启动过程
- 11.1 Zygote进程的启动脚本
- 11.2 Zygote进程的启动过程
- 11.3 System进程的启动过程
- 第十二章 Android应用程序进程的启动过程
- 12.1 应用程序进程的创建过程
- 12.2 Binder线程池的启动过程
- 12.3 消息循环的创建过程
- 第十三章 Android应用程序的消息处理机制
- 13.1 创建线程消息队列
- 13.2 线程消息循环过程
- 13.3 线程消息发送过程
- 13.4 线程消息处理过程
- 第十四章 Android应用程序的键盘消息处理机制
- 14.1 InputManager的启动过程
- 14.1.1 创建InputManager
- 14.1.2 启动InputManager
- 14.1.3 启动InputDispatcher
- 14.1.4 启动InputReader
- 14.2 InputChannel的注册过程
- 14.2.1 创建InputChannel
- 14.2.2 注册Server端InputChannel
- 14.2.3 注册当前激活窗口
- 14.2.4 注册Client端InputChannel
- 14.3 键盘消息的分发过程
- 14.3.1 InputReader处理键盘事件
- 14.3.2 InputDispatcher分发键盘事件
- 14.3.3 当前激活的窗口获得键盘消息
- 14.3.4 InputDispatcher获得键盘事件处理完成通知
- 14.4 InputChannel的注销过程
- 14.4.1 销毁应用程序窗口
- 14.4.2 注销Client端InputChannel
- 14.4.3 注销Server端InputChannel
- 第十五章 Android应用程序线程的消息循环模型
- 15.1 应用程序主线程消息循环模型
- 15.2 界面无关的应用程序子线程消息循环模型
- 15.3 界面相关的应用程序子线程消息循环模型
- 第十六章 Android应用程序的安装和显示过程
- 16.1 应用程序的安装过程
- 16.2 应用程序的显示过程