Skip to main content

androtools

PyPI PyPI - Status PyPI - Python Version PyPI - Downloads PyPI - License

对 Android SDK、第三方模拟器命令行工具、以及通过 adb 连接的 Android 设备的封装。

核心价值是把「判断状态、等它就绪、卡死了怎么办」这段最容易出错、也最难手工验证的流程收进一处。 支持真机、雷电模拟器、MuMu 模拟器。

安装

pip install androtools

快速开始

真机

真机不需要任何子类 —— 它就是一个 adb 可寻址的目标。

from androtools.android_sdk.platform_tools import ADB
from androtools.core import Device, DeviceInfo

adb = ADB()
serial, status, _ = adb.get_devices()[0]

device = Device(DeviceInfo(name="我的手机", serial=serial, adb_path=adb.bin_path))
print(device.get_status())        # DeviceStatus.BOOT_COMPLETED
print(device.android_version)     # 问设备本身要,不是查本地表
print(device.read_prop("ro.product.model"))

device.shell.list_packages()      # 所有应用
device.shell.list_packages(flag=0)  # 只看第三方应用
device.shell.pull("/sdcard/DCIM", r"D:\backup")

同时插着多台设备时,serial 必须指定 —— 否则 AndroidShell 会抛 AmbiguousDeviceError,而不是让 adb 乱挑。

模拟器

模拟器比真机多两样东西:厂商 Console,以及「启动 / 关闭」。

from androtools.core import EmulatorInfo, LDPlayer

player = LDPlayer(
    EmulatorInfo(
        name="雷电",
        serial=None,                    # 库会按 MAC 指纹认出来,见下
        adb_path=r"D:\ProgramFiles\LDPlayer9.0.79.2\adb.exe",
        index="0",                      # 厂商 Console 里的编号
        console_path=r"D:\ProgramFiles\LDPlayer9.0.79.2\ldconsole.exe",
    )
)

player.launch()                # 启动模拟器并等它就绪
print(player.get_status())
player.shell.tap(100, 200)
player.run_app("com.android.settings")
player.close()

LDPlayer 会自己按 console_path 找 ldconsole.exe,MuMu 同理:

from androtools.core import MumuPlayer

mumu = MumuPlayer(EmulatorInfo(
    name="MuMu",
    serial=None,
    adb_path=r"D:\Program Files\Netease\MuMu\nx_main\adb.exe",
    index="1",
    console_path=r"D:\Program Files\Netease\MuMu\nx_main\MuMuManager.exe",
))
mumu.launch()   # 启动 → 反查 serial → adb connect

MuMu 启动后 adb 端口会变,launch() 会从 Console 查出新的 serial 并 adb connect。 adb connect 不只在 launch() 里做 —— 从 MuMu 启动器外部启动、没经过 launch() 的实例,127.0.0.1:<adb_port> 默认并不在 adb devices 里,库会在用之前补上连接。

多开模拟器时 serial 会被认错

雷电和 MuMu 的 serial 都落在 emulator-5554 这一段号段里,两家会撞。实测两台雷电

  • 两台 MuMu 同时开着时:
serial 实际是谁
emulator-5554 雷电 index 0
emulator-5556 MuMu index 1 —— 正好是雷电 index 1 该去的位置
127.0.0.1:16384 / 127.0.0.1:16416 MuMu(tcp 地址,MuMu 的权威身份)
—— 雷电 index 1 在 adb 里根本不出现

推出来的 serial 不会「连不上」,它会合法地连到别人的模拟器上 —— 连雷电官方的 ldconsole adb --index 1 都会安静地返回 MuMu 那台设备的属性(实测返回 SM-A5560)。

把 MuMu 关掉再跑同样的两台雷电,emulator-5556 就变回雷电 index 1(V1938T)。同一段 代码、同一个 serial,换个环境就换了个主人 —— 这就是为什么不能按 index 算端口。

所以 LDPlayer 在 serial=None 时不按 index 算端口,而是用每实例唯一的 MAC 指纹 (雷电实例配置里的 propertySettings.macAddress,guest 的 wlan0 上是同一个值)在 adb 上认自己的那一台,核不上就报错,绝不猜。多开雷电和 MuMu 混用时,如果某个实例的 serial 被对方占了,你会看到明确的报错而不是错误设备的数据。

想手动指定也完全可以,两个厂商都一样:

EmulatorInfo(..., serial="emulator-5554", ...)   # 显式指定,跳过反查

反查:adb 上这个 serial 到底是谁的

撞号之后最需要问的一句话。identify() 拿指纹核对,不问厂商工具 —— 因为实测 ldconsole adb --index 1 在那个环境下会返回 MuMu 那台设备且不报错。

from androtools.android_sdk.platform_tools import ADB
from androtools.core import LDConsole, MumuConsole, identify

adb = ADB(r"D:\ProgramFiles\LDPlayer9.0.79.2\adb.exe")
consoles = [
    ("雷电", LDConsole(r"D:\ProgramFiles\LDPlayer9.0.79.2\ldconsole.exe")),
    ("MuMu",  MumuConsole(r"D:\Program Files\Netease\MuMu\nx_main\MuMuManager.exe")),
]

identify(adb, consoles, "emulator-5556")
# InstanceIdentity(vendor='雷电', index='1', serial='emulator-5556')

identify(adb, consoles, "A87V026107005402")   # 真机
# None

None 是正常结果,不是错误 —— 真机、以及指纹读不出来的厂商(目前是 MuMu, 原因见 MumuConsole.fingerprint 的文档)都属于这一类。

抓包

按应用抓 —— 用应用的 user id 做 iptables 标记。

from androtools.utils import Tcpdump

tcpdump = Tcpdump(device.shell, "com.example.app")
tcpdump.start_capture()
...
tcpdump.stop_capture()
tcpdump.pull_pcap_file(r"D:\capture")

设计

Device                 真机。serial、状态判定、超时探测、应用操作
└─ EmulatorSession     加上厂商 Console 和 launch / close / ensure_ready / recover
   └─ ConsoleSession   雷电、MuMu

AndroidShell           adb 命令字典(shallow:省的是打字,不是藏复杂度)
EmulatorConsole        厂商 CLI 的方言止步于此(probe_state / getprop / run_app / …)

两个 seam 都可以替换,所以生命周期逻辑不需要真机就能测:

from androtools.testing import FakeADB

adb = FakeADB(responses={("get-state",): CmdResult("device", "")}, devices=[...])
device = Device(info, adb=adb, sleeper=lambda _s: None)

FakeADB 未声明的命令会直接抛错,所以「代码多调了一次 adb」会变成红灯而不是悄悄通过。

开发

make sync            # uv sync
make test            # 单元测试(默认排除 integration)
make typecheck       # mypy
make test-integration   # 需要真实 Android SDK 和设备

CI 在 .github/workflows/ci.yml,跑 Python 3.10(声明的最低版本)和 3.13。

术语

领域词汇见 CONTEXT.md —— 尤其「Device 与 Emulator 差在哪」「Serial 为什么是运行期才确定的」这两条。

破坏性变更

1.x → 2.0 的迁移说明见 CHANGELOG.md。

Metadata

Release files for androtools 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for androtools 2.1.0
File Size Uploaded Provenance
androtools-2.1.0.tar.gz 32.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for androtools 2.1.0
File Interpreter ABI Platform
androtools-2.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 75.2 kB

Release files / androtools-2.1.0.tar.gz

Download URL androtools-2.1.0.tar.gz
Size 32.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6713591bf8d07586f13b55b4d904e92b0e482f7883682cdb18f6744281aa9e8d
BLAKE2b-256 checksum
How to use checksums
6ef48f918f67e2b445d5713c9aabbcb260cd42cc624ef8bef61406bae148cfaf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.12

Release files / androtools-2.1.0-py3-none-any.whl

Download URL androtools-2.1.0-py3-none-any.whl
Size 42.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fbda00092341793223f5141f6c8909c4d90bc389665ba7bea72cbca6126c8be3
BLAKE2b-256 checksum
How to use checksums
0641711a32a7fd6544d2019f409cb2903493d73c82879e682249ad824babb0c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.12

Security for androtools 2.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page