苹果群控 USB HID 不安装 APP 如何截图?
不装 App 也能截图,用 image.captureFullScreenNoAuto(),iOS 17 以上先调一次 image.resetCaptureScreenNoAutoEnv()。
代码例子:
截完的图可以直接交给 OCR:找出「搜索」两个字的位置,点进去,再往下滑视频。从截图、识别、点击到滑动,都在这一段里:
javascript
let tocr = null;
function initOcr() {
ocr.releaseAll();
tocr = ocr.newOcr();
return tocr.initOcr({
"type": "paddleOcrOnnxV6",
"numThread": 2,
"single": 0,
"matMode": 1,
"padding": 32,
"maxSideLen": 640,
"doAngleFlag": 0,
"mostAngleFlag": 0
});
}
function findText(img, keyword) {
let result = tocr.ocrImage(img, 30 * 1000, {"padding": 32, "numThread": 1});
if (!result) return null;
for (let i = 0; i < result.length; i++) {
let item = result[i];
if (item.label && item.label.indexOf(keyword) >= 0) {
return {
x: parseInt(item.x + item.width / 2),
y: parseInt(item.y + item.height / 2)
};
}
}
return null;
}
function main() {
usbHidEvent.sessionStart();
usbHidEvent.setScreenSize(1170, 2532); // 换成你的机型分辨率
image.resetCaptureScreenNoAutoEnv();
sleep(1000);
image.useOpencvMat(1);
initOcr();
let img = image.captureFullScreenNoAuto();
if (img == null) {
loge("截图失败");
return;
}
let pos = findText(img, "搜索");
image.recycle(img);
if (pos) {
usbHidEvent.clickPoint(pos.x, pos.y);
logd("点了搜索 " + pos.x + "," + pos.y);
sleep(1500);
} else {
loge("没识别到「搜索」");
}
// 往下滑,看下一个视频
for (let i = 0; i < 5; i++) {
usbHidEvent.swipeToPoint(585, 2000, 585, 600, 600);
sleep(2000);
}
usbHidEvent.sessionStop();
tocr.releaseAll();
}
main();
USB HID 不装 App 也能截图:captureFullScreenNoAuto 截完交给 OCR 识别文字,点识别结果,再滑视频页。
这段代码在做什么
usbHidEvent.sessionStart() 开 HID 会话,setScreenSize 设屏幕尺寸。点击和滑动的坐标都以这个尺寸为基准,设错了就会点偏。
image.resetCaptureScreenNoAutoEnv() 是 iOS 17 以上必须做的,先启动截图隧道;低版本调用不报错但无效。调完等一秒再截。
image.useOpencvMat(1) 把图片切到 mat 存储,截图、OCR 都吃这个格式,内存更省。
image.captureFullScreenNoAuto() 截一张无自动化图。截不到返回 null,先别急着写业务。
OCR 初始化用 paddleOcrOnnxV6,配一组偏快的参数:maxSideLen: 640、doAngleFlag: 0、mostAngleFlag: 0。日常正立的界面够用,遇到小字再调回 960。
tocr.ocrImage(img, 30 * 1000, {...}) 返回一个数组,每条带 label、confidence、x、y、width、height。要点的位置取文字框中心:
javascript
x = item.x + item.width / 2;
y = item.y + item.height / 2;
找到含「搜索」的文章坐标,用 usbHidEvent.clickPoint(x, y) 点过去。clickPoint 返回 null 或空字符串算成功,返回别的字符串是错误信息。
滑视频用 usbHidEvent.swipeToPoint(585, 2000, 585, 600, 600):起点在屏幕下方,终点在上方,从下往上滑,等于翻到下一个。每次滑完等两秒,让视频加载。
最后 sessionStop 关会话,tocr.releaseAll() 释放 OCR 资源,image.recycle(img) 回收截图对象。
USBHID 场景为什么用 NoAuto 截图
USB HID 和代理自动化是两条路,同一台设备只能开一条。USBHID 的特点是手机上不装 App、不装描述文件,那就不能用需要自动化服务的截图函数。
几种截图方式的区别:
| 方式 | 依赖 | 速度 | 适合 |
|---|---|---|---|
captureFullScreenNoAuto |
不开自动化;iOS 17+ 先重置隧道 | 稍慢 | USBHID、BLE,没装代理 IPA |
captureFullScreen / captureFullScreenEx |
自动化服务(代理或镜像) | 快 | 常规代理模式 |
imageAgent.captureFullScreenEx |
手机内执行 | 省传输 | 代理模式、要在手机侧处理 |
截图之后:使用OCR 找文字,再去滑视频
真拿它刷短视频,跑一天下来是这么个循环:
- 截一张图
- OCR 识别,按关键字找目标,比如「搜索」「关注」「私信」
- 算中心点,
clickPoint点过去 swipeToPoint往上滑,进下一个视频- 等加载,再截一张图,重复
容易错误的是这几处:
- 关键字用
indexOf做包含匹配。「搜索」和「搜索框」「搜索商品」都能匹配上 - 滑动的起点终点别贴着屏幕边缘,留一百多像素。贴边滑容易触发系统手势,滑到一半弹回去
- 每次循环都重新截图。用上一轮的图算这一轮的坐标,界面变了,点过去就是别的按钮
- OCR 一次要几百毫秒,循环里别太密集,需要sleep,滑完等两秒比连续滑被风控强
如果识别不到,先用 image.saveTo(img, "debug.png") 把图存下来看一眼,查看图片是否正确。
耗时太大或者黑屏了如何排查
耗时别猜,拿 console.time 量十次:
javascript
for (let i = 0; i < 10; i++) {
console.time("shot");
let img = image.captureFullScreenNoAuto();
logd("第 " + (i + 1) + " 次: " + console.timeEnd("shot") + " ms");
if (img != null) image.recycle(img);
sleep(300);
}
影响耗时的地方,从大到小:分辨率、图片格式(PNG 大、JPG 小)、有没有开预截图、有没有切 mat。
返回 null :先确认设备亮屏、没锁屏,iOS 17 以上调过 resetCaptureScreenNoAutoEnv。隧道没起来的时候日志里全是 null,人容易去翻点击函数,其实跟点击没关系。连续 null 就重置环境,等十秒恢复再重试:
javascript
let img = image.captureFullScreenNoAuto();
if (img == null) {
image.resetCaptureScreenNoAutoEnv();
sleep(10000);
img = image.captureFullScreenNoAuto();
}
截出来是黑图:设备锁屏、目标 App 在后台、隧道刚重置还没恢复,都会黑。先点亮、切回前台,等一两秒再截。
跑一段时间后开始失败 :九成是没回收。每张 AutoImage 都要 image.recycle(),循环里尤其别漏。
要提速:换成预截图。开一个后台预截图线程,之后每帧只取图,不再走完整截图:
javascript
function main() {
image.resetCaptureScreenNoAutoEnv();
image.useOpencvMat(1);
// types 传 10:PNG 不压缩,不开自动化也能截
image.startPreCapScreen("10", 0, 20);
for (let i = 0; i < 10; i++) {
sleep(1000);
console.time("stream");
let img = image.captureScreenStream();
logd("取一帧 " + console.timeEnd("stream") + " ms");
if (img != null) image.recycle(img);
}
image.stopScreenStream();
}
main();