运行多部手机
给手机命名,用 Python、Node.js 或 shell 并行运行它们,更换移动 IP 地址,并让一整架手机连续多天保持连接。
本页为译文。如与英文版不一致,以英文版为准。 English
一个 droidline serve 能驱动的手机数量,只受你的电脑和网络承载能力的限制。每部手机都有自己的命令队列:发给同一部手机的命令按顺序执行,不同手机之间同时执行。
给每部手机起名字
每部手机都有一个随机 ID,例如 k7d2q9xa。在配对时给它起一个好记的名字,或者之后再重命名:
droidline pair 482913 --name shelf-01
droidline rename k7d2q9xa shelf-02
droidline devices
名称可以使用字母、数字、点、连字符和下划线。在手机上贴一张写着同样名字的标签,能省去很多猜测。
并行运行
向服务器询问哪些手机在线,然后为每部手机分配各自的线程、任务或后台作业:
from concurrent.futures import ThreadPoolExecutor
from droidline import Droidline
dl = Droidline()
def morning(name: str) -> str:
d = dl.device(name)
d.wake()
d.launch("com.example.app")
d.touch("text", "Check in", timeout=20)
return d.get_text("id", "status")
names = [p["name"] for p in dl.devices() if p["online"]]
with ThreadPoolExecutor(len(names) or 1) as pool:
for name, status in zip(names, pool.map(morning, names)):
print(name, status)import { Droidline } from "droidline";
const dl = new Droidline();
const phones = (await dl.devices()).filter((p) => p.online);
const results = await Promise.allSettled(phones.map(async (p) => {
const d = dl.device(p.name);
await d.wake();
await d.launch("com.example.app");
await d.touch("text", "Check in", { timeout: 20 });
return d.getText("id", "status");
}));
results.forEach((r, i) => console.log(phones[i].name, r.status === "fulfilled" ? r.value : r.reason.message));for name in $(droidline --json devices | jq -r '.value[] | select(.online) | .name'); do
(
droidline -d "$name" wake
droidline -d "$name" launch com.example.app
droidline -d "$name" touch text "Check in" --timeout 20
echo "$name: $(droidline -d "$name" get_text id status)"
) &
done
wait一部手机失败不会让其他手机停下:在 Python 中,异常会在你取到那部手机的结果时从 pool.map 中抛出;在 Node.js 中,allSettled 会记录它;在 shell 中,每部手机都在各自的子 shell 中运行。
让每部手机使用各自的网络
借助按应用代理,每部手机可以让某个应用通过不同的上游联网。先保存一次上游,然后按名称分配:
droidline proxy add kr1 socks5://user:pass@203.0.113.10:1080
droidline proxy add kr2 socks5://user:pass@203.0.113.11:1080
plan = {"shelf-01": "@kr1", "shelf-02": "@kr2"}
for name, upstream in plan.items():
dl.device(name).proxy(upstream, app="com.android.chrome")const plan = { "shelf-01": "@kr1", "shelf-02": "@kr2" };
for (const [name, upstream] of Object.entries(plan)) {
await dl.device(name).proxy(upstream, { app: "com.android.chrome" });
}droidline -d shelf-01 proxy @kr1 --app com.android.chrome
droidline -d shelf-02 proxy @kr2 --app com.android.chrome新的移动 IP 地址
使用移动数据时,开关一次飞行模式通常能从运营商处获得新地址。手机离线期间无法接收命令,所以要把开和关两个操作放进同一个 batch 发送,由手机自行完成:
d.batch([("airplane", True), ("sleep", 3000), ("airplane", False)], wait=True)await d.batch([["airplane", true], ["sleep", 3000], ["airplane", false]], { wait: true });droidline -d shelf-01 batch '[["airplane",true],["sleep",3000],["airplane",false]]' --wait不传 wait 时,调用会立即返回 {"accepted": true},结果稍后作为 result 事件到达。
屏幕熄灭时
手机屏幕熄灭时,点击和滑动会失败。每项任务开始前调用 wake();它会点亮屏幕,并解除没有 PIN 的锁屏。对于无人值守的手机,请在手机设置中移除 PIN、图案或密码锁,因为 Droidline 无法解锁它们。
连续多天保持连接
- 把 Droidline 的电池优化设为不受限制,并在手机品牌额外添加的后台限制中允许它(故障排查列出了常见的位置)。
- 配对之后,手机开机时应用会自动启动。
- 重新连接后,手机会重发电脑漏收的回复,重复的命令不会执行两次。参见概念。
droidline devices显示每部手机的线路和缺少的权限。devices()中的last_seen告诉你离线手机最后一次连接的时间。- 让手机一直充电。USB 充电器就够了,不需要数据连接。
监控整架手机
订阅 device 事件,就能在手机掉线或重新上线的那一刻得知:
from droidline import Droidline
dl = Droidline()
dl.subscribe(["device"])
dl.on("device", lambda e: print(e["name"], e["state"], e.get("route")))
input("Watching. Press Enter to stop.\n")droidline --json devices | jq -r '.value[] | "\(.name)\t\(if .online then "online" else "offline" end)\t\(.route)"'更新
应用每天检查一次 GitHub Releases,有新版本时显示一条通知。把更新直接安装在旧版应用上,配对会保留。