在服务器上搭建Android开发调试环境的尝试
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
时的端口变化,发现确实有新增端口!
(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竟然读到了环境变量!!
中午吃饭时才反应过来,会不会是这个功能是最近才加的,于是赶紧翻了下提交记录,相关链接:
- Oct 26, 2022 : Make remote-android local ports configurable · llvm/llvm-project@1e210ab
- How to debug a truly remote android? · Issue #58114 · llvm/llvm-project
目前通过软件源安装的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的文档也没有写,略坑(像个无头苍蝇试了一上午…)
后面有必要研究下这个配置的原理(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 ps
和adb 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有局限性,日志不是实时刷新的