客户端写日志过程分析

这是一个介绍 Android 平台日志系统的系列文章:

本文基于 AOSP android-10.0.0_r41 版本讲解

1. 客户端接口

1.1 Java 层

Android 应用层提供日志系统的 Java 接口:Log.java、Rlog.java、Slog.java、EventLog.java。其功能类似只是写入 logd 的日志节点不同。Java 接口封装在 android.jar 中,作为 SDK 提供给开发者使用,在运行时通过 libandroid_runtime.so 中的 JNI 接口调用系统 native api,native 层的 api 看似很复杂,梳理清楚,你就会发现,实际就是建立 socket 连接,然后通过 socket 发送数据。后面我们分析以 Log.java 为例,其他接口都大体类似。

Java 层写日志的代码通常如下:

java 复制代码
Log.d(TAG,"Message");

1.2 Native 层

Natvie 层有以下两种常用的方式来打 Log:

cpp 复制代码
// 方式一
#include <utils/Log.h>             
#define LOG_TAG "my_log"          //日志tag
int main(int args,char** argv){
    ALOGE("......");               
    SLOGE("......");              
}

//方式二
#include <android/log.h>
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO   , LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN   , LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)

实际上的流程和 Java 层陷入到 Natvie 层以后类似,所以我们主要分析 Java 层的流程。

2. Java 源码层分析

Java 层写日志的整体流程如下:

图片来自参考资料2

接下来我们来看代码细节:

Java 层一个常见的打 Log 方式如下:

java 复制代码
Log.d(TAG,"Message");

其源码实现如下:

java 复制代码
    // frameworks/base/core/java/android/util/Log.java
    public static int d(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }

    public static native int println_native(int bufID, int priority, String tag, String msg);

实际是调用到了 Native 方法 println_native,其对应的 JNI 函数如下:

cpp 复制代码
//frameworks/base/core/jni/android_util_Log.cpp
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) {
        jniThrowNullPointerException(env, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jniThrowNullPointerException(env, "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;
}    

核心内容是调用 __android_log_buf_write 函数来写入日志:

cpp 复制代码
//system/core/liblog/logger_write.cpp
int __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) {
  struct iovec vec[3];
  char tmp_tag[32];

  if (!tag) tag = "";

// 处理特殊日志,不用管
  /* XXX: This needs to go! */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstring-plus-int"
  if (bufID != LOG_ID_RADIO) {
    switch (tag[0]) {
      case 'H':
        if (strcmp(tag + 1, "HTC_RIL" + 1)) break;
        goto inform;
      case 'R':
        /* Any log tag with "RIL" as the prefix */
        if (strncmp(tag + 1, "RIL" + 1, strlen("RIL") - 1)) break;
        goto inform;
      case 'Q':
        /* Any log tag with "QC_RIL" as the prefix */
        if (strncmp(tag + 1, "QC_RIL" + 1, strlen("QC_RIL") - 1)) break;
        goto inform;
      case 'I':
        /* Any log tag with "IMS" as the prefix */
        if (strncmp(tag + 1, "IMS" + 1, strlen("IMS") - 1)) break;
        goto inform;
      case 'A':
        if (strcmp(tag + 1, "AT" + 1)) break;
        goto 
        ;
      case 'G':
        if (strcmp(tag + 1, "GSM" + 1)) break;
        goto inform;
      case 'S':
        if (strcmp(tag + 1, "STK" + 1) && strcmp(tag + 1, "SMS" + 1)) break;
        goto inform;
      case 'C':
        if (strcmp(tag + 1, "CDMA" + 1)) break;
        goto inform;
      case 'P':
        if (strcmp(tag + 1, "PHONE" + 1)) break;
      /* FALLTHRU */
      inform:
        bufID = LOG_ID_RADIO;
        snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
        tag = tmp_tag;
        [[fallthrough]];
      default:
        break;
    }
  }
#pragma clang diagnostic pop

#if __BIONIC__
  if (prio == ANDROID_LOG_FATAL) {
    android_set_abort_message(msg);
  }
#endif

  // 将日志信息转换为 iovec
  // 日志 Level
  vec[0].iov_base = (unsigned char*)&prio;
  vec[0].iov_len = 1;
  // 日志 TAG
  vec[1].iov_base = (void*)tag;
  vec[1].iov_len = strlen(tag) + 1;
  // 日志内容
  vec[2].iov_base = (void*)msg;
  vec[2].iov_len = strlen(msg) + 1;

  // 接着调用 write_to_log 函数指针
  return write_to_log(static_cast<log_id_t>(bufID), vec, 3);
}

先处理一些特殊类型的日志,这个不用管,接着将日志信息,主要是日志 Level、日志 TAG 以及日志内容转换为 struct iovec vec[3]; 数组,接着调用 write_to_log 函数指针来写日志。

cpp 复制代码
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) {
  int ret, save_errno = errno;

  __android_log_lock();

  if (write_to_log == __write_to_log_init) {  // 进入 if
    // 初始化
    ret = __write_to_log_initialize();

    if (ret < 0) { //  __write_to_log_initialize() 失败的处理
      __android_log_unlock();
      if (!list_empty(&__android_log_persist_write)) { 
        __write_to_log_daemon(log_id, vec, nr);
      }
      errno = save_errno;
      return ret;
    }

    // write_to_log 重学赋值为 __write_to_log_daemon
    write_to_log = __write_to_log_daemon; 
  }

  __android_log_unlock();

  // 调用 __write_to_log_daemon 
  ret = write_to_log(log_id, vec, nr);
  errno = save_errno;
  return ret;
}

write_to_log 是一个函数指针,初始化时,指向 __write_to_log_init 函数。函数内部主要有三点需要关注:

  • 调用 __write_to_log_initialize() 函数初始化日志写环境
  • 将 write_to_log 函数指针赋值为 __write_to_log_daemon
  • 调用 __write_to_log_daemon(log_id, vec, nr); 写日志

接下来来看 __write_to_log_initialize()__write_to_log_daemon(log_id, vec, nr); 的实现:

cpp 复制代码
static int __write_to_log_initialize() {
  struct android_log_transport_write* transport;
  struct listnode* n;
  int i = 0, ret = 0;

  // 配置
  __android_log_config_write();

  //遍历 __android_log_transport_write 链表,挨个处理
  write_transport_for_each_safe(transport, n, &__android_log_transport_write) {
    __android_log_cache_available(transport);
    if (!transport->logMask) {
      list_remove(&transport->node);
      continue;
    }
    if (!transport->open || ((*transport->open)() < 0)) {
      if (transport->close) {
        (*transport->close)();
      }
      list_remove(&transport->node);
      continue;
    }
    ++ret;
  }

  //遍历 __android_log_persist_write 链表,挨个处理
  write_transport_for_each_safe(transport, n, &__android_log_persist_write) {
    __android_log_cache_available(transport);
    if (!transport->logMask) {
      list_remove(&transport->node);
      continue;
    }
    if (!transport->open || ((*transport->open)() < 0)) {
      if (transport->close) {
        (*transport->close)();
      }
      list_remove(&transport->node);
      continue;
    }
    ++i;
  }
  if (!ret && !i) {
    return -ENODEV;
  }

  return ret;
}

这里先调用 __android_log_config_write() 函数来做配置工作,然后后面遍历两个链表,处理每个链表节点,可以猜到 __android_log_config_write() 函数内部可能会给这两个链表添加节点:

cpp 复制代码
// system/core/liblog/config_write.cpp

void __android_log_config_write() {
    // __android_log_transport 没有被赋值,默认值就是 0 即 LOGGER_DEFAULT
  if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
#if (FAKE_LOG_DEVICE == 0) // 进入分支

    // 两个结构体都是 extern 的
    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;

    /*
     * stderr logger should be primary if we can be the only one, or if
     * already in the primary list.  Otherwise land in the persist list.
     * Remember we can be called here if we are already initialized.
     */
    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);
    }
  }
}

这里 extern 了两个结构体,分别来自 system/core/liblog/logd_writer.cppsystem/core/liblog/pmsg_writer.cpp

cpp 复制代码
// system/core/liblog/logd_writer.cpp
struct android_log_transport_write logdLoggerWrite = {
    .node = {&logdLoggerWrite.node, &logdLoggerWrite.node},
    .context.sock = -EBADF,
    .name = "logd",
    .available = logdAvailable,
    .open = logdOpen,
    .close = logdClose,
    .write = logdWrite,
};

// system/core/liblog/pmsg_writer.cpp
struct android_log_transport_write pmsgLoggerWrite = {
    .node = {&pmsgLoggerWrite.node, &pmsgLoggerWrite.node},
    .context.fd = -1,
    .name = "pmsg",
    .available = pmsgAvailable,
    .open = pmsgOpen,
    .close = pmsgClose,
    .write = pmsgWrite,
};

接着通过 __android_log_add_transport 函数将两个结构体分别写入 _android_log_transport_write __android_log_persist_write 链表。

接着回到 __write_to_log_initialize() 函数

cpp 复制代码
static int __write_to_log_initialize() {
  struct android_log_transport_write* transport;
  struct listnode* n;
  int i = 0, ret = 0;

  // 配置
  __android_log_config_write();

  //遍历 __android_log_transport_write 链表,挨个处理
  write_transport_for_each_safe(transport, n, &__android_log_transport_write) {
    __android_log_cache_available(transport);
    if (!transport->logMask) {
      list_remove(&transport->node);
      continue;
    }
    if (!transport->open || ((*transport->open)() < 0)) {
      if (transport->close) {
        (*transport->close)();
      }
      list_remove(&transport->node);
      continue;
    }
    ++ret;
  }

  //遍历 __android_log_persist_write 链表,挨个处理
  write_transport_for_each_safe(transport, n, &__android_log_persist_write) {
    __android_log_cache_available(transport);
    if (!transport->logMask) {
      list_remove(&transport->node);
      continue;
    }
    if (!transport->open || ((*transport->open)() < 0)) {
      if (transport->close) {
        (*transport->close)();
      }
      list_remove(&transport->node);
      continue;
    }
    ++i;
  }
  if (!ret && !i) {
    return -ENODEV;
  }

  return ret;
}

配置完成后,就开始遍历链表,调用链表的 open 函数,我们以 logdLoggerWrite 节点为例来做分析

cpp 复制代码
// system/core/liblog/logd_writer.cpp

struct android_log_transport_write logdLoggerWrite = {
    .node = {&logdLoggerWrite.node, &logdLoggerWrite.node},
    .context.sock = -EBADF,
    .name = "logd",
    .available = logdAvailable,
    .open = logdOpen,
    .close = logdClose,
    .write = logdWrite,
};

static int logdOpen() {
  int i, ret = 0;

  i = atomic_load(&logdLoggerWrite.context.sock);
  if (i < 0) {
    // 初始化 socket
    int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
    if (sock < 0) {
      ret = -errno;
    } else {
      // 目标 socket 地址
      struct sockaddr_un un;
      memset(&un, 0, sizeof(struct sockaddr_un));
      un.sun_family = AF_UNIX;
      strcpy(un.sun_path, "/dev/socket/logdw");

      // connect 目标 socket
      if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) <
          0) {
        ret = -errno;
        switch (ret) {
          case -ENOTCONN:
          case -ECONNREFUSED:
          case -ENOENT:
            i = atomic_exchange(&logdLoggerWrite.context.sock, ret);
            [[fallthrough]];
          default:
            break;
        }
        close(sock);
      } else {
        ret = atomic_exchange(&logdLoggerWrite.context.sock, sock);
        if ((ret >= 0) && (ret != sock)) {
          close(ret);
        }
        ret = 0;
      }
    }
  }

  return ret;
}

内容很简单,socket 连接的一般套路:

  • 初始化 socket
  • 构建目标 socket 地址
  • connect 目标 socket

这里就完成了 socket 通信的准备工作。

接下来就会调用 __write_to_log_daemon 发起通信:

cpp 复制代码
static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
  struct android_log_transport_write* node;
  int ret, save_errno;
  struct timespec ts;
  size_t len, i;

  for (len = i = 0; i < nr; ++i) {
    len += vec[i].iov_len;
  }
  if (!len) {
    return -EINVAL;
  }

  save_errno = errno;
#if defined(__ANDROID__)
  clock_gettime(android_log_clockid(), &ts);

  if (log_id == LOG_ID_SECURITY) {
    if (vec[0].iov_len < 4) {
      errno = save_errno;
      return -EINVAL;
    }

    ret = check_log_uid_permissions();
    if (ret < 0) {
      errno = save_errno;
      return ret;
    }
    if (!__android_log_security()) {
      /* If only we could reset downstream logd counter */
      errno = save_errno;
      return -EPERM;
    }
  } else if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS) {
    const char* tag;
    size_t len;
    EventTagMap *m, *f;

    if (vec[0].iov_len < 4) {
      errno = save_errno;
      return -EINVAL;
    }

    tag = NULL;
    len = 0;
    f = NULL;
    m = (EventTagMap*)atomic_load(&tagMap);

    if (!m) {
      ret = __android_log_trylock();
      m = (EventTagMap*)atomic_load(&tagMap); /* trylock flush cache */
      if (!m) {
        m = android_openEventTagMap(NULL);
        if (ret) { /* trylock failed, use local copy, mark for close */
          f = m;
        } else {
          if (!m) { /* One chance to open map file */
            m = (EventTagMap*)(uintptr_t)-1LL;
          }
          atomic_store(&tagMap, (uintptr_t)m);
        }
      }
      if (!ret) { /* trylock succeeded, unlock */
        __android_log_unlock();
      }
    }
    if (m && (m != (EventTagMap*)(uintptr_t)-1LL)) {
      tag = android_lookupEventTag_len(m, &len, get4LE(static_cast<uint8_t*>(vec[0].iov_base)));
    }
    ret = __android_log_is_loggable_len(ANDROID_LOG_INFO, tag, len, ANDROID_LOG_VERBOSE);
    if (f) { /* local copy marked for close */
      android_closeEventTagMap(f);
    }
    if (!ret) {
      errno = save_errno;
      return -EPERM;
    }
  } else {
    /* Validate the incoming tag, tag content can not split across iovec */
    char prio = ANDROID_LOG_VERBOSE;
    const char* tag = static_cast<const char*>(vec[0].iov_base);
    size_t len = vec[0].iov_len;
    if (!tag) {
      len = 0;
    }
    if (len > 0) {
      prio = *tag;
      if (len > 1) {
        --len;
        ++tag;
      } else {
        len = vec[1].iov_len;
        tag = ((const char*)vec[1].iov_base);
        if (!tag) {
          len = 0;
        }
      }
    }
    /* tag must be nul terminated */
    if (tag && strnlen(tag, len) >= len) {
      tag = NULL;
    }

    if (!__android_log_is_loggable_len(prio, tag, len - 1, ANDROID_LOG_VERBOSE)) {
      errno = save_errno;
      return -EPERM;
    }
  }
#else
  /* simulate clock_gettime(CLOCK_REALTIME, &ts); */
  {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    ts.tv_sec = tv.tv_sec;
    ts.tv_nsec = tv.tv_usec * 1000;
  }
#endif

  ret = 0;
  i = 1 << log_id;

  // 关注这里
  write_transport_for_each(node, &__android_log_transport_write) {
    if (node->logMask & i) {
      ssize_t retval;
      retval = (*node->write)(log_id, &ts, vec, nr);
      if (ret >= 0) {
        ret = retval;
      }
    }
  }

  write_transport_for_each(node, &__android_log_persist_write)  {
    if (node->logMask & i) {
      (void)(*node->write)(log_id, &ts, vec, nr);
    }
  }

  errno = save_errno;
  return ret;
}

函数特别长,大部分都是一些特殊情况边界条件的处理,核心的流程其实是遍历 __android_log_transport_write __android_log_persist_write 两个链表,然后调用链表节点的 write 函数,这里我们还是以 logdLoggerWrite 节点为例来做分析:

cpp 复制代码
// system/core/liblog/logd_writer.cpp
struct android_log_transport_write logdLoggerWrite = {
    .node = {&logdLoggerWrite.node, &logdLoggerWrite.node},
    .context.sock = -EBADF,
    .name = "logd",
    .available = logdAvailable,
    .open = logdOpen,
    .close = logdClose,
    .write = logdWrite, 
};
 
// write 指针定义为 logdWrite 函数
static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
  ssize_t ret;
  int sock;
  static const unsigned headerLength = 1;
  struct iovec newVec[nr + headerLength];
  android_log_header_t header;
  size_t i, payloadSize;
  static atomic_int dropped;
  static atomic_int droppedSecurity;

  sock = atomic_load(&logdLoggerWrite.context.sock);
  if (sock < 0) switch (sock) {
      case -ENOTCONN:
      case -ECONNREFUSED:
      case -ENOENT:
        break;
      default:
        return -EBADF;
    }

  /* logd, after initialization and priv drop */
  if (__android_log_uid() == AID_LOGD) {
    /*
     * ignore log messages we send to ourself (logd).
     * Such log messages are often generated by libraries we depend on
     * which use standard Android logging.
     */
    return 0;
  }

  /*
   *  struct {
   *      // what we provide to socket
   *      android_log_header_t header;
   *      // caller provides
   *      union {
   *          struct {
   *              char     prio;
   *              char     payload[];
   *          } string;
   *          struct {
   *              uint32_t tag
   *              char     payload[];
   *          } binary;
   *      };
   *  };
   */

  header.tid = gettid();
  header.realtime.tv_sec = ts->tv_sec;
  header.realtime.tv_nsec = ts->tv_nsec;

  newVec[0].iov_base = (unsigned char*)&header;
  newVec[0].iov_len = sizeof(header);

  if (sock >= 0) {
    int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
    if (snapshot) {
      android_log_event_int_t buffer;

      header.id = LOG_ID_SECURITY;
      buffer.header.tag = htole32(LIBLOG_LOG_TAG);
      buffer.payload.type = EVENT_TYPE_INT;
      buffer.payload.data = htole32(snapshot);

      newVec[headerLength].iov_base = &buffer;
      newVec[headerLength].iov_len = sizeof(buffer);

      ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
      if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
        atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
      }
    }
    snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
    if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
                                                  ANDROID_LOG_VERBOSE)) {
      android_log_event_int_t buffer;

      header.id = LOG_ID_EVENTS;
      buffer.header.tag = htole32(LIBLOG_LOG_TAG);
      buffer.payload.type = EVENT_TYPE_INT;
      buffer.payload.data = htole32(snapshot);

      newVec[headerLength].iov_base = &buffer;
      newVec[headerLength].iov_len = sizeof(buffer);

      ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
      if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
        atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
      }
    }
  }

  header.id = logId;

  for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
    newVec[i].iov_base = vec[i - headerLength].iov_base;
    payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;

    if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
      newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
      if (newVec[i].iov_len) {
        ++i;
      }
      break;
    }
  }

  /*
   * The write below could be lost, but will never block.
   *
   * ENOTCONN occurs if logd has died.
   * ENOENT occurs if logd is not running and socket is missing.
   * ECONNREFUSED occurs if we can not reconnect to logd.
   * EAGAIN occurs if logd is overloaded.
   */
  if (sock < 0) {
    ret = sock;
  } else {
    ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i));
    if (ret < 0) {
      ret = -errno;
    }
  }
  switch (ret) {
    case -ENOTCONN:
    case -ECONNREFUSED:
    case -ENOENT:
      if (__android_log_trylock()) {
        return ret; /* in a signal handler? try again when less stressed */
      }
      __logdClose(ret);
      ret = logdOpen();
      __android_log_unlock();

      if (ret < 0) {
        return ret;
      }

      ret = TEMP_FAILURE_RETRY(writev(atomic_load(&logdLoggerWrite.context.sock), newVec, i));
      if (ret < 0) {
        ret = -errno;
      }
      [[fallthrough]];
    default:
      break;
  }

  if (ret > (ssize_t)sizeof(header)) {
    ret -= sizeof(header);
  } else if (ret == -EAGAIN) {
    atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
    if (logId == LOG_ID_SECURITY) {
      atomic_fetch_add_explicit(&droppedSecurity, 1, memory_order_relaxed);
    }
  }

  return ret;
}

代码很长,不用细看,其实就是按协议规则,准备好数据,然后通过 writev 发送给服务端,具体的协议规则我们在服务端来分析,那里更清晰。

参考资料

  1. Android 日志系统分析(一):概述
  2. 深入理解安卓日志系统(logcat / liblog / logd)

关于

我叫阿豪,2015 年本科毕业于国防科学技术大学指挥信息系统专业,毕业后从事信息化装备的研发工作,工作内容主要涉及 Android Framework 与 Linux Kernel。

如果你对 Android Framework 感兴趣或者正在学习 Android Framework,可以关注我的微信公众号和抖音,我会持续分享我的学习经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

相关推荐
风槐啊37 分钟前
六、Java 基础语法(下)
android·java·开发语言
皮皮虾在睡觉2 小时前
数据库基础04
android·数据库
彭于晏6894 小时前
Android高级控件
android·java·android-studio
666xiaoniuzi9 小时前
深入理解 C 语言中的内存操作函数:memcpy、memmove、memset 和 memcmp
android·c语言·数据库
沐言人生13 小时前
Android10 Framework—Init进程-8.服务端属性文件创建和mmap映射
android
沐言人生14 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
沐言人生14 小时前
Android10 Framework—Init进程-7.服务端属性安全上下文序列化
android·android studio·android jetpack
追光天使14 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru14 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app