介绍
一:简介
scrcpyRpa是对scrcpy二次开发,增加rpa能力,为游戏脚本和群控真机而设计scrcpyRpa,行业人的需求,(1)技术不要触碰红线,被游戏公司封号(2)软件简单,能快速完成任务,节省劳力力,不想看到各种新概念和没见过的新设计,疲于学习(3)硬件节能,不想购买高配手机和电脑,增加了购买成本和电费支出 (4)能远程控制,不想用远程桌面(各种不便利)。scrcpyRpa为解决行业人四种需求而开发。
二:架构
scrcpyRpa底层使用的是scrcpy,利用scrcpy强大的视频流和执行动作功能,并将图像识别和ocr文字识别合并到scrcpy里面作为机器人的眼睛,再将执行动作(点击滑动等)抽离出来供python调用作为机器人的手。中间层使用的是python,负责多进程多脚本的调度并开启了一个web服务器,下与scrcpy联系,上与上层联系。最上层三大模块,web中控,自动控制(python脚本),手动控制(投屏)。
三:特征
(1):手动和自动共用scrcpy,避免了资源竞争(android录屏接口是独占的)手动和自动一个用另一个就不能用,并节省了一倍的资源消耗,即使避开了资源竞争,那么手动和自动是两个不相关的人员开发,那么也要开启两个进程做着同样的工作。
(2):三大模块,web中控,自动控制(python脚本),手动控制(投屏),全部支持本地使用和远程使用。远程使用的好处是,不管你有多少台设备,只需要一台电脑就能全部控制,无需远程桌面,就像从baidu.com切换到qq.com一样简单。
(3),手动和自动可同时进行,投屏窗口无论放大缩小或隐藏都不会影响自动使用。
四:免费
无限制的永久免费,不会出现使用几个月之后又收费了,也不会出现这个功能免费,使用另外的功能要开通VIP这种情况。
五:优点
(1):非侵入无污染
手机端:不需要安装任何app,也没有任何弹窗需要您给授权,就像手机不存在一样。
电脑端:下载解压双击即可使用,右键点击删除就能彻底删除干净。
(2):简单
手动:甚至不用学习,仅凭自己的本能就能完成控制。
自动:眼睛只有4个接口,手只有6个接口,而大脑,只会用到python的if,循环,函数,变量,逻辑表达式,只用这几个就够了,其他可自由选择用或不用,即可完成所有机器人任务。而开发工具就用notepad Next一个记事本工具即可,或者使用web中控自带的代码编辑器也可以,都是开箱即用。
(3):真人接口
非root,非无障碍,非adb
adb只做信息传递,非执行,执行使用的是scrcpy,而scrcpy使用的是android inputManagerService,inputManagerService就是java层的真人接口。
在些人害怕开启usb调试,这里要跟大家解释一下,任何厂商app都没有办法得知您是否开启了usb模式或者无障碍,但厂商app是可以通过MotionEvent/KeyEvent,也就是事件来源来判断您是用adb或者是用无障碍还是真人操控的手机。也就是说您开启没有问题,但您要使用它操控手机就可能会出现问题。
scrcpyRpa也是需要开启usb调试的,需要使用adb,但adb的作用只负责端口转发和对scrcpyServer的推送,其他事情adb不做,全部都由scrcpyServer完成,包括通信也没有用adb,使用的是unix socket,scrcpyServer使用的是真人接口操控的手机。所以使用scrcpyRpa永远不用担心技术触碰红线。
如果app厂商给您要权限,当你授权之后,提示不能开启开发者模式,这个问题也不要担心,因为手机是您自己的,您的权限大于app厂商的权限,您有很多办法可以绕过。
六:注意
不能跨分辨率和dpi,最好开发时用什么型号手机执行时也用相同型号的,opencv里面有很多可以跨手机的算法,我都帮您试过了,太费资源而且不可靠,如果您不信,您可以试一下网易的airtest,干粗活可以,细活干不了的,当然它主要做测试用的,而游戏的自动完成任务属于细活,所以最终采用了原始的算法,因为越原始越可靠而且节能。
视觉识别
共四条指令,三个图像识别一个ocr文本识别,注意当发生横纵屏切换的时候,那么您开发时候输入的坐标会可能不存在,如果坐标不存在置信度统一返回0,通道求和也是返回0,所以使用通道求和的比较的时候一定要用大于,否则发生横纵屏切换的时候会误判断。
一:固位匹配 fixedMatch
fixedMatch(by)
像素级别的匹配,固定坐标的固定像素之间的匹配
返回值为置信度,范围是0-1
参数:
1,by:必填,图像对象或颜色数组
示例:
#图像对象,图像路径中包含了坐标信息,(各种图像坐标颜色等信息都会有工具生成)。
byImg = loadImage('dingjidong/5l9f662-1894-749-1948')
if fixedMatch(byImg)>0.8:
print('存在')
else:
print('不存在')
#颜色坐标集合,这些数据会有图色工具识取,集合中前面的一维数组[680,1379]代表坐标,后面的[252,93,147]代表rgb颜色
byColor = [[[680,1379],[252,93,147]],[[783,1433],[240,254,250]],[[700,1484],[255,93,144]],[[743,1409],[237,94,135]]]
if fixedMatch(byColor)>0.8:
print('存在')
else:
print('不存在')
#使用颜色坐标集合时,即使只有一个也要使用集合,比如[[[680,1379],[252,93,147]]]正确,[[680,1379],[252,93,147]]错误
二:扫描查找 scanFind
scanFind(by,roi=None,cf=None,nms=None,grey=True):
此方法在cf=None时查找的是一个最大置信度匹配项,在cf不为None查找多个多个大于cf(置信度)的匹配项。
当cf为None时返回值为一个字典{'xy':[700,800],'cf':0.89} ,xy代表匹配项的中心点坐标,cf代表置信度。
当cf不为None时,返回字典数组[{'xy':[700,800],'cf':0.89} ,{'xy':[700,800],'cf':0.89}]。
参数:
1,by:必填,与fixedMatch是一样的
#全屏扫描查找图像,此图像路径虽然包含坐标信息,但实际并不起作用,可任意自定名称,这是跟fixedMatch的区别
byImg = loadImage('dingjidong/5l9f662-1894-749-1948')
aa = scanFind(byImg)
if aa['cf']>0.8:
click(aa['xy'])
#全屏扫描查找颜色,程序内部通过掩码的方式,将aa组成一张图像,没有坐标的部分用掩码填充
aa = [[[680,1379],[252,93,147]],[[783,1433],[240,254,250]],[[700,1484],[255,93,144]],[[743,1409],[237,94,135]]]
sf = scanFind(aa)
print(sf)
2:roi,选填,区域,默认为None,类型为矩形坐标二维数组,前一个是左上角坐标,后一个是右下角坐标,代表只在本区域内查找,查找成功返回中心点坐标,这个参数很重要,如果没有这个参数扫描查找的资源占用率是固位匹配的几万倍到几百万倍,要合理的使用这个参数,将资源占用缩小。
示例:
#只在[[593,1129],[817,1306]]这个区域查找 ,前两个代表左上角坐标,后两个右下角坐标
byImg = loadImage('dingjidong/5l9f662-1894-749-1948')
aa = scanFind(byImg,roi=[[593,1129],[817,1306]])
if aa['cf']>0.88:
click(aa['xy'])
3:cf,选填,置信度,默认为None,代表只查找一个最大置信度匹配项,在cf不为None查找多个多个大于cf(置信度)的匹配项
#查找所有置信度大于0.8的匹配项
by = loadImage('dingjidong/s5qj1650-775-1703-847')
aa = scanFind(by,roi=None,cf=0.8)
print(aa)
4:nms,选填,去除重复,默认为None,此参数只有在cf不为None时才有效,专业术语叫做非极大值抑制,作用就是去除重复匹配,当为None使用的是横向去除重复区域为:模板图像宽度除2,纵向去除重复区域为模板图像高度除2,正常情况下使用默认即可,除非有特殊情况可自行填写,单位为像素。
#背景:因为在扫描查找过程当中存在匹配位置有多个匹配,这些多个匹配只有一个是有效的,所以需要去除无效的匹配
#查找所有置信度大于0.8的匹配项,去除重复横向为10像素,纵向去除重复区域为20像素,一般情况下nms使用默认None即可
by = loadImage('dingjidong/s5qj1650-775-1703-847')
aa = scanFind(by,roi=None,cf=0.8,nms=(10,20))
print(aa)
5:grey,选填,代表灰色,默认就使用灰色。因为灰色只有一个通道,而彩色是三个通道,所以灰色资源占用是彩色的1/3。如果灰色无法实现您的精准查找,可以将grey设置为False
#使用彩色图进行查找
by = loadImage('dingjidong/s5qj1650-775-1703-847')
aa = scanFind(by,grey=False)
print(aa)
三:ocr识别 ocrText
ocrText(roi,lang='en'):
本系统ocr只识别中文和英文两种语言,如果只识别数字和标点符号,建议使用英文识别,一般只用英文识别数字,如果识别文字请用图色来替代可节省资源。返回值为一个一个字典{'txt': '99877', 'cf': 0.99},共两个值,txt为识别的字符,cf为置信度。
参数:
1,roi:必填,类型矩形坐标的数组,第一个是图像左上角坐标,第二个是图像右下角坐标。
示例:
#识别英文,只能识别一行,如果识别多行,就多次识别,这样是为了简单,如果多行识别,还得写正则之类的,太麻烦
aa = ocrText([[693,599],[841,670]])
print(aa)
2:lang:选填,默认为en代表是英文,也可以输入ch,代表中文识别。
示例:
#识别中文,只能识别一行,如果识别多行,就多次识别,这样是为了简单,如果多行识别,还得写正则之类的,太麻烦
aa = ocr([[693,599],[841,670]],lang='ch')
print(aa)
注意事项:scrcpyRpa设计最初目标使用ocr只识别阿拉伯数字和标点符号,所以默认是英文,如果要做文字匹配的话,建立不要用ocr,因为比较费资源,除非必要的情况下,否则应该使用图色来代替。
四:通道求和 channelSum
channelSum(pointsORroi,channel='v',type='points'):
在有动画的情况下,使用前面三个无法达到目的,可使用此方法进行辅助。
参数:
1:pointsORroi:必填,坐标点集合或者roi
示例:
#使用坐标点集合,注意必须是集合,如果一个坐标点要写成[[1875,632]]不能是[1875,632]
pointsORroi = [[1875,632],[1863,651],[1868,673],[1885,689]]
aa = channelSum(pointsORroi)
plog(aa)
2:channel 选填,默认是v,代表亮度,s代表饱和度,r代表红色通道,g代表绿色通道,b代表蓝色通道。
示例:
points = [[1875,632],[1863,651],[1868,673],[1885,689]]
aa = channelSum(points,channel='b')
plog(aa)
3:type 选填,默认points,因为当有两个坐标点的时候,scrcpyRpa无法知道是roi还是两个坐标点,所以用这个参数来区分
#使用roi,roi代表区域,两个坐标点,一个代表左上角一个代表右下角,代表这个一个矩形区域内所有坐标点
pointsORroi = [[569,632],[1885,689]]
aa = channelSum(pointsORroi,type='roi')
plog(aa)
执行动作
模拟人工操作的指令
一:点击 click
click(xy) 无返回值
参数:
1,xy:此为必填参数,代表坐标,类型为一个数组,左为横坐标,右为纵坐标
示例:
click([973,1069])
二:休息 rest
rest(duration) 无返回值,这个用python自带的time.sleep也可以,效果是一样的,因为这个非常常用,用python自带的还得导包,多写一行代码,所以scrcpyRpa提供了这个函数。
参数:
1,duration:必填,代表休息多长时间,单位为秒,类型为数值,可以是整数也可以是浮点数
示例:
#休息5秒钟
rest(5)
#休息0.5秒钟
rest(0.5)
#休息0.01秒钟
rest(0.01)
三:滑动 slide
slide(startEnd,duration=0.1,steps=6) 无返回值
参数:
1,startEnd:必填:滑动开始和结束坐标,类型为一个数组
示例:
slide([[1072,866], [1521,865]])
2,duration:选填,默认0.1秒,类型为浮点或整数,代表滑动的持续时间
示例:
slide([[1072,866], [1521,865]],duration=2)
#模拟长按,在同一个位置滑动就是长按
slide([[1072,866], [1072,866]],duration=2)
3,steps:选填,默认为6,代表用6个步骤完成这一次操作
示例:
slide([[1072,866], [1521,865]],duration=1,steps=10)
#模拟长按,在同一个位置滑动就是长按
slide([[1072,866], [1072,866]],duration=1,steps=10)
四:捏合 zoom
zoom(type,percent) 无返回值
参数:
1,type:in,双手从外到中心点,out从中心点向外扩散,类型为字符串,只能从'in'或'out'两个之间选择一个
#放大50%
zoom('out',50)
#缩小50%
zoom('in',50)
2,percent 代表放大或缩小的百分比,取值范围是1-100,类型为整型
五:按键 pressKey
pressKey(keyCode) 无返回值
参数:keyCode,类型字符串, 代表按下设备某个键。
示例
#按下home键
pressKey('HOME')
# 按下back键
pressKey('BACK')
# 按下app_switch键
pressKey('SWITCH')
# 按下电源键
pressKey('POWER')
# 按下声音增大键
pressKey('V_UP')
# 按下声音减小键
pressKey('V_DOWN')
# 按下MENU键,这个meun现在app很少有用这个键的了。
pressKey('MENU')
#按下回车键
pressKey('KEYCODE_ENTER')
#用回车键的时候,即使按下了用户界面不一定起作用,这是因为用户app界面设计的时候控件未必监听回车键,如果监听了按下回车键起作用,如果没有监听按下回车键无效。
六:app管理 appManage
appManage(package,action) 无返回值
参数:
1,package:必填,填写app的包名路径,类型为字符串,如果不知道具体app的包名路径,可在 设备管理/设置查询下查询。
2,action:必填,默认为open,可以为close,install,uninstall
示例:
#打开抖音
appManage('com.ss.android.ugc.aweme','open')
#关闭抖音
appManage('com.ss.android.ugc.aweme','close')
#安装抖音,注意安装要填写windows下的路径
appManage('d:/douyin.apk','install')
#卸载抖音
appManage('com.ss.android.ugc.aweme','uninstall')
注意:如果app使用了双开,则无法打开和关闭,或者app没有界面也无法进行打开和关闭
七:打字 typing
typing(myText') 无返回值
参数:
1,myText:必填,类型为字符串,如果想在多个字符串中随机选择一个用|号来分隔,如果输入随机ID号,请输入s-j,将生成一个以随机字母开头后面9位字母和数字随机生成的10位字符串
示例:
#只输入字符串东南
typing('东南')
#将在aaa,bbb,ccc中随机选择一个字符串
typing('aaa|bbb|ccc')
#输入的并不是s-j这个字符串,而是ur8edf90i8这种类型的字符串,以字母开头,后9位是字母数字随机组合,可用这个方式输入随机用户名
typing('s-j')
注意事项,汉字最多输入1万个,英文最多可以输入3万个。
磁盘存储
一:获取用户输入 userConfig
userConfig(itemId) :
返回值是用户在单选框或复选框或输入框输入的数据,作用是可根据用户的选择和输入去执行不同的程序。需要自己定义json配置文件,可以自动生成ui界面,供用户输入。
参数:
1,itemId:项目id号 类型为字符串。
示例:
#一共三种,单选框,复选框,输入框
#获取单选框 ,返回类型为数值,用户选择单选框组的第几个
aa = userConfig('a1')
plog(aa)
#获取复选框,返回类型为一个数组,选中是1,没选中是0
aa = userConfig('b2')
plog(aa)
#获取输入框的值,返回类型也是一个数组,输入框只能是数值
aa = userConfig('c1')
plog(aa)
2:定义ui界面,需要自己编写json文件,系统会根据json文件自动生成ui界面
//这个json文件在本地使用时命名方式必须叫config.json,
{
//定义单选框
"a1": {
"name": "a1",
"type":"radio",
"branch": [
"454",
"546",
"67"
],
"val": 0
},
//定义复选框
"b1":{
"name":"b1",
"type":"checkbox",
"lab":["复1","复2","复3"],
"val":[0,1,0]
},
//定义输入框
"c1":{
"name":"c1",
"type":"input",
"lab":["输1","输2","输3"],
"val":[10,85,69]
}
}
二:自定存储 customStore
customStore(action,itemId=None,val=None)
以字典的形式保存到json文件中,可进行查询,设置,删除
参数:
1,action: 类型为字符串,只有三种值,get ,set ,del,代表查询,设置,和删除
示例:
#查询id编号为a1的值,如果没有a1,则返回None,存在则返回a1的值
aa = customStore('get',itemId='a1')
plog(aa)
#设置a1的值,设置的时候无返回值
customStore('set',itemId='a1',val=100)
#删除,删除不是删除一个,是将当前设备设置的所有自定义存储全部删除
customStore('del')
三,写入统计 writeHtml
writeHtml(val,name='金币') 无返回值,统计完成之后会自动在 运行状态/数据日志 中显示。一般跟ocr识别联合使用。最多显示50条,如果超过50条,则删除最后一条,再加入最新一条。
参数:
1,val: 必填,类型为数值
示例:
writeHtml(96.36)
2,name:选填,类型为字符串,代表统计项的名称,比如游戏中的金币,钻石之类。
示例:
#将当前金币数量写入统计页
writeHtml(96.36,name='金币')
扩展指令
用的相对比较少的指令
一:打印日志 plog()
plog(myText) 无返回值,也可以使用python自带的print(),如果使用print()函数,则在cmd控制台中显示。
参数:
1,myText:必填,类型为可以转换为字符串的任意类型,请注意此条指令在python测试页面中执行和在调度监控中执行显示的地方不一样,如果在调度监控中,将在 运行状态/统计日志 /查看日志 中显示,最多显示500条,超过删除最旧的进入最新的。如果在python测试页面则在网页代码编辑器右侧显示,最多显示1万字符,超过最旧的出最新的进。
这个打印语句是唯一和原生python不一样的地方,原生python打印用的是print(),是在命令行下打印,由于scrcpyRpa是在网页环境下,所以新创了一个指令,plog()
示例:
plog('hello')
plog(23.6)
plog(True)
二,手动报警 alarm():
alarm(strTalk) 无返回值,这个指令没有太大的用处,可能永远都用不上,因为写脚本的时候scrcpyRpa把各种意外情况给自动处理了,所以此函数用处不大, 但如果你专门做测试,或者断网的情况,此函数还是有用的,手动报警之后,进程将结束,并且将报警信息显示在 调度监控表格的状态消息中或者脚本编辑器中的右侧。
参数:
1,strTalk: 必填,类型为字符串,代表报警的消息。
示例:
alarm('可能已断网,请人工检查原因')
三,获取当前时间 currentTime()
这个功能用python自带的也可以,由于常用就写了一个函数。获取的是2025-01-22 15:01:34这种格式的字符串,时,分,秒
示例:
#最终打印出2025-01-22 15:01:34
aa = currentTime()
plog(aa)
四,比较时间 compareTime()
compareTime(aTime,bTime) ,返回值是aTime-bTime,单位是秒,如果aTime比bTime比则返回负数
这个功能用python自带的也可以,由于常用就写了这样一个函数,比较两个时间的时间差(aTime-bTime),aTime和bTime要求都是2025-01-22 15:01:34这种格式的字符串
示例:
aa = currentTime()
bb = '2025-01-22 10:01:12'
cc = compareTime(aa,bb)
plog(cc)
五,随机数
rd = random.randint(1, 5) 生成1到5之间的随机数,这个也是比较常用,如果自己写还得导一下包,多写一条代码,所以scrcpyRpa已经把random包导入 ,不用再导包了
六:自由页与全局内存
有的时候我们需要一个实时提供信息的界面,比如扑克牌的记牌器,这个界面为自由页,可以自由任意编写html css js代码
有了自由页,我们还需要 有一个跟python脚本通信的桥梁。
scrcpyRpa在内存中开辟了一块0.1M的全局内存,在这块内存空间上,自由页和任何python脚本进程都可以进行读写,格式为json
1:web对全局内存读取
//读取使用get请求 如果全局内存没有任何数据则返回空{}
const freeUrl = window.location.origin+ "/freeJson";
async function getFreeJson() {
try {
const response = await fetch(freeUrl);
const data = await response.json();
console.log('请求成功:', data);
//这里做页面渲染的工作
} catch (error) {
console.error('请求失败:', error);
}
2:web对自全局内存写操作
//写操作使用post请求,成功返回1,失败返回0
const freeUrl = window.location.origin+ "/freeJson";
async function setFreeJson(data) {
try {
const response = await fetch(freeUrl, {
method: 'POST', // 指定请求方法为 POST
headers: {
'Content-Type': 'application/json', // 告诉服务器请求体是 JSON 格式
},
body: JSON.stringify(data) // 将数据转为 JSON 字符串
});
const result = await response.json();
console.log('请求成功,返回数据:', result);
//这里一般什么都不用做
} catch (error) {
console.error('请求失败:', error.message);
}
3:python脚本对全局内存读取操作
#如果全局内存没有任何数据则返回空字典{}
aa = getFreeJson()
print(aa)
4:python脚本对全局内存写操作
myDict = {"cc":11,"dd":22}
setFreeJson(myDict)
七:图像识别特殊情况
如果遇到特殊情况,scrcpyRpa提供的opencv图像识别无法满意您的需要,您可以自行导入opencv
#导入opencv
import numpy as np
import cv2
#获取设备截屏
img = getScreen()
#有了opencv和截屏您就可以自行处特殊需求了
#比如在桌面显示图像
cv2.imshow('this is my images', img)
cv2.waitKey(0)
注意:img = getScreen()是动态截屏,如果想使用静态截屏则img = getScreen().copy()。
动态截屏是指针,没有截屏动作,实时获取。而静态截屏是真实数据,有截屏动作,非实时的。
八:全局常量
selfDirectory 返回字符串格式的self目录的路径
此常量获取的是自己本地工作目录(self目录),有的时候自己会自定义一个json文件的时候会用到。当然不用也可以,自己想把json文件放到哪里都是可以的,最建议放到这里。
脚本的架构和执行方式
一:脚本的架构
所有脚本都是独立的,但有一个例外就是pub.py,pub.py是所有脚本共用的。比如执行one.py,那么pub.py也跟着执行,执行two.py,pub.py也跟着执行,执行任何脚本pub.py都会执行。
二:脚本的执行方式
(1):python测试页面,如果有代码就执行写的代码,如果没有任何代码就执行self/project下的pub.py和test.py。这两个都执行。
(2):在调试监控页面,可以任何选择脚本,并排序,但同样是执行任何一个脚本pub.py都跟随执行。
本地投屏快捷键
与scrcpy原生是一样的没有做任何改动,下面这段文字也是在scrcpy官网中复制的。
MOD在windows上面是ALT键
常用:
Home键 ALT+H
Back键 ALT+B
SWITCH键 ALT+S
电源键 ALT+P
安装或者复制文件,直接拖动到窗口即可
详细快捷键
| 切换全屏模式 | MOD+f |
|---|---|
| 向左旋转显示器 | MOD+← (左) |
| 向右旋转显示器 | MOD+→ (右) |
| 水平翻转显示 | MOD+Shift+← (左) |++*(右)*MODShift→ |
| 垂直翻转显示 | MOD+Shift+↑ (向上) |++*(向下)*MODShift↓ |
| 暂停或重新暂停显示 | MOD+z |
| 取消暂停显示 | MOD+Shift+z |
| 重置视频捕获/编码 | MOD+Shift+r |
| 将窗口大小调整为 1:1(像素完美) | MOD+g |
| 调整窗口大小以删除黑色边框 | MOD+w|双击左键¹ |
点击HOME | MOD+h|单击鼠标中键 |
点击BACK | MOD+b|+ |右键单击²MODBackspace |
点击APP_SWITCH | MOD+s|第 4 次点击³ |
点击 (解锁屏幕)⁴MENU | MOD+m |
点击VOLUME_UP | MOD+↑ (上) |
点击VOLUME_DOWN | MOD+↓ (向下) |
点击POWER | MOD+p |
| 开机 | 右键单击² |
| 关闭设备屏幕(保持镜像) | MOD+o |
| 打开设备屏幕 | MOD+Shift+o |
| 旋转设备屏幕 | MOD+r |
| 展开通知面板 | MOD+n|第 5 次点击³ |
| 展开设置面板 | MOD+n+n|双击第 5 次单击³ |
| 折叠面板 | MOD+Shift+n |
| 复制到剪贴板⁵ | MOD+c |
| 剪切到剪贴板⁵ | MOD+x |
| 同步剪贴板和粘贴⁵ | MOD+v |
| 插入计算机剪贴板文本 | MOD+Shift+v |
| 打开键盘设置(仅限 HID 键盘) | MOD+k |
| 启用/禁用 FPS 计数器(在 stdout 上) | MOD+i |
| 捏合缩放/旋转 | Ctrl+单击并移动 |
| 垂直倾斜(用 2 个手指滑动) | Shift+单击并移动 |
| 水平倾斜(用 2 个手指滑动) | Ctrl+Shift+单击并移动 |
| 拖放APK文件 | 从计算机安装 APK |
| 拖放非APK文件 | 将文件推送到设备 |
出现 ERROR: Capture/encoding error: java.lang.IllegalStateException: null,
1:是屏幕录制被其他应用占用,请关闭这个应用再重新打开
2:手机资源不够用,请关闭某些APP,释放资源再打开scrcpy