企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
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日志驱动程序中的日志记录。