在服务器上搭建Android开发调试环境的尝试

本文约 3000 字,阅读需 6 分钟。

1. 背景说明

当前的开发工作特点:

  • 项目代码C++为主,编译任务重,最大的工程全量编一次80min+,小的改动增量10min+,更新一下增量构建30min+。而且编译期间CPU全部占用,无法进行其他工作
  • 涉及多个平台,本地开发机是iMac(10核32G内存1T固态硬盘)

目标:把Android的构建放在DevCloud(云服务器,16核32G内存1T硬盘)上,可以有效避免Android构建对本地工作流的打断,释放本地硬盘空间,甚至可以同时进行2个平台的开发、构建、调试。

挑战:DevCloud如何能像本机开发机那样和Android设备无缝交互?

2. 方案思考

方案1:远程编译

简单来说,就是在Local和Server(后面就这么称呼了)同时各有一份代码,本地修改,通过rsync同步给Server,Server构建,再把产物通过rsync同步到Local。这样做的问题:

  • 由于经常有调试需要,对于C++而言,此时要配置映射:调试符号表的文件路径->本地文件路径,比较繁琐。
  • 每个工程,需要定制ignore配置(中间产物一般不同步),如果工程多,工作量挺大(每个工程都要了解其构建细节),而且如果只有自己用,ROI太低

方案2:DevCloud直连Android

简单来说,就是让DevCloud能像Local那样直接操作Android设备。这样的好处是如果验证成功了,就可以快速复用到任意一个Android项目,ROI比较高。

当然了,对应的难度也比较大。下面是踩坑记录。

3. 实践记录

3.1 可行性调研

简单了解了下ADB的原是,发现是C/S模式:ADB Client和ADB Server(默认占用5037端口)应该是通过Socket通信。这样就基本可行了:Server的ADB Client和Local的ADB Server通信,而Local的ADB Server和Android的adbd通信。整个链路是通的。

这里有个问题,就是要实现这种通信,需要做端口转发,每次登陆DevCloud的时候,都会提示:ssh -R/-L 之类的行为是不安全的,所以接下来就是咨询安全的同学,看有没有可能实现。

3.2 安全咨询

具体记录就不贴了,得到的结论是“本地PC和Devcloud是可以互相通信的,但不能涉及外网”,自己这个Case应该不涉及,看来是自己之前理解有偏差(自己对于网络安全这块的理解有待加强)。

3.3 ADB链路

这部分比较容易,如下:

# 登陆DevCloud
# ssh -R  5037:localhost:5037 devcloud
# 杀死Server的ADB Server,防止冲突
adb kill-server
# 尝试连接本地的ADB Server,注意会有版本校验
adb server version (40) doesn't match this client (41); killing...

本地Android SDK如何更新:

# Android Studio用最新的
# 注意用cmdline-tools目录下的,通过sdkmanager命令行更新
$ find . -iname "sdkmanager"
./tools/bin/sdkmanager
./cmdline-tools/latest/bin/sdkmanager

在Server上执行adb devices能看到设备,这步就成功了。

3.4 LLDB链路

低估了这一个环节的难度!

# Server上尝试,遇到一个错误
(lldb) platform select remote-android
(lldb) platform connect unix-abstract-connect:///com.xxx.yyy/debug.sock
error: Failed to connect port

不是很理解,于是本地同样的命令试了下,发现可以:

(lldb) platform connect unix-abstract-connect:///com.xxx.yyy/debug.sock
  Platform: remote-android
    Triple: aarch64-unknown-linux-android
OS Version: 31 (5.4.86-qgki-g2ee5fc001494-dirty)
  Hostname: localhost
 Connected: yes
    Kernel: #1 SMP PREEMPT Fri Apr 14 11:19:58 CST 2023

按照经验,猜测可能是端口问题!于是查看本地使用lldb时,执行platform connect/disconnect时的端口变化,发现确实有新增端口!

pic

(Via 活动监视器)

比较棘手的是,每次使用的端口是随机的,胡乱尝试一圈后,发现原来文档都有写!

From: https://lldb.llvm.org/use/remote.html#local-system When using the “remote-android” platform, the client LLDB forwards two ports, one for connecting to the platform, and another for connecting to the gdbserver. The client ports are configurable through the environment variables ANDROID_PLATFORM_LOCAL_PORT and ANDROID_PLATFORM_LOCAL_GDB_PORT, respectively.

比较坑爹的是,这个参数是最近才添加的,但是lldb的文档完全没有版本概念。在我尝试了半天没有效果后,无奈clone了llvm的源码,编了个lldb准备加点日志看下,结果发现编出来的lldb竟然读到了环境变量!!

中午吃饭时才反应过来,会不会是这个功能是最近才加的,于是赶紧翻了下提交记录,相关链接:

目前通过软件源安装的llvm都是14(25 Mar 2022/14.0.0~24 Jun 2022/14.0.6),这个特性要16才有,怪不得自己试了半天都没有效果,原来是被不严谨的文档坑了(没有版本概念-_-)!!!

经过这一步,platform connect xxxx终于在Server上跑起来了。接下来就是process attach -p xxx,又遇到了error: Failed to connect port的问题,不过这一次轻车熟路,配置文档中提到的另一个宏就可以了。

最终需要转发3个端口:

ssh -R  5037:localhost:5037  -R  50012:localhost:50012  -R  50013:localhost:50013  devcloud

3.5 VSCode链路

接下来就是用VSCode调试的链路了,按理lldb命令行跑通了,应该不存在技术阻碍的。但坑还是挺多的。

第一,如何让VSCode(的codelldb插件)使用自定义的lldb?比较粗暴:

mv /usr/bin/lldb /usr/bin/lldb-14
ln -s ~/.install/llvm16.0.0/bin/lldb /usr/bin/lldb

第二个问题比较坑,就是codelldb在启动lldb的时候,无论如何都配置不了自定义的环境变量,比如:

  • bashrc /bashprofile
  • etc/environment
  • .zshrc
  • platform shell export ANDROID_PLATFORM_LOCAL_PORT

最终,在VSCode的Setting里面发现了线索,终于配置成功,codelldb的文档也没有写,略坑(像个无头苍蝇试了一上午…)

pic

后面有必要研究下这个配置的原理(lldb.adapterEnv)以及codelldb的源码。

如此,就基本成功了。剩下一些细节,如add-dsym/append target.exec-search-paths之类的就不赘述了。

4. 番外

4.1 关于lldb

从官网下载的lldb 16 的命令行补全有问题,但在VSCode的插件中使用又很丝滑。 本地clone代码编的lldb是能正确补全的。

Mac平台,lldb在 根目录/.build 一次构建成功:

cmake  -DLLVM_ENABLE_PROJECTS='clang;lldb' .. -DCMAKE_BUILD_TYPE=Debug -DLLDB_INCLUDE_TESTS=OFF

CentOS Server一直离奇失败…

clang-14: error: unable to execute command: Killed
clang-14: error: linker command failed due to signal (use -v to see invocation)

单个目录基本可以成功,后来意识到,应该是make -j20导致clang占用太多内存,补系统Kill了,于是用make跑了一晚了,果然成功了。

另外,本地要libpython3.10.so,不是很好安装,把相关名字全部软链到到3.8也可以:

ln -s /usr/lib/libpython3.10.a /usr/local/lib/libpython3.10.a
ln -s /usr/lib64/libpython3.8.so.1.0  /usr/lib/libpython3.10.a
ln -s /usr/lib64/libpython3.8.so.1.0  /usr/lib/libpython3.10.so

4.2 platform connect

这个讨论给我很大启发,后面有必要研究下各种通信方式的细节。

关键是这里:

# 这个host很多教程写死成remote,是一种误导,有必要研究下其含义与依据
# https://lldb.llvm.org/use/remote.html
platform connect connect://39688bd9:10086

Android杀进程

adb shell psadb shell之后ps,结果是不同的,为啥?

5. 总结

  • 虽然对这一领域不甚了解,但还是在有限时间内完成了MVP,不然这事一直挂在心上,挥之不去。但确实因为不熟悉这些工具,走了不少弯路
  • 继续复用业余时间打磨,不仅可以应用在工程中,也可以应用在其他开源项目的研究中
  • 深入研究没太弄懂的地方
    • lldb/adb的原理
    • codelldb插件
    • 对于内网安全和端口转发的理解

6. 更新

6.1

2023/06/05,遇到一次诡异现象,明明端口都配置对了,就是connect失败,最终通过杀死本地ssh,重新登录解决。

(lldb) platform select remote-android
  Platform: remote-android
 Connected: no
(lldb) platform connect connect://f6d56607:1234
error: Failed to connect port
(lldb) platform connect connect://f6d56607:1234
  Platform: remote-android
    Triple: aarch64-unknown-linux-android
OS Version: 30 (4.14.180-perf-g11d81629da33)
  Hostname: localhost
 Connected: yes
WorkingDir: /data/user/0/com.tencent.tdf.core
    Kernel: #1 SMP PREEMPT Fri May 20 11:46:47 CST 2022

有问题的时候,ssh的“打开文件和端口”信息里,是没有localhost:60300->localhost:50012 这样的信息的,说明即使使用了ssh -R,转发也有可能失败。

6.2

Server端使用 adb logcat有局限性,日志不是实时刷新的

总阅读量次。