Adb Logcat with Process Name

Adb日志

想查看Android端侧的日志信息输出,一般采用命令行 adb logcat或Android Stadio中Logcat插件的方式.最近要实现一个Android App的自动测试,所以需要使用真机连接到服务器上运行,因此更多使用命令行输出Logcat的形式.

但是Adb Logcat的输出中只包含pid\tid等信息,不包含包名等属性,导致无法过滤来自指定包名所有Activity的log.在Github上翻了下,好像也没有支持Package Name过滤的Logcat客户端.因此决定自己改一个出来.

如何获取Package Name?

这个问题的解答可能还要从AS的Logcat Plugin的源码入手.这部分功能在Plugin中被称为 ProcessMonitor.具体的代码仓库位于这里.这个功能由两部分组成,运行在端侧的Agent(C++编写),运行在Server端的AgentTracker(来采集并缓存ProcessName与Pid的映射)以及管理Agent运行.

端侧的Agent代码十分简单,只是一个loop来从procfs中获取进程信息,并从cmdline文件中截断出文件名.在loop中不断轮询/proc下所有pid,并与缓存的pid set进行比较,来输出pid新增或减少情况.Server端的Tracker根据Agent的输出来更新pid与ProcessName(在Zygote启动的Java App子进程中,这应当是包名).

 /**
   * Reads processes from "/proc" and retrieves their name from
   * "/proc/<pid>/cmdline" and prints: When a process is added:   "+ <pid>
   * <process-name>" When a process is removed: "- <pid>" New processes are
   * added to the processes set and processes that not longer exists are removed
   * from it.
   */
  void scanProcesses() {
    // Whatever is left in this set needs to be removed from ::processes
    set<int> markedForRemoval(processes);

    DIR* pDir = opendir("/proc");
    while (true) {
      dirent* pDirent = readdir(pDir);
      if (pDirent == nullptr) {
        break;
      }

      int pid = parseInt(pDirent->d_name, -1);
      // Ignore entries that are not a valid pid
      if (pid < 0) {
        continue;
      }
      markedForRemoval.erase(pid);
      if (processes.find(pid) != processes.end()) {
        continue;
      }

      // New process found
      string path = string("/proc/") + pDirent->d_name;
      const string& name = readCommand(path);
      if (name.empty() || startsWith(name, "zygote") ||
          name == "<pre-initialized>") {
        // Ignore processes without a name or that haven't initialized yet
        continue;
      }
      processes.insert(pid);
      printf("+ %d %s\n", pid, name.c_str());
    }
    closedir(pDir);

    for (const int pid : markedForRemoval) {
      processes.erase(pid);
      printf("- %d\n", pid);
    }
    ::fflush(stdout);
  }

这样实现确实比较简单,但是感觉并不算优雅.AS作为第一方的App可以采用这种设计,但是如果要设计一个第三方实现,在端侧启动一个Forever Loop应用程序似乎并不太合适.翻了下老版本的实现,发现有注释提到可以用 ps aux 来提取ProcessName.(这样的另一个好处是避免了权限问题,而且某些命令行服务在cmdline文件中可能是以全路径的格式保存,还要进一步分割).

成品

在Github上翻找一通,只找到了一个6年前的Rust项目rogcat 大致符合我的要求,奈何依赖库太老,甚至Tokio都还没有正式版.按照他的思路(使用nom处理logcat的原输出,Stream处理流式输出)仿了一个精简版:

image-20230926193029280

如果你感兴趣,也可以在这里找到这个简陋的项目.

Edit with markdown