我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第6篇文章,点击查看活动详情
牙叔教程 简单易懂
上一节讲了模拟器怎么发现手机的ip最后一位
autojs文件互传(模拟器和手机)(一)
这一节讲 传文件
传文件用什么?
传文件还用socket吗?
No, 大大的No!
我们最重要的目标是实现我们的目标, 成本最低, 时间最短,
会什么, 就用什么, 那个最熟最快, 就用那个
已知的信息
- 手机ip
- 手机是服务端
- 模拟器是客户端
- 双方可以通过socket通信
手机给模拟器传文件步骤
- 用户选择文件, 点击发送, 通过socket, 通知模拟器下载文件
- 手机用koa做服务器, 以便模拟器下载文件
- 模拟器下载完之后, 通过socket通知手机:我下载完了
\
模拟器给手机传文件步骤
- 用户选择文件, 模拟器用autojs的http.post直接上传文件
- 模拟器上传文件之后, 通过socket通知手机文件已经上传
至于进度条百分比, 就不写了, 不然还得写一堆代码;
下载完后, 会有toast气泡提示;
以上就是大概的思路, 下面开始写代码
koa作为服务端接收文件
"nodejs";
const koa = require("koa");
const app = new koa();
const koaBody = require("koa-body");
const router = require("koa-router")();
const fs = require("fs");
let dirName = "upload";
fs.mkdir(dirName, (err) => {
// if (err) throw err; // 如果出现错误就抛出错误信息
console.log("文件夹创建成功");
});
app.use(
koaBody({
multipart: true,
formidable: {
maxFileSize: 200 * 1024 * 1024, // 设置上传文件大小最大限制,默认2M
},
})
);
router.get("/hello", async (ctx) => {
console.log("hello");
ctx.body = "router: hello world";
});
router.post("/upload", async (ctx) => {
const file = ctx.request.files.file; // 获取上传文件
const fileName = file.originalFilename;
const reader = fs.createReadStream(file.filepath); // 创建可读流
console.log(file.filepath);
const writeStream = fs.createWriteStream(`${dirName}/${fileName}`); // 创建可写流
reader.pipe(writeStream); // 可读流通过管道写入可写流
console.log("文件写入完成");
//文件关闭事件
writeStream.on("close", () => {
console.log("文件已关闭!");
fs.unlink(file.filepath, (err) => {
if (err) throw err;
console.log("删除成功");
});
});
return (ctx.body = "上传成功");
});
// 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件
// 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented`
app.use(router.routes());
app.use(router.allowedMethods({}));
app.listen(3001, () => {
console.log("koa is listening in 3001");
});
文件互传的关键代码就以上这些, 下面开始整合代码
UI界面
如果给我自己用的话, 局域网ip就是固定的192.168.5,
要给别人用的话, 这个就得弄个输入框让用户自己修改
"ui";
ui.layout(
<vertical padding="32">
<text>局域网IP前三位</text>
<input>192.168.5.</input>
</vertical>
);
下面是一个搜索界面, 搜索周围的设备
\
界面之后的代码
接下来要判断是模拟器还是手机
- 如果是模拟器, 就一遍一遍的扫描端口, 最多扫1分钟, 没有就关闭程序
- 如果是手机, 就把socket服务开起来, 把koa也开起来
- 双方之间交换信息用socket, 传文件用koa和autojs的http
判断是否模拟器, socket以及koa的关键代码, 在上一节中有写, 没有的可以回去看上一小节;
注意事项
autojs的nodejs用koa-body接收文件上传的时候, 临时文件默认在/tmp这个目录下面, 但是autojs作为普通app, 是不可能有这个权限的, 因此我们将koa-body换成可以修改临时文件存储的地方的模块 formidable
用法如下, 作为koa的中间件
koa2中的app.use()只能用于注册中间件并且必须是生成器函数
修改临时文件存储的方法, 就是在 formidable 中添加属性 ****uploadDir
app.use(async (ctx, next) => {
if (ctx.url === "/upload" && ctx.method.toLowerCase() === "post") {
// const form = formidable({ multiples: true, uploadDir: "/sdcard/脚本/ajnodetest/temp" });
const form = formidable({ multiples: true, uploadDir: "temp" });
await new Promise((resolve, reject) => {
form.parse(ctx.req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
ctx.set("Content-Type", "application/json");
ctx.status = 200;
ctx.state = { fields, files };
ctx.body = JSON.stringify(ctx.state, null, 2);
resolve();
});
});
await next();
return;
}
......
});
发现局域网设备步骤
- 手机作为服务端, 监听指定端口
- 模拟器遍历0-255, 一共256个ip, 持续一分钟, 直到通信成功
- 模拟器发现设备后, 绘制用户头像到UI上
模拟器是客户端, 手机是服务端
下面展示 客户端 断开连接的情况:
点击返回键, 程序运行结束,
再点击运行项目;
注意看头像
\
下面展示 服务端 断开连接的情况:
服务端上方会显示当前连接的IP和端口, 如果没有客户端, 就显示为空;
注意看头像
点击客户端的女生头像以后, 会跳转到上传文件界面
\
上传文件注意事项
安卓是linux系统, 默认临时文件存储在/tmp这个文件夹里, 但是autojs作为普通app是没有这个权限访问该文件夹的;
因此, 我们要修改临时文件目录, 以便让autojs有访问权;
在koa中, 我们监听文件上传使用的是koa-body, ,在koa-body中, 有很多选项可以配置
修改临时文件的方法如下
在formidable属性中, 修改uploadDir属性的值
app.use(
koaBody({
multipart: true,
formidable: {
maxFileSize: 200 * 1024 * 1024, // 设置上传文件大小最大限制,默认2M
uploadDir: "/sdcard/脚本/autojs-file-transfer/upload-temp",
},
})
);
上传文件之后, 我们可以用socket通知, 或者post一个消息, 通知服务端我们传完了,
这里我就不写了, 因为我要写好多字, 简便起见, 这个通知功能我也不做了;
上面是模拟器给手机发送文件, 下面我们用手机给模拟器发送文件
手机给模拟器发送文件
一开始, 我们设计的是, 模拟器没有监听端口的功能, 因此, 思路是这样的:
- 手机通过socket通知模拟器, 你过来取某某文件;
- 模拟器听到之后, 就发送get命令, 取获取某某文件
- over
这个主要添加的代码是autojs的rhino和nodejs以及socket之间的通信;
手机这边点击头像, 同样是跳转到发送文件的页面, 点击发送按钮;
由于模拟器和手机用的是同一个页面, 因此在这个页面, 同样要判断是不是模拟器,
从而执行不同的操作;
这个上传界面是一个独立的脚本引擎, 我们要通知给之前已经连接好的socket,
socket在nodejs环境, 因此, 我们可以创建一个新的nodejs脚本, 在nodejs环境中广播;
也可以直接用autojs的rhino广播, 主脚本接收到广播, 再通过持有的nodejs引用, 去通知nodejs的socket发送消息
这里我选择rhino广播, 广播内容如下
- rhino发广播给nodejs, 我们要传文件啦, 文件路径是XXX
- nodejs收到广播, 通过socket传给模拟器的socket, 传递的数据是: 文件路径 + 上传文件的IP和端口
- 模拟器接收到消息, 按照指定IP和端口, 并提交文件路径,
- 服务器接收到请求, 读取指定文件, 返回给模拟器
socket这个nodjs代码中, 只管端口监听, 不管文件上传的监听, 因此
在该文件中, 要加一个nodejs广播监听, 去接收管文件上传的那个nodejs的广播, 以此获得上传文件的IP和端口数据
nodejs广播通信方式:
备注:
- nodejs的广播, 只有nodejs的脚本能接收到,
- rhino的脚本是接收不到的
- nodejs本身没有这个广播功能, 这个是autojs封装了nodejs后, 新增的功能
\
发送广播
const { broadcast } = require("engines");
broadcast("name", name);
监听广播
engines.myEngine().on("name", function (name) {
...
});
nodejs给rhino发送消息
// 从参数中取出Rhino引擎的ID
const serverEngineId = engines.myEngine().execArgv.serverEngineId;
// 根据ID找出Rhino引擎
const serverEngine = engines.getRunningEngines().find(e => e.id === serverEngineId);
serverEngine.emit('reply', {
command: 'httpGet',
result: res.data
});
rhino给nodejs发送消息
// 启动Node.js脚本
const execution = $engines.execScriptFile('./http请求.node.js', {
arguments: {
serverEngineId: $engines.myEngine().id
}
});
// 给Node.js引擎发送消息
execution.engine.emit('command', {
name: 'httpGet',
args: {
url: 'https://pro.autojs.org'
}
});
rhino广播监听
监听广播
events.broadcast.on("hello", function(name){
toast("你好, " + name);
});
发送广播
events.broadcast.emit("hello", "小明");
从哪里获取服务端IP
我网上搜了一下, nodejs获取自己的IP代码有点多, 所以我决定, 还是让模拟器的socket获取服务端的IP吧,
毕竟客户端获取IP只需要一个socket的属性
// 获取服务端ip
let serverIP = socket.remoteAddress;
// 获取服务端端口
let serverPort = socket.remotePort;
// 获取客户端ip
let clientIP = socket.localAddress;
// 获取客户端端口
let clientPort = socket.localPort;
\
\
发送文件效果演示
手机发送文件给模拟器
模拟器发送文件给手机
\
\
注意事项
- 本教程使用了nodejs, 并且依赖koa, 打包的时候, 注意现在手机上安装koa, 不然因为这个报错, 又浪费不少时间;
- 本教程的代码经过测试没有明显的bug, 不代表没有bug
- 代码仅提供手机和模拟器传文件的功能, 手机与手机传文件的功能请自行修改
- UI界面输入框里的值都可以修改, 修改以后记录到了文本中, 重启app就会生效
环境
设备: 小米11pro
Android版本: 12
雷电模拟器:9.0.17
Android版本: 9
Autojs版本: 9.2.13
名人名言
思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问 --- 牙叔教程
声明
部分内容来自网络 本教程仅用于学习, 禁止用于其他用途