autojs文件互传(模拟器和手机)(二)

873 阅读8分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第6篇文章,点击查看活动详情

牙叔教程 简单易懂

上一节讲了模拟器怎么发现手机的ip最后一位

autojs文件互传(模拟器和手机)(一)

www.yuque.com/yashujs/bfu…

这一节讲 传文件

传文件用什么?

传文件还用socket吗?

No, 大大的No!

我们最重要的目标是实现我们的目标, 成本最低, 时间最短,

会什么, 就用什么, 那个最熟最快, 就用那个

已知的信息

  • 手机ip
  • 手机是服务端
  • 模拟器是客户端
  • 双方可以通过socket通信

手机给模拟器传文件步骤

  1. 用户选择文件, 点击发送, 通过socket, 通知模拟器下载文件
  2. 手机用koa做服务器, 以便模拟器下载文件
  3. 模拟器下载完之后, 通过socket通知手机:我下载完了

\

模拟器给手机传文件步骤

  1. 用户选择文件, 模拟器用autojs的http.post直接上传文件
  2. 模拟器上传文件之后, 通过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;
    }
  	......
});

发现局域网设备步骤

  1. 手机作为服务端, 监听指定端口
  2. 模拟器遍历0-255, 一共256个ip, 持续一分钟, 直到通信成功
  3. 模拟器发现设备后, 绘制用户头像到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一个消息, 通知服务端我们传完了,

这里我就不写了, 因为我要写好多字, 简便起见, 这个通知功能我也不做了;

上面是模拟器给手机发送文件, 下面我们用手机给模拟器发送文件

手机给模拟器发送文件

一开始, 我们设计的是, 模拟器没有监听端口的功能, 因此, 思路是这样的:

  1. 手机通过socket通知模拟器, 你过来取某某文件;
  2. 模拟器听到之后, 就发送get命令, 取获取某某文件
  3. over

这个主要添加的代码是autojs的rhino和nodejs以及socket之间的通信;

手机这边点击头像, 同样是跳转到发送文件的页面, 点击发送按钮;

由于模拟器和手机用的是同一个页面, 因此在这个页面, 同样要判断是不是模拟器,

从而执行不同的操作;

这个上传界面是一个独立的脚本引擎, 我们要通知给之前已经连接好的socket,

socket在nodejs环境, 因此, 我们可以创建一个新的nodejs脚本, 在nodejs环境中广播;

也可以直接用autojs的rhino广播, 主脚本接收到广播, 再通过持有的nodejs引用, 去通知nodejs的socket发送消息

这里我选择rhino广播, 广播内容如下

  1. rhino发广播给nodejs, 我们要传文件啦, 文件路径是XXX
  2. nodejs收到广播, 通过socket传给模拟器的socket, 传递的数据是: 文件路径 + 上传文件的IP和端口
  3. 模拟器接收到消息, 按照指定IP和端口, 并提交文件路径,
  4. 服务器接收到请求, 读取指定文件, 返回给模拟器

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;

\

\

发送文件效果演示

手机发送文件给模拟器

模拟器发送文件给手机

\

\

注意事项

  1. 本教程使用了nodejs, 并且依赖koa, 打包的时候, 注意现在手机上安装koa, 不然因为这个报错, 又浪费不少时间;
  2. 本教程的代码经过测试没有明显的bug, 不代表没有bug
  3. 代码仅提供手机和模拟器传文件的功能, 手机与手机传文件的功能请自行修改
  4. UI界面输入框里的值都可以修改, 修改以后记录到了文本中, 重启app就会生效

环境

设备: 小米11pro
Android版本: 12
雷电模拟器:9.0.17
Android版本: 9
Autojs版本: 9.2.13

名人名言

思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问 --- 牙叔教程

声明

部分内容来自网络 本教程仅用于学习, 禁止用于其他用途

微信公众号 牙叔教程