25、ADB 与自动化框架:ADB + Python(subprocess 调用)、ADB + Appium 简介、实战:编写一个自动安装脚本

说实话,到了这一章,才算真正进入「自动化」的大门。

前面我们一直在手动敲命令,对吧?

敲一次两次还行,要是让你每天重复 50 次同样的 adb install,你肯定想骂人。

所以,我们需要把 ADB 和脚本结合起来。

我个人习惯用 Python,因为它写起来快,生态也好。你想想看,一个 subprocess 模块就能搞定所有 shell 调用,再配合 Appium 做 UI 自动化,基本上手机上的重复劳动都能交给机器了。

25.1 ADB + Python:用 subprocess 调用 ADB 命令

Python 调用外部命令,最常用的就是 subprocess 模块。

我刚开始用的时候也踩过坑——直接用 os.system(),结果拿不到输出,调试起来特别痛苦。后来老老实实换成了 subprocess,真香。

先看一个最简单的例子:

import subprocess

# 执行 adb devices
result = subprocess.run(
    ['adb', 'devices'],
    capture_output=True,
    text=True,
    encoding='utf-8'
)
print(result.stdout)

这里要注意几个点:

  • capture_output=True 才能拿到标准输出和错误输出
  • text=True 让输出以字符串形式返回,而不是字节
  • encoding='utf-8' 避免中文乱码——嗯,这个坑我踩过

如果你需要实时获取输出(比如看 logcat),可以用 Popen

import subprocess

proc = subprocess.Popen(
    ['adb', 'logcat', '-c'],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)
# 清空日志后,再启动实时读取
proc = subprocess.Popen(
    ['adb', 'logcat'],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    text=True
)
for line in proc.stdout:
    print(line.strip())

我曾经在做一个自动化测试框架时,需要同时监控多台设备的日志。用 Popen 配合线程,每台设备开一个子进程,完美解决。

小技巧: 如果你经常切换设备,建议把 adb -s 设备序列号 封装成一个函数,避免每次拼字符串。

25.2 ADB + Appium 简介

Appium 是什么?

说白了,它是一个跨平台的 UI 自动化框架。底层还是调用的 ADB(Android 端),但它帮你封装了元素定位、手势操作、等待机制这些麻烦事。

我个人的理解:

  • ADB 是「手」,能执行命令、安装应用、点击屏幕
  • Appium 是「大脑」,能理解 UI 结构、找到按钮、判断页面状态

安装 Appium 很简单:

npm install -g appium
appium

然后写一个 Python 客户端脚本:

from appium import webdriver

desired_caps = {
    'platformName': 'Android',
    'deviceName': 'emulator-5554',
    'appPackage': 'com.android.settings',
    'appActivity': '.Settings',
    'noReset': True
}

driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

# 点击 "关于手机"
driver.find_element_by_xpath("//*[@text='关于手机']").click()

# 获取 Android 版本
version = driver.find_element_by_xpath("//*[@text='Android 版本']/following-sibling::*").text
print(f'Android 版本: {version}')

driver.quit()

你看,代码比纯 ADB 直观多了吧?

但要注意,Appium 启动比较慢,每次都要启动一个 Appium Server。我建议在调试阶段先用 ADB 验证可行性,再用 Appium 做精细化操作。

避坑指南: 我曾经在 CI 环境里直接用 Appium,结果发现它依赖 Node.js 和一大堆 npm 包,安装时间比测试本身还长。后来改成「ADB 做安装和基础操作 + Appium 做 UI 验证」的组合方案,效率高了很多。

25.3 实战:编写一个自动安装脚本

好了,理论说完了,咱们直接写一个能用的脚本。

这个脚本的功能:

  • 自动检测连接的设备
  • 如果有多台设备,让用户选择
  • 安装指定的 APK 文件
  • 如果已安装,先卸载再安装
  • 显示安装进度和结果
import subprocess
import sys
import os

def get_devices():
    """获取已连接的设备列表"""
    result = subprocess.run(
        ['adb', 'devices'],
        capture_output=True,
        text=True,
        encoding='utf-8'
    )
    lines = result.stdout.strip().split('\n')[1:]
    devices = []
    for line in lines:
        if line.strip() and 'device' in line and 'offline' not in line:
            serial = line.split('\t')[0]
            devices.append(serial)
    return devices

def select_device(devices):
    """让用户选择设备"""
    if not devices:
        print('没有检测到设备,请检查连接。')
        sys.exit(1)
    if len(devices) == 1:
        return devices[0]
    print('检测到多台设备:')
    for i, dev in enumerate(devices):
        print(f'  [{i}] {dev}')
    choice = input('请选择设备编号(默认0):').strip()
    if not choice:
        choice = '0'
    return devices[int(choice)]

def install_apk(serial, apk_path):
    """安装 APK,如果已存在则先卸载"""
    # 获取包名
    result = subprocess.run(
        ['aapt', 'dump', 'badging', apk_path],
        capture_output=True,
        text=True,
        encoding='utf-8'
    )
    # 简单解析包名
    for line in result.stdout.split('\n'):
        if line.startswith('package:'):
            package_name = line.split("name='")[1].split("'")[0]
            break
    else:
        print('无法解析 APK 包名,请检查 aapt 是否安装。')
        sys.exit(1)

    # 检查是否已安装
    check = subprocess.run(
        ['adb', '-s', serial, 'shell', 'pm', 'list', 'packages', package_name],
        capture_output=True,
        text=True,
        encoding='utf-8'
    )
    if package_name in check.stdout:
        print(f'设备 {serial} 上已安装 {package_name},正在卸载...')
        subprocess.run(
            ['adb', '-s', serial, 'uninstall', package_name],
            capture_output=True
        )
        print('卸载完成。')

    # 安装
    print(f'正在安装 {apk_path} 到设备 {serial}...')
    result = subprocess.run(
        ['adb', '-s', serial, 'install', '-r', apk_path],
        capture_output=True,
        text=True,
        encoding='utf-8'
    )
    if 'Success' in result.stdout:
        print('安装成功!')
    else:
        print('安装失败:')
        print(result.stdout)
        print(result.stderr)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('用法:python install_apk.py <apk_path>')
        sys.exit(1)

    apk_path = sys.argv[1]
    if not os.path.exists(apk_path):
        print(f'文件不存在:{apk_path}')
        sys.exit(1)

    devices = get_devices()
    serial = select_device(devices)
    install_apk(serial, apk_path)

这个脚本我实际在项目里用过,当时需要给 20 台测试机批量安装同一个应用。手动装一台要 2 分钟,用这个脚本,加上一个循环,5 分钟全部搞定。

你可以把它扩展一下:

  • 加上 --multi 参数,一键安装到所有设备
  • 加上 --log 参数,把安装结果写入日志文件
  • 加上重试机制,安装失败自动重试 3 次
核心思路: ADB 是基础能力,Python 是粘合剂。把重复的 ADB 命令封装成函数,再组合成自动化流程,这就是「ADB 自动化」的本质。

25.4 本章知识体系

下面这张图,帮你理清 ADB、Python、Appium 三者的关系:

ADB 自动化框架知识体系 ADB 命令 Python subprocess 调用 ADB 命令 Appium UI 自动化框架 实战:自动安装脚本 ADB 提供底层能力,Python 做逻辑编排,Appium 做 UI 精细化操作 三者结合,可以覆盖 90% 以上的 Android 自动化场景

说白了,这一章的核心就三句话:

  1. Python + subprocess 是调用 ADB 最灵活的方式
  2. Appium 适合做 UI 层面的自动化,但启动慢、依赖多
  3. 实战脚本 把前面学的 ADB 命令串起来,解决真实问题

我建议你先把那个安装脚本跑通,然后试着改一改——比如加上批量安装、日志记录、失败重试。改着改着,你就发现 ADB 自动化其实没那么神秘。

个人经验: 别一上来就想搞个大而全的框架。先写一个能用的脚本,解决眼前的问题。等用顺手了,再慢慢抽象、封装。我见过太多人花两周搭框架,结果一周后项目需求变了,框架白搭。

公众号:蓝海资料掘金营,微信deep3321