前言
大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!
第十六章:与第三方应用集成
一、理论讲解:Auto.js 与第三方应用交互的原理与方式
1.1 为什么需要与第三方应用集成?
在自动化脚本中,仅限于操作当前应用或 Auto.js 自身功能是远远不够的。很多自动化场景需要与系统服务、其他应用(如微信、支付宝、短信、浏览器、文件管理器等)进行深度交互,例如:
- 消息通知:自动发送消息给微信/QQ好友或群组,发送短信、邮件。
- 支付场景:唤起支付宝/微信支付,完成自动支付或查询订单。
- 数据共享:在不同应用间传递数据(如从浏览器复制内容到笔记应用)。
- 功能扩展:利用其他应用提供的能力(如相册选择图片、调用地图导航)。
- 跨应用自动化:从一个应用跳转到另一个应用执行特定操作。
1.2 Android 应用间通信机制 (Intent、Activity、BroadcastReceiver)
Auto.js 作为运行在 Android 平台上的 JavaScript 引擎,可以通过调用 Android 原生 API 来实现与第三方应用的集成。核心机制包括:
- Intent (意图):Android 中用于组件间通信的消息对象。可以用于启动 Activity(界面)、Service(后台服务)、BroadcastReceiver(广播)。Intent 可以携带数据,是应用间交互的主要方式。
- 显式 Intent:明确指定目标组件(包名+类名)。
- 隐式 Intent:通过 Action (动作) 和 Category (类别) 匹配目标组件,由系统选择最合适的应用处理。
- Activity (活动):应用程序界面的基本组件。Intent 可以用来启动其他应用的 Activity。
- BroadcastReceiver (广播接收器):用于接收系统或应用发送的广播消息。Auto.js 可以发送广播。
- ContentProvider (内容提供者):用于在应用间共享数据(如联系人、短信)。
- AccessibilityService (无障碍服务):Auto.js 的核心能力,通过模拟用户操作(点击、滑动、输入等)实现 UI 自动化,这是最常用的集成方式。
1.3 Auto.js 集成第三方应用的主要方法
- UI 自动化操作:通过
text(),id(),className(),desc()等查找控件并模拟点击、输入、滑动。这是最通用也最直接的方式,但对 UI 变化敏感。 app模块 (API 封装):Auto.js 提供了app模块,封装了常用的应用操作 API,如app.launchApp(),app.openUrl(),app.sendSms(),app.viewFile()等,方便直接调用。context.startActivity(intent)(原生 Intent):最灵活、强大的方式,可以直接构造 Android Intent 对象,启动任何支持 Intent 的 Activity 或 Service,并传递复杂数据。- 调用
shell命令:通过shell命令执行系统级别的操作,如am(Activity Manager) 命令启动应用或 Activity。 - 利用公共接口/API:某些应用会开放 HTTP API 或 SDK 供开发者集成,Auto.js 可以通过
http模块进行网络请求。
1.4 权限与兼容性问题
- 权限:与第三方应用集成通常需要额外的权限,如发送短信、读取联系人、悬浮窗、无障碍服务等。需要确保脚本拥有必要的权限。
- 兼容性:不同 Android 版本、不同手机品牌、不同应用版本都可能导致集成方式的差异。例如,应用的 UI 变化可能导致控件查找失败,高版本 Android 对后台启动应用、文件访问等有更严格的限制。
- App 内部检测:部分 App 会检测自动化工具,可能导致脚本失效甚至封号风险。
二、代码示例:Auto.js 与第三方应用集成全场景实战
2.1 启动第三方应用 (包名/应用名)
// 方式一:通过包名启动 (推荐,精确)
// 获取微信包名:在Auto.js的"App信息"里查看,或用adb shell pm list packages -f | grep weixin
var wechatPackage = "com.tencent.mm";
if (app.launch(wechatPackage)) {
toast("微信已启动");
sleep(3000);
// 启动后可以继续进行UI自动化操作
// 例如:click("通讯录");
} else {
toast("微信启动失败,可能未安装或包名错误");
}
// 方式二:通过应用名启动 (可能不准确,有同名应用风险)
if (app.launchApp("支付宝")) {
toast("支付宝已启动");
} else {
toast("支付宝启动失败");
}
2.2 发送短信、打电话、打开网页
这些是 app 模块提供的便捷功能。
// 发送短信
app.sendSms("10086", "查询话费");
toast("短信已发送到短信应用");
// 打电话
app.call("10010");
toast("已跳转到拨号界面");
// 打开网页
app.openUrl("https://www.baidu.com");
toast("已用浏览器打开百度");
// 访问文件 (这里会弹出一个文件选择器或文件管理器)
app.viewFile("/sdcard/test.txt");
toast("已尝试打开文件");
2.3 构造并发送复杂 Intent
这是 Auto.js 与第三方应用深度集成的核心,允许传递各种数据类型和标志。
// 引入必要的 Java 类
var Intent = Java.type("android.content.Intent");
var Uri = Java.type("android.net.Uri");
// 示例1:启动微信扫一扫 (显式Intent,指定包名和类名)
function launchWechatScan() {
var intent = new Intent();
intent.setClassName("com.tencent.mm", "com.tencent.mm.plugin.scanner.ui.BaseScanUI");
// 或者指定Action来启动,如果知道的话
// intent.setAction("com.tencent.mm.action.WX_SCAN");
try {
context.startActivity(intent);
toast("已尝试启动微信扫一扫");
} catch (e) {
log("启动微信扫一扫失败: " + e, "ERROR");
toast("启动失败,请检查微信版本或权限");
}
}
// launchWechatScan();
// 示例2:跳转到支付宝付款码界面 (隐式Intent,通过Uri)
function launchAlipayPaymentCode() {
var intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("alipayqr://platformapi/startapp?saId=10000007"));
// 添加 FLAG_ACTIVITY_NEW_TASK,确保在非Activity上下文也能启动
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(intent);
toast("已尝试启动支付宝付款码");
} catch (e) {
log("启动支付宝付款码失败: " + e, "ERROR");
toast("启动失败,请检查支付宝是否安装");
}
}
// launchAlipayPaymentCode();
// 示例3:分享文本到任意应用
function shareText(textToShare) {
var intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain"); // 指定分享内容类型为纯文本
intent.putExtra(Intent.EXTRA_TEXT, textToShare); // 放入要分享的文本
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 兼容无界面启动
try {
// 创建一个选择器,让用户选择分享到哪个应用
var chooserIntent = Intent.createChooser(intent, "分享到...");
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 选择器也需要此Flag
context.startActivity(chooserIntent);
toast("已弹出分享选择器");
} catch (e) {
log("分享失败: " + e, "ERROR");
}
}
// shareText("我正在使用Auto.js自动化脚本!");
// 示例4:安装 APK 文件
function installApk(apkPath) {
var file = new java.io.File(apkPath);
if (!file.exists()) {
toast("APK文件不存在: " + apkPath);
return;
}
var intent = new Intent(Intent.ACTION_VIEW);
var MimeTypeMap = Java.type("android.webkit.MimeTypeMap");
var type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(apkPath));
// Android 7.0 (API 24) 及以上需要使用 FileProvider
if (device.sdkInt >= 24) {
var FileProvider = Java.type("androidx.core.content.FileProvider");
// 替换你的应用包名
var authority = context.getPackageName() + ".fileprovider";
var uri = FileProvider.getUriForFile(context, authority, file);
intent.setDataAndType(uri, type || "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予临时读取URI权限
} else {
intent.setDataAndType(Uri.fromFile(file), type || "application/vnd.android.package-archive");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(intent);
toast("已尝试安装APK");
} catch (e) {
log("安装APK失败: " + e, "ERROR");
toast("安装失败,请检查文件或权限");
}
}
// installApk("/sdcard/Download/some_app.apk"); // 替换为实际APK路径
2.4 使用 shell 命令启动应用/Activity
这是一种更底层的方式,可以绕过一些 Intent 的限制,但需要 root 权限或特定设置。
// 启动微信主界面 (需要Root权限或者在无障碍服务中运行)
// 注意:shell命令的路径可能因设备而异
// var cmd = "am start -n com.tencent.mm/.ui.LauncherUI";
// shell(cmd, true); // true 表示需要 root 权限
// toast("尝试通过shell启动微信");
// 启动指定 Activity 并传递数据
// var cmd2 = "am start -n com.example.app/.MainActivity --es 'param1' 'value1'";
// shell(cmd2, true);
2.5 监听广播与发送广播
Auto.js 可以发送自定义广播,也可以监听系统或应用发送的广播(但监听其他应用广播通常需要特定权限或更复杂实现)。
// 发送自定义广播 (需在Auto.js或其他应用中注册接收器)
function sendCustomBroadcast() {
var intent = new Intent("com.autojs.ACTION_CUSTOM_EVENT");
intent.putExtra("data", "Hello from Auto.js Broadcast!");
context.sendBroadcast(intent);
toast("自定义广播已发送");
}
// sendCustomBroadcast();
// 监听系统广播 (例如:监听屏幕亮灭)
// Auto.js 脚本通常以服务形式运行,可以注册 BroadcastReceiver
// var filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
// var receiver = new Java.type("android.content.BroadcastReceiver")({
// onReceive: function(context, intent) {
// log("屏幕亮了!");
// }
// });
// context.registerReceiver(receiver, filter);
// log("已注册屏幕亮屏广播接收器");
// 注意:在脚本结束时需要 unregisterReceiver 否则可能内存泄漏
// events.on("exit", function(){
// context.unregisterReceiver(receiver);
// log("广播接收器已注销");
// });
2.6 与 Web 服务集成 (HTTP API)
如果第三方应用提供 Web API,可以直接通过 http 模块进行交互。
// 示例:通过 API 发送消息到钉钉群 (假设有机器人Webhook)
function sendDingTalkMessage(content) {
var webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN";
var data = {
"msgtype": "text",
"text": {
"content": content
}
};
http.postJson(webhookUrl, data, function(res, err) {
if (err) {
log("发送钉钉消息失败: " + err, "ERROR");
} else {
log("发送钉钉消息结果: " + res.body.string(), "INFO");
}
});
}
// sendDingTalkMessage("Auto.js 自动化脚本运行通知!");
2.7 调用其他应用的 ContentProvider (读取联系人)
需要 android.permission.READ_CONTACTS 权限。
// 读取联系人 (需要 READ_CONTACTS 权限)
function readContacts() {
var ContactsContract = Java.type("android.provider.ContactsContract");
var cursor = context.getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI,
null,
null,
null,
null
);
if (cursor != null) {
while (cursor.moveToNext()) {
var id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
var name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
log("联系人: ID = " + id + ", 姓名 = " + name);
}
cursor.close();
} else {
log("无法读取联系人,请检查权限。", "WARN");
}
}
// readContacts();
三、实战项目:唤起微信自动发送消息
3.1 项目需求
开发一个 Auto.js 脚本,能够通过 Intent 直接唤起微信,并自动向指定好友或文件传输助手发送一条消息。这个项目将结合 Intent 和 UI 自动化,展示如何高效地与微信集成。
3.2 项目结构
autojs-wechat-integrator/
├── main.js # 主入口,调用发送消息模块
├── modules/
│ ├── wechatSender.js # 负责唤起微信和发送消息的核心逻辑
│ └── logger.js # 日志模块
├── logs/
│ └── wechat_log.log
└── README.md
3.3 logger.js 日志模块 (复用)
// modules/logger.js
function log(msg, level = "INFO") {
let line = `[${new Date().toLocaleTimeString()}][${level}] ${msg}`;
files.append("../logs/wechat_log.log", line + "\n");
console.log(line); // 同时输出到控制台
}
module.exports = { log };
3.4 wechatSender.js 微信发送模块
// modules/wechatSender.js
const logger = require('./logger.js');
const WECHAT_PACKAGE = "com.tencent.mm";
const WECHAT_LAUNCHER_ACTIVITY = "com.tencent.mm.ui.LauncherUI";
/**
* 启动微信并自动发送消息给指定联系人
* @param {string} contactName 接收消息的联系人名称(或文件传输助手)
* @param {string} message 要发送的消息内容
* @returns {boolean} 是否成功发送
*/
function sendWechatMessage(contactName, message) {
logger.log(`尝试向 '${contactName}' 发送消息:'${message}'`);
// 1. 启动微信
logger.log("启动微信...");
if (!app.launch(WECHAT_PACKAGE)) {
logger.log("微信启动失败,请检查是否安装。", "ERROR");
toast("微信启动失败");
return false;
}
// 等待微信主界面加载
waitForActivity(WECHAT_LAUNCHER_ACTIVITY, 5000);
sleep(2000); // 等待界面稳定
// 2. 进入搜索界面
logger.log("进入微信搜索...");
let searchBtn = id("com.tencent.mm:id/fsl").findOne(3000) || desc("搜索").findOne(3000); // 微信搜索按钮ID可能变化
if (!searchBtn) {
logger.log("未找到微信搜索按钮。", "ERROR");
toast("未找到搜索按钮");
return false;
}
searchBtn.click();
sleep(1500);
// 3. 输入联系人名称并选择
logger.log(`搜索联系人:'${contactName}'`);
let searchInput = id("com.tencent.mm:id/fsk").findOne(3000) || className("android.widget.EditText").desc("搜索").findOne(3000);
if (!searchInput) {
logger.log("未找到搜索输入框。", "ERROR");
toast("未找到搜索框");
return false;
}
searchInput.setText(contactName);
sleep(2000); // 等待搜索结果
let contactItem = text(contactName).findOne(3000);
if (!contactItem) {
logger.log(`未找到联系人:'${contactName}'`, "ERROR");
toast("未找到联系人");
// 清除搜索框内容并退出
id("com.tencent.mm:id/fsl").click(); // 返回到主界面
return false;
}
contactItem.click();
sleep(2000); // 等待进入聊天界面
// 4. 输入消息并发送
logger.log(`输入消息:'${message}' 并发送...`);
let messageInput = id("com.tencent.mm:id/fsk").findOne(3000) || className("android.widget.EditText").desc("输入").findOne(3000);
if (!messageInput) {
logger.log("未找到消息输入框。", "ERROR");
toast("未找到消息框");
return false;
}
messageInput.setText(message);
sleep(500);
let sendBtn = id("com.tencent.mm:id/fss").findOne(2000) || text("发送").findOne(2000);
if (!sendBtn) {
logger.log("未找到发送按钮。", "ERROR");
toast("未找到发送按钮");
return false;
}
sendBtn.click();
sleep(1000);
logger.log("消息发送成功!", "INFO");
toast("消息发送成功");
back(); // 返回到主界面
sleep(500);
home(); // 返回主页
return true;
}
module.exports = { sendWechatMessage };
3.5 main.js 主入口脚本
// main.js
const wechatSender = require('./modules/wechatSender.js');
const logger = require('./modules/logger.js');
logger.log("脚本启动:准备发送微信消息...");
// 示例:发送消息给文件传输助手
wechatSender.sendWechatMessage("文件传输助手", "Hello, Auto.js! 这是我的第十六章节示例。");
// 示例:发送消息给特定好友 (请替换为你的好友名称)
// wechatSender.sendWechatMessage("你的好友名称", "测试消息:今天的签到任务已完成。");
logger.log("所有微信消息发送任务已完成。");
3.6 运行效果与分析
- 运行
main.js脚本,它将自动启动微信。 - 脚本会模拟点击搜索按钮,输入联系人名称,选择联系人进入聊天界面。
- 然后,脚本会自动输入预设的消息内容,并点击发送按钮。
- 整个过程通过日志进行详细记录,方便追踪和调试。
- 这个实战项目展示了如何结合
app模块的启动功能和 UI 自动化来与复杂的第三方应用(如微信)进行交互。需要注意的是,微信的 UI 控件 ID 和文本可能会随版本更新而变化,因此脚本需要定期维护。
四、常见问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 应用无法启动/闪退 | 检查包名/类名是否正确;检查权限是否完整;应用版本兼容性;是否开启无障碍服务。 |
| 控件找不到/点击无效 | 使用 UI Automator Viewer 或 Auto.js Pro 布局分析器重新定位控件;增加等待时间;尝试多种查找方式(text/desc/id/className)。 |
| Intent 调用失败/无响应 | 检查 Intent Action/Category/Data 格式是否正确;添加 FLAG_ACTIVITY_NEW_TASK;检查目标应用是否支持此 Intent。 |
| 文件访问权限问题 | Android 6.0+ 需要动态申请存储权限;Android 11+ (API 30+) 需要适配分区存储 (Scoped Storage)。 |
| 后台操作被系统杀死 | 考虑使用 Auto.js 的前台服务或定时任务,确保脚本在后台稳定运行。 |
| 应用 UI 更新导致脚本失效 | 定期更新脚本以适配应用 UI 变化;尝试使用更通用的查找方式(如模糊查找、多条件查找)。 |
| 第三方应用反自动化/检测 | 增加随机延时;模拟更真实的用户操作;使用图像识别/OCR 规避控件检测;避免频繁操作。 |
| 数据共享/文件传输失败 | 检查文件路径和权限;对于 Android 7.0+ 考虑使用 FileProvider。 |
shell 命令无权限执行 | 确保设备已 Root,并授予 Auto.js Root 权限。 |
| 耗时操作阻塞主线程 | 将耗时操作放入子线程 (threads.start()),避免 UI 卡顿或 ANR。 |
五、性能优化建议
- 精确定位控件:优先使用
id或text精确查找控件,减少遍历开销。在无法精确查找时,再考虑desc、className或结合bounds。 - 合理设置等待时间:使用
waitFor...()函数或sleep()时,根据实际情况设置合适的等待时间,避免过长或过短导致效率低下或操作失败。 - 避免不必要的 UI 操作:能通过 Intent 或 API 完成的功能,优先使用 Intent 或 API,减少 UI 自动化操作。
- 分批处理:如果需要处理大量数据或重复操作,考虑分批进行,减少单次操作的负担。
- 日志分级:在生产环境中,降低日志输出等级,避免频繁的日志输出影响性能。
- 资源释放:确保在脚本结束后及时释放资源(如关闭文件、数据库连接、停止线程)。
- 异常捕获与重试:对关键操作进行异常捕获,并增加重试机制,提高脚本的健壮性。
- 多线程优化:将与 UI 自动化无关的耗时操作(如网络请求、数据处理)放入子线程执行,避免阻塞主线程。
- UI 适配:针对不同分辨率和 Android 版本进行兼容性测试和适配,提高脚本的通用性。
- 模拟真实用户行为:引入随机延时、滑动速度变化等,使操作更接近真人,降低被反自动化检测的风险。
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!