ADB批量安装APK到多台安卓设备:Python与Shell自动化脚本实战
1. 项目概述与核心价值
如果你是一名移动应用开发工程师、测试工程师,或者是一名需要频繁在大量安卓设备上部署应用的运维人员,那么“如何实现adb同时安装多个apk到多个设备”这个问题,你一定感同身受。想象一下,你手头有几十台测试机,每次版本迭代,都需要手动连接每一台设备,执行adb install命令。这个过程不仅枯燥、耗时,还极易出错,比如装错了版本、漏装了某台设备,或者因为设备连接不稳定而中断。这种重复性劳动,严重拖慢了开发测试的节奏。
这个项目的核心价值,就是通过自动化脚本,将我们从这种低效的“体力活”中解放出来。它要解决的不是一个高深的算法问题,而是一个典型的“工程效率”问题。其目标非常明确:一键操作,批量执行。具体来说,就是编写一个脚本或工具,能够自动识别当前连接的所有安卓设备,并针对每一台设备,依次安装一个或多个指定的APK安装包。这听起来简单,但其中涉及到设备状态管理、命令并行/串行执行、错误处理、日志记录等多个实操细节,把这些细节处理好,才能真正做到稳定、可靠、省心。
在实际工作中,这个需求场景非常普遍。比如,在兼容性测试中,我们需要将同一个APK安装到数十款不同型号、不同系统版本的手机上;在功能测试中,我们可能需要将一个主APK和若干个插件APK同时部署到测试机;在演示环境搭建时,也需要快速在多台设备上安装好演示应用。手动操作在这些场景下是完全不可行的。因此,掌握这项自动化技能,是提升个人和团队工作效率的一个非常实在的切入点。
接下来,我将从一个多年移动端开发者的角度,详细拆解实现这一目标的完整思路、技术细节、避坑指南,并提供可直接复用的脚本方案。
2. 核心思路与方案设计
实现“多设备多APK”安装,核心在于两个循环的嵌套:设备循环和APK文件循环。最直接的思路是:先获取设备列表,然后遍历每一台设备,在每台设备上再遍历每一个APK文件进行安装。这个基础模型是所有方案的起点。
2.1 基础模型:串行执行
最简单的实现是串行模型:
- 使用
adb devices命令获取在线设备列表。 - 过滤掉列表头(如
List of devices attached)和非法行(如offline状态的设备)。 - 对于列表中的每一台设备(记为 Device_A):
- 对于指定的每一个APK文件(记为 APK_1): 2. 执行
adb -s Device_A install APK_1。
- 对于指定的每一个APK文件(记为 APK_1): 2. 执行
这个模型的优点是逻辑清晰,易于理解和调试。但缺点也很明显:效率低。如果设备多、APK多,总耗时是单个设备安装单个APK耗时的乘积,等待时间会很长。
2.2 进阶模型:并行执行
为了提升效率,我们很自然地会想到并行。这里的“并行”通常指在设备维度上进行并行。也就是说,同时对所有设备发起安装任务,而不是等一台设备装完所有APK再装下一台。
为什么选择在设备维度并行?因为每台设备都是独立的硬件,它们之间的安装操作互不干扰。而在一台设备上连续安装多个APK,有时存在依赖关系(比如先装基础包再装插件),或者安装本身是串行的(虽然可以一个接一个发命令,但设备处理时需要时间)。因此,更高效的策略是:为每一台设备启动一个独立的安装进程,这个进程负责将该设备需要安装的所有APK串行安装完毕。
这样,假设有N台设备,安装时间就从“所有设备串行时间之和”缩短到了“单台设备安装最慢的那台所花费的时间”,效率提升立竿见影。
2.3 技术方案选型
基于上述思路,我们有几种常见的技术实现方案:
Shell脚本 (Bash/Batch):
- 优点:无需额外环境依赖,直接利用操作系统和ADB的能力。利用
&操作符(在Unix-like系统)或start命令(在Windows批处理)可以方便地实现后台进程,从而达到并行效果。 - 缺点:跨平台兼容性差(需分别写Bash和Batch脚本),错误处理和进程管理相对简陋。
- 适用场景:快速实现、轻量级任务、运行环境固定的情况。
- 优点:无需额外环境依赖,直接利用操作系统和ADB的能力。利用
Python脚本:
- 优点:跨平台,语法简洁,拥有强大的标准库(如
subprocess用于执行命令,threading/multiprocessing或concurrent.futures用于并发控制,argparse用于参数解析)。可以非常精细地控制并发度、处理超时、收集和整理日志。 - 缺点:需要目标机器安装Python环境。
- 适用场景:需要复杂逻辑、良好错误处理、日志记录和跨平台部署的中大型项目。
- 优点:跨平台,语法简洁,拥有强大的标准库(如
Gradle/构建脚本插件:
- 优点:与Android项目原生集成,可以在构建流程中直接调用,方便CI/CD集成。
- 缺点:灵活性相对脚本较差,更依赖于特定的项目结构。
- 适用场景:在Android Studio项目内,作为自定义构建任务使用。
对于大多数追求灵活性和控制力的开发者,Python脚本是首选方案。它平衡了功能、易用性和可维护性。因此,下文将主要围绕Python方案展开,并会对比给出Shell脚本的核心思路。
3. 核心细节解析与实操要点
在动手编码之前,有几个关键细节必须提前厘清,它们直接决定了脚本的健壮性和实用性。
3.1 设备列表的获取与过滤
adb devices命令的输出不是纯净的设备ID列表。它的典型输出如下:
List of devices attached emulator-5554 device 192.168.1.100:5555 device ABCDEF0123456789 unauthorized ZXCVBNM987654321 offline我们需要解析这个输出,提取出状态为device的行中的设备标识符(如emulator-5554,192.168.1.100:5555)。状态为unauthorized(未授权)或offline(离线)的设备无法执行安装命令,必须被过滤掉。
注意:
unauthorized状态常见于真机首次连接时,需要在设备上点击“允许USB调试”。脚本应能识别并提示这类设备,而不是简单地忽略或报错退出。
3.2 安装命令的参数与选项
adb install命令有几个常用参数,直接影响安装行为:
-r: 替换已存在的应用(相当于升级安装)。在批量安装中,强烈建议加上此参数,因为它可以覆盖安装,避免因已存在旧版本而失败。-t: 允许安装测试APK(针对android:testOnly的应用)。如果你的APK是测试包,需要此参数。-d: 允许版本降级安装。在需要回退版本测试时有用。-g: 授予APK清单文件中声明的所有运行时权限。在Android 6.0+上,可以避免安装后首次运行还需要手动点授权。
在批量脚本中,一个稳妥的安装命令基础模板是:adb -s <device_id> install -r <apk_path>。根据需求再添加-t或-g。
3.3 并发控制与资源管理
虽然我们希望在设备维度并行,但“无限制的并行”可能带来问题。如果你同时连接了50台设备,并瞬间发起50个并行的adb install进程,可能会:
- 耗尽本机的CPU或I/O资源,导致脚本本身响应缓慢。
- ADB服务端可能因瞬间请求过多而出现不稳定。
- 不利于观察日志(50个进程的输出混在一起)。
因此,引入并发度控制是必要的。例如,可以设置一个最大并发数(如5或10),让脚本最多同时处理这个数量的设备,其他设备排队等待。这可以通过Python的ThreadPoolExecutor或Semaphore轻松实现。
3.4 错误处理与日志记录
批量操作中,部分失败是常态。脚本必须具备良好的错误处理能力:
- 命令执行失败:
adb install可能因各种原因失败(签名冲突、空间不足、不兼容等)。脚本需要捕获这些错误(通过检查命令的返回码),并记录下是哪个设备、哪个APK失败了,以及失败原因(尽可能从输出中提取)。 - 设备断开连接:在安装过程中,设备可能意外断开。脚本需要能感知到这种变化,并将对应设备的任务标记为失败,而不是无限期挂起。
- 日志分离:理想的日志系统应该能同时提供两种视图:
- 全局进度日志:记录脚本开始、结束、总体进度。
- 每设备详细日志:每个设备的安装过程输出(stdout和stderr)最好能分别重定向到独立的日志文件中,方便事后排查特定设备的问题。
4. 基于Python的完整实现方案
下面我将展示一个功能相对完整的Python脚本,它包含了上述所有考量,并添加了参数解析、进度显示等实用功能。
4.1 环境准备与依赖
确保你的开发机上已经具备:
- Python 3.6+:现代Python版本即可。
- ADB命令行工具:已安装并添加到系统PATH环境变量中。在终端输入
adb version能正常显示版本号即表示配置成功。 - 目标APK文件:准备一个或多个需要安装的APK文件。
无需安装额外的Python第三方库,我们将完全使用标准库:subprocess,concurrent.futures,argparse,os,re,time。
4.2 脚本代码详解
我们将脚本保存为batch_install.py。
#!/usr/bin/env python3 """ 批量安装APK到多个Android设备 用法: python batch_install.py [APK文件路径1] [APK文件路径2] ... [选项] """ import subprocess import re import argparse import os import sys from concurrent.futures import ThreadPoolExecutor, as_completed import time def get_connected_devices(): """ 获取当前所有已连接且状态为‘device’的ADB设备ID列表。 返回: 设备ID列表,例如 ['emulator-5554', '192.168.1.100:5555'] """ try: # 执行 adb devices 命令 result = subprocess.run(['adb', 'devices'], capture_output=True, text=True, timeout=10) output = result.stdout except subprocess.TimeoutExpired: print("[错误] 执行 'adb devices' 命令超时,请检查ADB服务。") return [] except FileNotFoundError: print("[错误] 未找到adb命令,请确保ADB已安装并加入PATH环境变量。") return [] devices = [] lines = output.strip().splitlines() # 跳过第一行 “List of devices attached” for line in lines[1:]: if line.strip(): # 跳过空行 parts = line.split() if len(parts) >= 2 and parts[1] == 'device': devices.append(parts[0]) elif len(parts) >= 2: print(f"[信息] 设备 {parts[0]} 状态为 '{parts[1]}',将被跳过。") return devices def install_apk_to_device(device_id, apk_paths, install_args, device_log_dir): """ 在单个设备上安装一系列APK文件。 参数: device_id: 设备标识符 apk_paths: APK文件路径列表 install_args: 传递给 `adb install` 的额外参数列表,如 ['-r', '-g'] device_log_dir: 存放该设备详细日志的目录 返回: (device_id, success_count, total_count, error_messages) """ os.makedirs(device_log_dir, exist_ok=True) log_file_path = os.path.join(device_log_dir, f'install_log_{device_id.replace(":", "_").replace(".", "_")}.txt') success = 0 total = len(apk_paths) errors = [] with open(log_file_path, 'w', encoding='utf-8') as log_file: log_file.write(f"=== 开始为设备 {device_id} 安装 {total} 个APK ===\n") log_file.write(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") log_file.write(f"安装参数: {install_args}\n") log_file.write("-" * 50 + "\n") for i, apk_path in enumerate(apk_paths, 1): apk_name = os.path.basename(apk_path) if not os.path.exists(apk_path): error_msg = f"APK文件不存在: {apk_path}" print(f"[设备 {device_id}] 错误: {error_msg}") log_file.write(f"[{i}/{total}] {apk_name}: 文件不存在\n") errors.append(error_msg) continue # 构建安装命令 cmd = ['adb', '-s', device_id, 'install'] + install_args + [apk_path] log_file.write(f"[{i}/{total}] 执行: {' '.join(cmd)}\n") try: # 执行安装命令,同时捕获输出 process = subprocess.run(cmd, capture_output=True, text=True, timeout=120) # 设置2分钟超时 stdout = process.stdout stderr = process.stderr returncode = process.returncode log_file.write(f"返回码: {returncode}\n") log_file.write(f"标准输出:\n{stdout}\n") if stderr: log_file.write(f"标准错误:\n{stderr}\n") log_file.write("-" * 30 + "\n") if returncode == 0: if 'Success' in stdout: print(f"[设备 {device_id}] ({i}/{total}) {apk_name}: 安装成功") success += 1 else: # 有时adb返回0但输出里没有Success,可能是其他信息 error_msg = f"安装命令返回0,但未检测到成功信息。输出: {stdout[:200]}" print(f"[设备 {device_id}] ({i}/{total}) {apk_name}: 疑似失败 - {error_msg}") errors.append(f"{apk_name}: {error_msg}") else: error_msg = f"安装失败。返回码: {returncode}, 错误: {stderr[:200] if stderr else stdout[:200]}" print(f"[设备 {device_id}] ({i}/{total}) {apk_name}: 失败 - {error_msg}") errors.append(f"{apk_name}: {error_msg}") except subprocess.TimeoutExpired: error_msg = "安装命令执行超时(超过120秒)" print(f"[设备 {device_id}] ({i}/{total}) {apk_name}: {error_msg}") log_file.write(f"[{i}/{total}] {apk_name}: {error_msg}\n") errors.append(f"{apk_name}: {error_msg}") except Exception as e: error_msg = f"执行命令时发生未知异常: {e}" print(f"[设备 {device_id}] ({i}/{total}) {apk_name}: {error_msg}") log_file.write(f"[{i}/{total}] {apk_name}: {error_msg}\n") errors.append(f"{apk_name}: {error_msg}") log_file.write(f"\n=== 安装结束 ===\n成功: {success}/{total}\n") if errors: log_file.write("错误列表:\n") for err in errors: log_file.write(f" - {err}\n") return device_id, success, total, errors def main(): parser = argparse.ArgumentParser(description='批量安装APK到多个已连接的Android设备') parser.add_argument('apk_files', nargs='+', help='一个或多个APK文件的路径') parser.add_argument('-r', '--replace', action='store_true', help='替换已存在的应用(相当于adb install -r)') parser.add_argument('-g', '--grant-permissions', action='store_true', help='授予所有运行时权限(相当于adb install -g)') parser.add_argument('-t', '--test-only', action='store_true', help='允许安装测试APK(相当于adb install -t)') parser.add_argument('-d', '--downgrade', action='store_true', help='允许版本降级安装(相当于adb install -d)') parser.add_argument('-j', '--jobs', type=int, default=5, help='最大并行设备数(默认: 5)') parser.add_argument('-l', '--log-dir', default='./install_logs', help='日志文件保存目录(默认: ./install_logs)') args = parser.parse_args() # 检查APK文件是否存在 valid_apks = [] for apk in args.apk_files: if os.path.exists(apk): valid_apks.append(apk) else: print(f"[警告] 跳过不存在的文件: {apk}") if not valid_apks: print("[错误] 未提供任何有效的APK文件路径。") sys.exit(1) # 构建adb install参数列表 install_args = [] if args.replace: install_args.append('-r') if args.grant_permissions: install_args.append('-g') if args.test_only: install_args.append('-t') if args.downgrade: install_args.append('-d') # 可以添加更多参数映射... # 获取设备列表 print("正在扫描已连接的ADB设备...") devices = get_connected_devices() if not devices: print("未找到任何状态为‘device’的已连接ADB设备。请检查:") print(" 1. 设备是否通过USB连接或网络连接?") print(" 2. 设备上是否已开启‘USB调试’?") print(" 3. 对于真机,连接后是否在设备上点击了‘允许USB调试’?") sys.exit(1) print(f"找到 {len(devices)} 台可用设备: {devices}") # 准备日志目录 os.makedirs(args.log_dir, exist_ok=True) timestamp = time.strftime('%Y%m%d_%H%M%S') session_log_dir = os.path.join(args.log_dir, f'session_{timestamp}') os.makedirs(session_log_dir, exist_ok=True) print(f"详细日志将保存至: {session_log_dir}\n") # 使用线程池并发执行设备安装任务 results = [] start_time = time.time() with ThreadPoolExecutor(max_workers=args.jobs) as executor: # 提交所有设备任务到线程池 future_to_device = { executor.submit(install_apk_to_device, device, valid_apks, install_args, os.path.join(session_log_dir, device.replace(':', '_').replace('.', '_'))): device for device in devices } # 处理完成的任务 for future in as_completed(future_to_device): device_id = future_to_device[future] try: result = future.result(timeout=5) # 获取结果,设置短超时防止阻塞 results.append(result) dev_id, success, total, errors = result status = "成功" if success == total else "部分失败" if success > 0 else "完全失败" print(f"[汇总] 设备 {dev_id}: {status} ({success}/{total})") except Exception as exc: print(f"[汇总] 设备 {device_id} 的任务生成异常: {exc}") results.append((device_id, 0, len(valid_apks), [f"任务执行异常: {exc}"])) total_time = time.time() - start_time # 打印最终汇总报告 print("\n" + "="*60) print("批量安装任务完成!") print(f"总耗时: {total_time:.2f} 秒") print(f"设备总数: {len(devices)}") print(f"APK总数: {len(valid_apks)}") print("-"*60) total_apk_attempts = len(devices) * len(valid_apks) total_success = sum(r[1] for r in results) total_failed = total_apk_attempts - total_success print(f"总安装尝试次数: {total_apk_attempts}") print(f"成功次数: {total_success}") print(f"失败次数: {total_failed}") print(f"成功率: {total_success/total_apk_attempts*100:.1f}%" if total_apk_attempts > 0 else "N/A") print("="*60) print(f"每台设备的详细安装日志请查看: {session_log_dir}") if __name__ == '__main__': main()4.3 脚本使用示例
假设你有两个APK文件:app-release.apk和plugin-release.apk,并且连接了3台设备。
基本用法(替换安装):
python batch_install.py app-release.apk plugin-release.apk -r这会将两个APK以替换模式(-r)安装到所有已连接的设备上。
授予权限并限制并发数为3:
python batch_install.py app-release.apk -r -g -j 3这会将app-release.apk以替换模式安装并授予运行时权限,同时最多只允许3台设备并行安装。
指定日志目录:
python batch_install.py app-release.apk -r -l ./my_logs日志将保存在当前目录下的my_logs文件夹中,并按会话时间创建子目录。
5. Shell脚本快速实现方案
如果你需要一个更轻量、无需Python环境的解决方案,可以使用Shell脚本。以下是一个Bash脚本示例,实现了基础的多设备并行安装。
#!/bin/bash # batch_install.sh - 使用Shell并行安装APK到多个设备 # 配置部分 APK_FILES=("$@") # 通过命令行参数传入APK路径 ADB_INSTALL_FLAGS="-r" # 默认使用替换安装 MAX_PARALLEL=5 # 最大并行进程数 # 检查参数 if [ ${#APK_FILES[@]} -eq 0 ]; then echo "用法: $0 <apk文件1> [apk文件2 ...]" exit 1 fi for apk in "${APK_FILES[@]}"; do if [ ! -f "$apk" ]; then echo "错误: 文件 '$apk' 不存在。" exit 1 fi done # 获取设备列表 DEVICES=$(adb devices | grep -v "List of devices attached" | grep "device$" | awk '{print $1}') if [ -z "$DEVICES" ]; then echo "未找到已连接的设备。" exit 1 fi echo "找到设备: $DEVICES" echo "将安装APK: ${APK_FILES[*]}" echo "开始并行安装 (最大并行数: $MAX_PARALLEL)..." # 创建一个临时目录存放设备日志 LOG_DIR="./install_logs_$(date +%Y%m%d_%H%M%S)" mkdir -p "$LOG_DIR" # 用于控制并发的计数器 CURRENT_JOBS=0 # 为每个设备启动安装进程 for DEVICE in $DEVICES; do ( # 为每个设备创建独立日志文件 LOG_FILE="$LOG_DIR/device_${DEVICE//:/_}.log" echo "=== 开始为设备 $DEVICE 安装 ===" > "$LOG_FILE" date >> "$LOG_FILE" SUCCESS_COUNT=0 for APK in "${APK_FILES[@]}"; do APK_NAME=$(basename "$APK") echo "[设备 $DEVICE] 正在安装: $APK_NAME" | tee -a "$LOG_FILE" # 执行安装命令,并将输出重定向到日志文件 if adb -s "$DEVICE" install $ADB_INSTALL_FLAGS "$APK" >> "$LOG_FILE" 2>&1; then echo "[设备 $DEVICE] $APK_NAME: 成功" | tee -a "$LOG_FILE" ((SUCCESS_COUNT++)) else echo "[设备 $DEVICE] $APK_NAME: 失败" | tee -a "$LOG_FILE" fi echo "---" >> "$LOG_FILE" done echo "=== 设备 $DEVICE 安装结束 ===" >> "$LOG_FILE" echo "成功: $SUCCESS_COUNT/${#APK_FILES[@]}" >> "$LOG_FILE" echo "[设备 $DEVICE] 任务完成。日志: $LOG_FILE" ) & # 将整个子shell放入后台执行,实现并行 # 并发控制 ((CURRENT_JOBS++)) if [ $CURRENT_JOBS -ge $MAX_PARALLEL ]; then wait -n # 等待任意一个后台任务完成 ((CURRENT_JOBS--)) fi done # 等待所有剩余的后台任务完成 wait echo "所有设备安装任务已提交完成。" echo "详细日志请查看目录: $LOG_DIR"使用方式:
# 赋予执行权限 chmod +x batch_install.sh # 运行脚本 ./batch_install.sh path/to/app1.apk path/to/app2.apkShell脚本的局限性:这个脚本的错误处理比Python版本简单,它主要依赖命令的返回码。并发控制使用了基础的作业控制(
&和wait -n),在大多数现代Bash中可用。日志是每个设备一个文件,但所有输出(包括你屏幕上看到的)也会混在一起。对于更复杂的需求,还是推荐Python方案。
6. 常见问题与排查技巧实录
在实际使用中,你肯定会遇到各种问题。下面是我在多年实践中总结的一些典型问题及其解决方法。
6.1 设备识别与连接问题
问题1:脚本检测不到设备,但adb devices手动执行能看到。
- 可能原因1:ADB服务未启动或异常。
- 排查:执行
adb kill-server && adb start-server重启ADB服务。 - 技巧:可以在脚本开头主动执行一次
adb start-server来确保服务已启动。
- 排查:执行
- 可能原因2:设备状态为
unauthorized。- 排查:脚本已过滤此状态。你需要检查设备屏幕,是否弹出了“允许USB调试”的提示框,并点击“允许”。
- 技巧:对于测试机群,可以在首次连接时统一点击授权,或者使用一些自动化工具预先完成授权。
- 可能原因3:设备通过网络连接,但端口被占用或IP变化。
- 排查:使用
adb connect <device_ip:port>重新连接。如果IP经常变,可以考虑编写脚本动态发现同一网段内的设备。
- 排查:使用
问题2:安装过程中设备突然离线(offline)。
- 可能原因:USB线接触不良、设备休眠、ADB连接不稳定。
- 解决:脚本中已经通过命令超时机制处理了这种情况。物理上,尽量使用质量好的USB线和Hub,并在开发者选项中关闭“USB调试安全设置”(如果允许的话),并设置“保持唤醒状态”。
6.2 安装命令执行失败
问题3:安装失败,提示INSTALL_FAILED_UPDATE_INCOMPATIBLE或类似签名冲突错误。
- 可能原因:尝试安装的APK与设备上已安装的应用签名不一致,且未使用
-r参数,或者即使用了-r也无法覆盖(如系统应用)。 - 解决:
- 首先确保脚本中使用了
-r参数。 - 如果仍失败,需要先卸载旧应用:
adb -s device_id uninstall your.package.name。可以将卸载逻辑集成到脚本中,作为安装前的一个可选步骤。 - 对于系统应用,可能需要
adb root权限,这在普通设备上通常无法获取。
- 首先确保脚本中使用了
问题4:安装失败,提示INSTALL_FAILED_INSUFFICIENT_STORAGE。
- 可能原因:设备存储空间不足。
- 解决:脚本应捕获此错误并明确提示。手动处理需要清理设备空间。可以在脚本中增加一个安装前的存储空间检查步骤,但这会增加复杂度。更实用的做法是让脚本清晰报错,由人工介入清理。
问题5:安装失败,提示adb: error: cannot stat 'apk_path': No such file or directory。
- 可能原因:提供给
adb install的APK文件路径不正确,或者路径中包含特殊字符(如空格)未正确处理。 - 解决:确保脚本中文件路径的传递是正确的。在Python中使用列表传递参数(
subprocess.run([..., apk_path]))可以避免Shell转义问题。在Shell脚本中,确保变量用双引号包裹:adb install "$APK_PATH"。
6.3 性能与稳定性问题
问题6:并行安装时,大量日志输出到控制台,造成混乱。
- 解决:这正是我们将每个设备的日志重定向到独立文件的原因。在Python脚本中,每个设备的安装进程将其所有输出写入专属日志文件。控制台只显示进度摘要和关键错误,保持整洁。
问题7:并行数设置过高,导致脚本卡顿或ADB无响应。
- 现象:设置
-j 50后,脚本运行缓慢,甚至有些设备任务超时。 - 解决:根据你的电脑性能和设备数量,合理设置并发数。对于普通开发机,建议并发数在5-10之间。我的脚本默认是5,这是一个比较保守稳定的值。你可以通过
-j参数调整。
问题8:如何知道每个APK分别安装成功了没有?
- 解决:查看每台设备的独立日志文件。脚本会为每台设备在日志目录下生成一个以设备ID命名的文件(如
install_log_emulator_5554.txt),里面详细记录了该设备上每个APK的安装命令和完整输出。
6.4 脚本扩展与优化建议
- 安装前卸载:增加一个
-u, --uninstall-first参数,在安装前先执行adb uninstall <package_name>。这需要你能够从APK中解析出包名(可以使用aapt工具或Python的androguard库)。 - 安装后启动:增加一个
-s, --start-after-install参数,安装成功后自动启动应用的主Activity。同样需要解析APK获取主Activity信息。 - 设备分组:有时你可能只想将APK安装到特定型号或特定系统版本的设备上。可以扩展脚本,通过
adb -s device_id shell getprop获取设备属性(如ro.product.model,ro.build.version.release),然后根据属性过滤设备。 - 集成到CI/CD:将Python脚本作为Jenkins Pipeline、GitLab CI或GitHub Actions的一个步骤。关键是要确保CI服务器上安装了正确的ADB驱动并能连接到设备(可能是通过USB Hub或网络)。
- 图形界面(GUI):对于非技术人员,可以使用
tkinter或PyQt为脚本包装一个简单的图形界面,方便选择APK文件和设备。
这个脚本提供了一个强大而灵活的基础。你可以根据自己的具体工作流,对它进行裁剪和增强。核心思想始终是:将重复、易错的手动操作,转化为可重复、可审计的自动化流程。
