Jenkins Emulator插件手动指定端口及APK安装失败问题排查
连接到正确端口后,安装操作仍然失败,核心原因是安装过程中存在进程文件描述符泄漏问题。按照相关技术社区的公开指引调整环境变量即可解决该问题。
更新2
经实测,调整环境变量仅能修改Gradle命令的目标端口,无法调整模拟器插件本身的等待循环逻辑,因此决定改为手动启动模拟器,对应执行命令为:
/var/lib/jenkins/android-sdk/emulator/emulator -avd testing24xlarge2 -wipe-data -no-window -no-audio -partition-size 4096 -ports 5558,5559 & EMULATOR_PID=$!
调整后发现未配置ADB超时时间,因此复用插件之前使用过的TCP端口重试,但操作完全没有生效,对应启动命令为:
/var/lib/jenkins/android-sdk/emulator/emulator -report-console tcp:5829,max=96000 -avd testing24xlarge2 -wipe-data -no-window -no-audio -partition-size 4096 -ports 5558,5559 & EMULATOR_PID=$!
目前能猜测的极低概率诱因是Debug安装包体积过大(测试所用应用体积在200MB至700MB区间)可能触发该问题,但报错信息没有指向该原因。尝试用ADB命令替代Gradle任务安装应用也没有生效,对应配置和命令为:
android { adbOptions { timeOutInMs 6000000 // 将超时时间设置为100分钟 } } # 执行构建命令 ./gradlew assembleStagingDebug --no-daemon -Dkotlin.compiler.execution.strategy="in-process" ./gradlew assembleStagingDebugAndroidTest --no-daemon -Dkotlin.compiler.execution.strategy="in-process" # 执行ADB操作 /var/lib/jenkins/android-sdk/platform-tools/adb -s emulator-5558 shell input keyevent 82 /var/lib/jenkins/android-sdk/platform-tools/adb -s emulator-5558 install -r -t myapp/build/outputs/apk/staging/debug/myapp-staging-debug.apk /var/lib/jenkins/android-sdk/platform-tools/adb -s emulator-5558 install -r -t myapp/build/outputs/apk/androidTest/staging/debug/myapp-staging-debug-androidTest.apk # 执行测试命令(已注释) # /var/lib/jenkins/android-sdk/platform-tools/adb -e shell am instrument -w mypackage.testcases.tests/androidx.test.runner.AndroidJUnitRunner
后续将在使用体积符合要求的安装包完成测试后,再补充更新该问题的最终结论。
中期更新:问题定位
我目前已经基本定位了问题根因,但仍然没有找到可落地的解决方法:
DDMLIB默认仅搜索5555-5586区间的端口,当模拟器运行在其他端口时,应用安装操作自然会失败。根据Android模拟器Jenkins插件的官方文档说明,可通过配置环境变量让模拟器插件在指定端口搜索设备,但我在Jenkins主配置中设置的所有端口相关环境变量(如ANDROID_SERIAL、ANDROID_AVD_DEVICE等),均没有改变adb -s的目标端口。
因此手动通过ports 5558,5559参数指定模拟器运行端口的操作会失败:模拟器虽然在5558端口正常启动,但插件执行的等待命令是:
/var/lib/jenkins/android-sdk/platform-tools/adb -s emulator-5776 wait-for-device shell getprop dev.bootcomplete
这条命令连接的是插件随机分配的端口,因此会一直卡在等待设备就绪的状态。
在解决了数十种模拟器环境下UI测试启动、运行的异常问题后,目前卡在最后一个卡点,已经尝试了所有能想到的方案均没有效果。
环境说明
- 为了规避
INCOMPATIBLE_DEVICE_ERROR错误,使用API等级为24的模拟器镜像,应用的最低SDK版本也设置为24 - Jenkins服务运行在Ubuntu 20.04系统上,我判断系统版本不会影响当前问题
- 应用的target SDK和compile SDK版本为28(我也会尝试降级这两个版本,但由于已经解决了INCOMPATIBLE错误,我判断降级无法解决当前问题)
ADB超时时间设置为96000秒,启动延迟设置为20秒预留缓冲时间
按脚本逻辑,可通过ADB命令正常检测到已连接的模拟器,但在安装应用并运行测试时,两种尝试方案均遇到问题。
第一种尝试方案
执行命令如下:
while [ "`/var/lib/jenkins/android-sdk/platform-tools/adb shell getprop service.bootanim.exit | tr -d '\r' `" != "1" ] ; do sleep 1; done /var/lib/jenkins/android-sdk/platform-tools/adb devices -l /var/lib/jenkins/android-sdk/platform-tools/adb shell input keyevent 82 ./gradlew myapp:connectedStagingDebugAndroidTest --no-daemon -Dkotlin.compiler.execution.strategy="in-process"
脚本可正常等待设备启动(Android模拟器插件在此之前也会校验sys.boot_completed状态),可正常列出已连接设备,也可正常执行input keyevent 82命令。
但最终抛出如下报错:
com.android.build.gradle.internal.testing.ConnectedDevice > runTests[testing24xlarge2(AVD) - 7.0] FAILED com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException at com.android.build.gradle.internal.testing.ConnectedDevice.installPackage(ConnectedDevice.java:132) [no message defined] java.util.concurrent.ExecutionException: java.lang.RuntimeException: com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException at java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1006) at com.android.ide.common.workers.ExecutorServiceAdapter.await(ExecutorServiceAdapter.kt:102) at com.android.build.gradle.internal.testing.BaseTestRunner.runTests(BaseTestRunner.java:201) at com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask.lambda$doTaskAction$2(DeviceProviderInstrumentTestTask.java:201) at com.android.builder.testing.api.DeviceProvider.use(DeviceProvider.java:53) at com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask.doTaskAction(DeviceProvider.java:191) Caused by: java.lang.RuntimeException: com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException at com.android.build.gradle.internal.testing.SimpleTestRunnable.run(SimpleTestRunnable.java:231) Caused by: com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException at com.android.build.gradle.internal.testing.ConnectedDevice.installPackage(ConnectedDevice.java:132) at com.android.build.gradle.internal.testing.SimpleTestRunnable.run(SimpleTestRunnable.java:134) ... 116 more Caused by: com.android.ddmlib.InstallException at com.android.ddmlib.Device.installRemotePackage(Device.java:1174) at com.android.ddmlib.Device.installPackage(Device.java:998) at com.android.ddmlib.Device.installPackage(Device.java:974) at com.android.ddmlib.Device.installPackage(Device.java:963) at com.android.build.gradle.internal.testing.ConnectedDevice.installPackage(ConnectedDevice.java:126) ... 117 more Caused by: com.android.ddmlib.ShellCommandUnresponsiveException at com.android.ddmlib.AdbHelper.executeRemoteCommand(AdbHelper.java:553) at com.android.ddmlib.AdbHelper.executeRemoteCommand(AdbHelper.java:378) at com.android.ddmlib.Device.executeShellCommand(Device.java:675) at com.android.ddmlib.Device.installRemotePackage(Device.java:1165) ... 121 more
(已在保留关键报错信息的前提下尽可能裁剪了异常链内容)
执行ADB设备列表命令,可看到模拟器已成功连接:
+ /var/lib/jenkins/android-sdk/platform-tools/adb devices -l List of devices attached emulator-5802 device product:sdk_google_phone_armv7 model:sdk_google_phone_armv7 device:generic transport_id:1
第二种尝试方案
参考技术社区公开的同类问题解决方案调整流程,也没有生效,对应命令为:
./gradlew clean ./gradlew assembleDebug ./gradlew assembleDebugAndroidTest /var/lib/jenkins/android-sdk/platform-tools/adb install app/build/outputs/apk/app-debug.apk /var/lib/jenkins/android-sdk/platform-tools/adb install app/build/outputs/apk/app-debug-androidTest-unaligned.apk /var/lib/jenkins/android-sdk/platform-tools/adb shell am instrument -w com.google.samples.apps.topeka.test/android.support.test.runner.AndroidJUnitRunner
执行完如下命令后,控制台输出会直接卡在安装命令步骤,无论是否添加-r -t -g参数都没有变化,后续命令也无法执行:
/var/lib/jenkins/android-sdk/platform-tools/adb shell input keyevent 82 /var/lib/jenkins/android-sdk/platform-tools/adb install -r -t -g myapp/build/outputs/apk/staging/debug/myapp-staging-debug.apk # 也尝试过使用adb -e参数直接指定当前唯一运行的模拟器作为目标设备
等待较长时间后抛出报错,提示ADB可能因为模拟器所在端口不符合规范无法正常工作:
adb: failed to install myapp/build/outputs/apk/staging/debug/myapp-staging-debug.apk: Performing Streamed Install Build step 'Execute shell' marked build as failure [android] Stopping Android emulator emulator: WARNING: encryption is off emulator: feeding guest with passive gps data, in headless mode emulator: WARNING: Requested adb port (5775) is outside the recommended range [5555,5586]. ADB may not function properly for the emulator. See -help-port for details. saving arm snapshot.... !!!
目前的疑问:该问题是否是端口配置错误导致?如何在Android模拟器插件中指定需要使用的固定端口?目前插件仅支持自动分配唯一TCP端口避免冲突的功能,即使在模拟器启动参数中指定端口,插件仍会使用其随机分配的端口执行ADB命令,因此该方案无效。后续尝试复制控制台输出的插件启动命令手动启动模拟器,也没有生效——手动添加这些参数后模拟器无法正常启动,对应命令如下:
/var/lib/jenkins/android-sdk/platform-tools/adb start-server /var/lib/jenkins/android-sdk/emulator/emulator -report-console tcp:5845,max=96000 -avd testing24xlarge2 -wipe-data -no-window -partition-size 4096 -ports 5558,5559 while [ "`/var/lib/jenkins/android-sdk/platform-tools/adb shell getprop service.bootanim.exit | tr -d '\r' `" != "1" ] ; do sleep 1; done # 等待就绪的逻辑无法通过
目前完全卡在该问题上,任意一种可正常运行的方案均可接受。
内容的提问来源于Stack Exchange,提问作者Prethia

