Android应用程序框架层和系统运行库层日志系统源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6598703

在开发Android应用程序时,少不了使用Log来监控和调试程序的执行。在上一篇文章Android日志系统驱动程序Logger源代码分析中,我们分析了驱动程序Logger的源代码,在前面的文章浅谈Android系统开发中Log的使用一文,我们也简单介绍在应用程序中使Log的方法,在这篇文章中,我们将详细介绍Android应用程序框架层和系统运行库存层日志系统的源代码,使得我们可以更好地理解Android的日志系统的实现。

我们在Android应用程序,一般是调用应用程序框架层的Java接口(android.util.Log)来使用日志系统,这个Java接口通过JNI方法和系统运行库最终调用内核驱动程序Logger把Log写到内核空间中。按照这个调用过程,我们一步步介绍Android应用程序框架层日志系统的源代码。学习完这个过程之后,我们可以很好地理解Android系统的架构,即应用程序层(Application)的接口是如何一步一步地调用到内核空间的。

一. 应用程序框架层日志系统Java接口的实现。

浅谈Android系统开发中Log的使用一文中,我们曾经介绍过Android应用程序框架层日志系统的源代码接口。这里,为了描述方便和文章的完整性,我们重新贴一下这部份的代码,在frameworks/base/core/java/android/util/Log.java文件中,实现日志系统的Java接口:

[java] view plaincopy

  1. ................................................
  2. public final class Log {
  3. ................................................
  4. /**
  5. * Priority constant for the println method; use Log.v.
  6. */
  7. public static final int VERBOSE = 2;
  8. /**
  9. * Priority constant for the println method; use Log.d.
  10. */
  11. public static final int DEBUG = 3;
  12. /**
  13. * Priority constant for the println method; use Log.i.
  14. */
  15. public static final int INFO = 4;
  16. /**
  17. * Priority constant for the println method; use Log.w.
  18. */
  19. public static final int WARN = 5;
  20. /**
  21. * Priority constant for the println method; use Log.e.
  22. */
  23. public static final int ERROR = 6;
  24. /**
  25. * Priority constant for the println method.
  26. */
  27. public static final int ASSERT = 7;
  28. .....................................................
  29. public static int v(String tag, String msg) {
  30. return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
  31. }
  32. public static int v(String tag, String msg, Throwable tr) {
  33. return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + ‘\n‘ + getStackTraceString(tr));
  34. }
  35. public static int d(String tag, String msg) {
  36. return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
  37. }
  38. public static int d(String tag, String msg, Throwable tr) {
  39. return println_native(LOG_ID_MAIN, DEBUG, tag, msg + ‘\n‘ + getStackTraceString(tr));
  40. }
  41. public static int i(String tag, String msg) {
  42. return println_native(LOG_ID_MAIN, INFO, tag, msg);
  43. }
  44. public static int i(String tag, String msg, Throwable tr) {
  45. return println_native(LOG_ID_MAIN, INFO, tag, msg + ‘\n‘ + getStackTraceString(tr));
  46. }
  47. public static int w(String tag, String msg) {
  48. return println_native(LOG_ID_MAIN, WARN, tag, msg);
  49. }
  50. public static int w(String tag, String msg, Throwable tr) {
  51. return println_native(LOG_ID_MAIN, WARN, tag, msg + ‘\n‘ + getStackTraceString(tr));
  52. }
  53. public static int w(String tag, Throwable tr) {
  54. return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
  55. }
  56. public static int e(String tag, String msg) {
  57. return println_native(LOG_ID_MAIN, ERROR, tag, msg);
  58. }
  59. public static int e(String tag, String msg, Throwable tr) {
  60. return println_native(LOG_ID_MAIN, ERROR, tag, msg + ‘\n‘ + getStackTraceString(tr));
  61. }
  62. ..................................................................
  63. /** @hide */ public static native int LOG_ID_MAIN = 0;
  64. /** @hide */ public static native int LOG_ID_RADIO = 1;
  65. /** @hide */ public static native int LOG_ID_EVENTS = 2;
  66. /** @hide */ public static native int LOG_ID_SYSTEM = 3;
  67. /** @hide */ public static native int println_native(int bufID,
  68. int priority, String tag, String msg);
  69. }

定义了2~7一共6个日志优先级别ID和4个日志缓冲区ID。回忆一下Android日志系统驱动程序Logger源代码分析一文,在Logger驱动程序模块中,定义了log_main、log_events和log_radio三个日志缓冲区,分别对应三个设备文件/dev/log/main、/dev/log/events和/dev/log/radio。这里的4个日志缓冲区的前面3个ID就是对应这三个设备文件的文件描述符了,在下面的章节中,我们将看到这三个文件描述符是如何创建的。在下载下来的Android内核源代码中,第4个日志缓冲区LOG_ID_SYSTEM并没有对应的设备文件,在这种情况下,它和LOG_ID_MAIN对应同一个缓冲区ID,在下面的章节中,我们同样可以看到这两个ID是如何对应到同一个设备文件的。

在整个Log接口中,最关键的地方声明了println_native本地方法,所有的Log接口都是通过调用这个本地方法来实现Log的定入。下面我们就继续分析这个本地方法println_native。

二. 应用程序框架层日志系统JNI方法的实现。

在frameworks/base/core/jni/android_util_Log.cpp文件中,实现JNI方法println_native:

[cpp] view plaincopy

  1. /* //device/libs/android_runtime/android_util_Log.cpp
  2. **
  3. ** Copyright 2006, The Android Open Source Project
  4. **
  5. ** Licensed under the Apache License, Version 2.0 (the "License");
  6. ** you may not use this file except in compliance with the License.
  7. ** You may obtain a copy of the License at
  8. **
  9. **     http://www.apache.org/licenses/LICENSE-2.0
  10. **
  11. ** Unless required by applicable law or agreed to in writing, software
  12. ** distributed under the License is distributed on an "AS IS" BASIS,
  13. ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. ** See the License for the specific language governing permissions and
  15. ** limitations under the License.
  16. */
  17. #define LOG_NAMESPACE "log.tag."
  18. #define LOG_TAG "Log_println"
  19. #include <assert.h>
  20. #include <cutils/properties.h>
  21. #include <utils/Log.h>
  22. #include <utils/String8.h>
  23. #include "jni.h"
  24. #include "utils/misc.h"
  25. #include "android_runtime/AndroidRuntime.h"
  26. #define MIN(a,b) ((a<b)?a:b)
  27. namespace android {
  28. struct levels_t {
  29. jint verbose;
  30. jint debug;
  31. jint info;
  32. jint warn;
  33. jint error;
  34. jint assert;
  35. };
  36. static levels_t levels;
  37. static int toLevel(const char* value)
  38. {
  39. switch (value[0]) {
  40. case ‘V‘: return levels.verbose;
  41. case ‘D‘: return levels.debug;
  42. case ‘I‘: return levels.info;
  43. case ‘W‘: return levels.warn;
  44. case ‘E‘: return levels.error;
  45. case ‘A‘: return levels.assert;
  46. case ‘S‘: return -1; // SUPPRESS
  47. }
  48. return levels.info;
  49. }
  50. static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
  51. {
  52. #ifndef HAVE_ANDROID_OS
  53. return false;
  54. #else /* HAVE_ANDROID_OS */
  55. int len;
  56. char key[PROPERTY_KEY_MAX];
  57. char buf[PROPERTY_VALUE_MAX];
  58. if (tag == NULL) {
  59. return false;
  60. }
  61. jboolean result = false;
  62. const char* chars = env->GetStringUTFChars(tag, NULL);
  63. if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
  64. jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
  65. char buf2[200];
  66. snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
  67. chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
  68. // release the chars!
  69. env->ReleaseStringUTFChars(tag, chars);
  70. env->ThrowNew(clazz, buf2);
  71. return false;
  72. } else {
  73. strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
  74. strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
  75. }
  76. env->ReleaseStringUTFChars(tag, chars);
  77. len = property_get(key, buf, "");
  78. int logLevel = toLevel(buf);
  79. return (logLevel >= 0 && level >= logLevel) ? true : false;
  80. #endif /* HAVE_ANDROID_OS */
  81. }
  82. /*
  83. * In class android.util.Log:
  84. *  public static native int println_native(int buffer, int priority, String tag, String msg)
  85. */
  86. static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
  87. jint bufID, jint priority, jstring tagObj, jstring msgObj)
  88. {
  89. const char* tag = NULL;
  90. const char* msg = NULL;
  91. if (msgObj == NULL) {
  92. jclass npeClazz;
  93. npeClazz = env->FindClass("java/lang/NullPointerException");
  94. assert(npeClazz != NULL);
  95. env->ThrowNew(npeClazz, "println needs a message");
  96. return -1;
  97. }
  98. if (bufID < 0 || bufID >= LOG_ID_MAX) {
  99. jclass npeClazz;
  100. npeClazz = env->FindClass("java/lang/NullPointerException");
  101. assert(npeClazz != NULL);
  102. env->ThrowNew(npeClazz, "bad bufID");
  103. return -1;
  104. }
  105. if (tagObj != NULL)
  106. tag = env->GetStringUTFChars(tagObj, NULL);
  107. msg = env->GetStringUTFChars(msgObj, NULL);
  108. int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
  109. if (tag != NULL)
  110. env->ReleaseStringUTFChars(tagObj, tag);
  111. env->ReleaseStringUTFChars(msgObj, msg);
  112. return res;
  113. }
  114. /*
  115. * JNI registration.
  116. */
  117. static JNINativeMethod gMethods[] = {
  118. /* name, signature, funcPtr */
  119. { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
  120. { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
  121. };
  122. int register_android_util_Log(JNIEnv* env)
  123. {
  124. jclass clazz = env->FindClass("android/util/Log");
  125. if (clazz == NULL) {
  126. LOGE("Can‘t find android/util/Log");
  127. return -1;
  128. }
  129. levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
  130. levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
  131. levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
  132. levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
  133. levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
  134. levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
  135. return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
  136. }
  137. }; // namespace android

在gMethods变量中,定义了println_native本地方法对应的函数调用是android_util_Log_println_native。在android_util_Log_println_native函数中,通过了各项参数验证正确后,就调用运行时库函数__android_log_buf_write来实现Log的写入操作。__android_log_buf_write函实实现在liblog库中,它有4个参数,分别缓冲区ID、优先级别ID、Tag字符串和Msg字符串。下面运行时库liblog中的__android_log_buf_write的实现。

三. 系统运行库层日志系统的实现。

在系统运行库层liblog库的实现中,内容比较多,这里,我们只关注日志写入操作__android_log_buf_write的相关实现:

[cpp] view plaincopy

  1. int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
  2. {
  3. struct iovec vec[3];
  4. if (!tag)
  5. tag = "";
  6. /* XXX: This needs to go! */
  7. if (!strcmp(tag, "HTC_RIL") ||
  8. !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
  9. !strcmp(tag, "AT") ||
  10. !strcmp(tag, "GSM") ||
  11. !strcmp(tag, "STK") ||
  12. !strcmp(tag, "CDMA") ||
  13. !strcmp(tag, "PHONE") ||
  14. !strcmp(tag, "SMS"))
  15. bufID = LOG_ID_RADIO;
  16. vec[0].iov_base   = (unsigned char *) &prio;
  17. vec[0].iov_len    = 1;
  18. vec[1].iov_base   = (void *) tag;
  19. vec[1].iov_len    = strlen(tag) + 1;
  20. vec[2].iov_base   = (void *) msg;
  21. vec[2].iov_len    = strlen(msg) + 1;
  22. return write_to_log(bufID, vec, 3);
  23. }

函数首先是检查传进来的tag参数是否是为HTC_RIL、RIL、AT、GSM、STK、CDMA、PHONE和SMS中的一个,如果是,就无条件地使用ID为LOG_ID_RADIO的日志缓冲区作为写入缓冲区,接着,把传进来的参数prio、tag和msg分别存放在一个向量数组中,调用write_to_log函数来进入下一步操作。write_to_log是一个函数指针,定义在文件开始的位置上:

[cpp] view plaincopy

  1. static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
  2. static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;

并且初始化为__write_to_log_init函数:

[cpp] view plaincopy

  1. static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
  2. {
  3. #ifdef HAVE_PTHREADS
  4. pthread_mutex_lock(&log_init_lock);
  5. #endif
  6. if (write_to_log == __write_to_log_init) {
  7. log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
  8. log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
  9. log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
  10. log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);
  11. write_to_log = __write_to_log_kernel;
  12. if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
  13. log_fds[LOG_ID_EVENTS] < 0) {
  14. log_close(log_fds[LOG_ID_MAIN]);
  15. log_close(log_fds[LOG_ID_RADIO]);
  16. log_close(log_fds[LOG_ID_EVENTS]);
  17. log_fds[LOG_ID_MAIN] = -1;
  18. log_fds[LOG_ID_RADIO] = -1;
  19. log_fds[LOG_ID_EVENTS] = -1;
  20. write_to_log = __write_to_log_null;
  21. }
  22. if (log_fds[LOG_ID_SYSTEM] < 0) {
  23. log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
  24. }
  25. }
  26. #ifdef HAVE_PTHREADS
  27. pthread_mutex_unlock(&log_init_lock);
  28. #endif
  29. return write_to_log(log_id, vec, nr);
  30. }

这里我们可以看到,如果是第一次调write_to_log函数,write_to_log == __write_to_log_init判断语句就会true,于是执行log_open函数打开设备文件,并把文件描述符保存在log_fds数组中。如果打开/dev/LOGGER_LOG_SYSTEM文件失败,即log_fds[LOG_ID_SYSTEM] < 0,就把log_fds[LOG_ID_SYSTEM]设置为log_fds[LOG_ID_MAIN],这就是我们上面描述的如果不存在ID为LOG_ID_SYSTEM的日志缓冲区,就把LOG_ID_SYSTEM设置为和LOG_ID_MAIN对应的日志缓冲区了。LOGGER_LOG_MAIN、LOGGER_LOG_RADIO、LOGGER_LOG_EVENTS和LOGGER_LOG_SYSTEM四个宏定义在system/core/include/cutils/logger.h文件中:

[cpp] view plaincopy

  1. #define LOGGER_LOG_MAIN     "log/main"
  2. #define LOGGER_LOG_RADIO    "log/radio"
  3. #define LOGGER_LOG_EVENTS   "log/events"
  4. #define LOGGER_LOG_SYSTEM   "log/system"

接着,把write_to_log函数指针指向__write_to_log_kernel函数:

[cpp] view plaincopy

  1. static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
  2. {
  3. ssize_t ret;
  4. int log_fd;
  5. if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
  6. log_fd = log_fds[(int)log_id];
  7. } else {
  8. return EBADF;
  9. }
  10. do {
  11. ret = log_writev(log_fd, vec, nr);
  12. } while (ret < 0 && errno == EINTR);
  13. return ret;
  14. }

函数调用log_writev来实现Log的写入,注意,这里通过一个循环来写入Log,直到写入成功为止。这里log_writev是一个宏,在文件开始的地方定义为:

[cpp] view plaincopy

  1. #if FAKE_LOG_DEVICE
  2. // This will be defined when building for the host.
  3. #define log_open(pathname, flags) fakeLogOpen(pathname, flags)
  4. #define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
  5. #define log_close(filedes) fakeLogClose(filedes)
  6. #else
  7. #define log_open(pathname, flags) open(pathname, flags)
  8. #define log_writev(filedes, vector, count) writev(filedes, vector, count)
  9. #define log_close(filedes) close(filedes)
  10. #endif

这里,我们看到,一般情况下,log_writev就是writev了,这是个常见的批量文件写入函数,就不多说了。

至些,整个调用过程就结束了。总结一下,首先是从应用程序层调用应用程序框架层的Java接口,应用程序框架层的Java接口通过调用本层的JNI方法进入到系统运行库层的C接口,系统运行库层的C接口通过设备文件来访问内核空间层的Logger驱动程序。这是一个典型的调用过程,很好地诠释Android的系统架构,希望读者好好领会。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

时间: 2024-12-21 10:42:04

Android应用程序框架层和系统运行库层日志系统源代码分析的相关文章

Android应用程序框架之无边界设计意图

Android的应用框架的外特性空间的描述在SDK文档有十分清楚的描述,Android应用的基本概念,组件生命周期等等有详细的描述.在外特性空间中,Android提供了Activity,Service,Broadcast receivers,Content Provider,Intent,task等概念,我在这里不讨论这些概念定义,因为SDK文档已经讲得够详细. 在阅读SDK文档和研究Activity这个概念时,我感觉到了在Android中若隐若现的Android自由无边界这个设计意图.Andr

android 应用程序框架

在进行Android软件开发时,开发者所开发的Android应用程序都是通过应用程序框架来与Android底层进行交互的,所以开发中接触到最多的部分就是应用程序框架.在整合应用程序框架 中有4个重要的组件,介绍如下. Activities:一个Activities就表示一个程序的显示界面,在一个应用程序中可以包含多个Activities组件,每个Activities组件都拥有各自的生命周期. Intent:当多个应用程序之间需要互相跳转时,就通过Intent完成,开发者所开发的程序也可以利用In

应用程序框架实战十九:工作单元层超类型

上一篇介绍了DDD聚合以及与并发相关的各种锁机制,本文将介绍另一个核心元素——工作单元,它是实现仓储的基础. 什么是工作单元 维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决. 这是<企业应用架构模式>中给出的定义,不过看上去有点抽象.它大概的意思是说,对多个操作进行打包,记录对象上的所有变化,并在最后提交时一次性将所有变化通过系统事务写入数据库. 当然,工作单元不一定是针对数据库的,不过大部分程序员还是工作在关系数据库中,所以我默认你也在使用关系数据库,由此产生的不准确性你就不

应用程序框架实战二十:映射层超类型

上一篇介绍了工作单元层超类型的封装演化过程,本文将介绍对Entity Framework映射层超类型的封装. 使用Entity Framework一般需要映射三种类型的对象,即实体.聚合.值对象. 聚合与实体映射的主要区别是:聚合映射单属性标识Id,并需要映射乐观离线锁Version,而实体的标识往往需要映射成复合属性,这样方便物理删除聚合中的实体.Entity Framework通过EntityTypeConfiguration进行实体映射. 值对象以嵌入值模式映射,这需要使用ComplexT

Linux 系统运行级别(SysVinit 系统)

个人博客首页(点击查看详情) -- https://blog.51cto.com/11495268个人微信公众号(点击查看详情) -- https://blog.51cto.com/11495268/2401194     1.简介     systemctl 基础操作 学习中,接触 系统运行级别 之间的 切换,本文 描述 SysVinit 系统运行级别 基本概念 # lsb_release -a No LSB modules are available. Distributor ID: Ubu

框架《Keras深度学习实战》中英文PDF+源代码分析

作为一款轻量级.模块化的开源深度学习框架, Keras 以容易上子.利于快速原型实现.能够与TensorFlow 和Theano 等后端计算平台很好兼容等优点, 深受众多开发人 员和研究人员的喜爱. <Keras深度学习实战>结合大量实例,简明扼要地介绍了目前热门的神经网络技术和深度学习技术 .从经典的多层感知机到用于图像处理的深度卷积网络,从处理序列化数据的循环网络到伪造仿真数据的生成对抗网络,从词嵌入到AI 游戏应用中的强化学习,引领一层一层揭开深度学习的面纱, 并在逐渐清晰的理论框架下,

Android 应用程序退出后不在运行列表中显示的方法

使应用信息不在运行列表中显示的方法需要修改配置文件中activity标签的两个值 <span style="font-size:14px;">android:noHistory="true" android:excludeFromRecents="true"></span> 将这两个值置为true android:noHistory 这个属性用于设置在用户离开该Activity,并且它在屏幕上不再可见的时候,它是否应

Android日志系统Logcat源代码简要分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6606957 在前面两篇文章Android日志系统驱动程序Logger源代码分析和Android应用程序框架层和系统运行库层日志系统源代码中,介绍了Android内核空间层.系统运行库层和应用程序框架层日志系统相关的源代码,其中,后一篇文章着重介绍了日志的写入操作.为了描述完整性,这篇文章着重介绍日志的读取操作,这就是我们在开发Android应用

Android系统介绍与框架(转)

一.Andriod是什么? Android系统是Google开发的一款开源移动OS,Android中文名被国内用户俗称“安卓”.Android操作系统基于Linux内核设计,使用了Google公司自己开发的Dalvik Java虚拟机.Android操作系统已经成为全球最大的智能手机操作系统. 1).开放性 Android完全开源,且该平台从底层操作系统到上层的用户界面和应用程序都不存在任何阻碍产业创新的专有权障碍.同时开源的最大好处是,使得Android平台会拥有越来越壮大的开发者队伍,并且随