1. AutoJS悬浮窗开发实战指南
作为一名长期从事Android自动化开发的工程师,我深知悬浮窗功能在实际项目中的重要性。悬浮窗不仅能够提供便捷的操作入口,还能实时展示关键信息。今天我将分享一个基于AutoJS的悬浮窗实现方案,这个方案已经在我多个项目中得到验证,虽然存在一些不足,但对于大多数场景已经足够使用。
1.1 为什么选择AutoJS实现悬浮窗
AutoJS作为一款Android平台上的JavaScript自动化工具,其优势在于:
- 无需Root权限即可实现丰富的自动化操作
- 基于JavaScript语法,学习曲线平缓
- 内置了完善的悬浮窗API,开发效率高
- 社区活跃,遇到问题容易找到解决方案
但需要注意的是,AutoJS的悬浮窗实现存在内存管理上的缺陷。根据我的实测,频繁创建和销毁悬浮窗会导致内存泄漏,这也是为什么我在项目中采用单例模式管理悬浮窗的原因。
2. 项目结构与核心代码解析
2.1 项目目录结构
一个标准的AutoJS悬浮窗项目通常包含以下文件:
code复制项目根目录/
├── ui/ # UI布局文件目录
│ └── main.xml # 主界面布局文件
├── floaty.js # 悬浮窗主逻辑文件
├── project.json # 项目配置文件
└── logo.png # 悬浮窗图标
2.2 项目配置文件详解
project.json是AutoJS项目的入口配置文件,关键配置项如下:
json复制{
"name": "CSDN辅助",
"main": "floaty.js",
"launchConfig": {
"hideLogs": false,
"displaySplash": true
},
"ignore": ["build"],
"packageName": "com.py.csdn",
"versionName": "1.0.0",
"versionCode": 1
}
提示:
hideLogs设置为false可以在开发阶段显示更多调试信息,发布时应改为true以提升性能。
2.3 悬浮窗核心实现
2.3.1 权限检查与初始化
在创建悬浮窗前,必须检查并获取悬浮窗权限:
javascript复制// 检查悬浮窗权限
function checkFloatyPermission() {
return android.provider.Settings.canDrawOverlays(context);
}
let floatyThread = threads.start(function() {
if (!checkFloatyPermission()) {
toast("请开启悬浮窗权限");
app.startActivity({
action: "android.settings.action.MANAGE_OVERLAY_PERMISSION",
data: "package:" + context.getPackageName()
});
}
});
floatyThread.join();
2.3.2 悬浮窗UI构建
悬浮窗采用XML+JS混合开发模式,核心UI结构如下:
xml复制<horizontal>
<vertical id="menuLeft" visibility="gone">
<button id="btnLeftStart" text="启动" w="45" h="45" />
<button id="btnLeftConfig" text="配置" w="45" h="45" marginTop="8" />
<button id="btnLeftConsole" text="打印" w="45" h="45" marginTop="8" />
<button id="btnLeftExit" text="退出" w="45" h="45" marginTop="8" />
</vertical>
<img id="mainImg" src="file:///storage/emulated/0/com.py.test/logo.png" w="40" h="40" />
<vertical id="menuRight" visibility="gone">
<!-- 右侧菜单按钮,结构与左侧相同 -->
</vertical>
</horizontal>
2.3.3 动态样式处理
通过代码动态设置按钮样式,提升UI一致性:
javascript复制// 创建圆角按钮背景
function createRoundButtonDrawable(normalColorStr, pressedColorStr, radiusDp) {
let normalColor = android.graphics.Color.parseColor(normalColorStr);
let pressedColor = android.graphics.Color.parseColor(pressedColorStr);
let normalDrawable = new android.graphics.drawable.GradientDrawable();
normalDrawable.setColor(normalColor);
normalDrawable.setCornerRadius(dpToPx(radiusDp));
let pressedDrawable = new android.graphics.drawable.GradientDrawable();
pressedDrawable.setColor(pressedColor);
pressedDrawable.setCornerRadius(dpToPx(radiusDp));
let stateList = new android.graphics.drawable.StateListDrawable();
stateList.addState([android.R.attr.state_pressed], pressedDrawable);
stateList.addState([], normalDrawable);
return stateList;
}
// 应用样式到所有按钮
[floatWindow.btnLeftStart, floatWindow.btnLeftConfig, /* 其他按钮 */].forEach(btn => {
btn.setTextColor(colors.parseColor("#FFFFFFFF"));
btn.setTextSize(12);
btn.setPadding(0, 8, 0, 8);
btn.setElevation(4);
});
3. 悬浮窗交互逻辑实现
3.1 位置管理与自适应
悬浮窗需要根据设备方向和位置自动调整:
javascript复制// 设备宽度和高度
let deviceWidth = device.width;
let deviceHeight = device.height;
// 更新悬浮窗位置
function updateFloatWindowPosition() {
if (hasEnterGame) {
rightX = deviceHeight - 70;
positionY = deviceWidth / 3 + 70;
} else {
rightX = deviceWidth - 70;
positionY = (deviceHeight - 120) / 2 + 250;
}
floatWindow.setPosition(rightX, positionY);
}
// 每500ms检查一次状态
setInterval(() => {
hasEnterGame = currentPackage() == currentGamePackage;
if (hasEnterGame != hasLastEnterGame) {
updateFloatWindowPosition();
hasLastEnterGame = hasEnterGame;
}
}, 500);
3.2 触摸事件处理
实现悬浮窗的拖动和点击交互:
javascript复制floatWindow.mainImg.setOnTouchListener(function(view, event) {
switch (event.getAction()) {
case event.ACTION_DOWN:
lastTouch.x = event.getRawX();
lastTouch.y = event.getRawY();
isDragging = false;
break;
case event.ACTION_MOVE:
var dx = event.getRawX() - lastTouch.x;
var dy = event.getRawY() - lastTouch.y;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
isDragging = true;
updatePosition(event.getRawX(), event.getRawY());
}
break;
case event.ACTION_UP:
if (!isDragging) {
handleClickEvent(event);
}
break;
}
return true;
});
3.3 功能按钮实现
四个核心功能按钮的实现逻辑:
javascript复制// 启动/停止按钮
floatWindow.btnLeftStart.click(function() {
if (!isRunning) {
startLogic();
floatWindow.btnLeftStart.setText("停止");
isRunning = true;
} else {
stopStartLogicThread();
floatWindow.btnLeftStart.setText("启动");
isRunning = false;
}
});
// 配置按钮
floatWindow.btnLeftConfig.click(function() {
switchScriptPage();
startMainUi();
});
// 控制台按钮
floatWindow.btnLeftConsole.click(function() {
if (!hasShowConsole) {
console.show();
hasShowConsole = true;
} else {
console.hide();
hasShowConsole = false;
}
});
// 退出按钮
floatWindow.btnLeftExit.click(function() {
forceStopScriptAndCleanMemory();
});
4. 性能优化与问题排查
4.1 内存管理技巧
由于AutoJS悬浮窗存在内存泄漏问题,需要特别注意:
javascript复制// 强制停止脚本并清理内存
function forceStopScriptAndCleanMemory() {
floaty.closeAll();
let rt = java.lang.Runtime.getRuntime();
rt.gc();
}
// 避免频繁创建销毁悬浮窗
let floatWindow; // 全局单例
function setFloaty() {
if (!floatWindow) {
floatWindow = floaty.window(/* ... */);
}
// ...
}
4.2 常见问题排查
-
悬浮窗不显示
- 检查是否已授予悬浮窗权限
- 确保没有其他应用遮挡
- 验证代码是否在UI线程执行
-
按钮点击无响应
- 检查click事件绑定是否正确
- 确认没有其他元素遮挡
- 查看控制台是否有错误输出
-
内存占用过高
- 避免频繁创建销毁悬浮窗
- 及时回收不再使用的资源
- 定期调用gc()手动触发垃圾回收
4.3 横竖屏适配方案
针对游戏等横屏场景的特殊处理:
javascript复制// 判断是否进入横屏应用
let currentGamePackage = "com.cyjh.mobileanjian";
let hasEnterGame = false;
setInterval(() => {
hasEnterGame = currentPackage() == currentGamePackage;
if (hasEnterGame) {
// 横屏特殊处理
rightX = device.height - 70;
positionY = device.width / 3 + 70;
} else {
// 竖屏默认位置
rightX = device.width - 70;
positionY = (device.height - 120) / 2 + 250;
}
floatWindow.setPosition(rightX, positionY);
}, 500);
5. 进阶优化方向
虽然当前实现已经能满足基本需求,但仍有改进空间:
-
自定义控制台集成
- 实现日志分级显示
- 添加过滤和搜索功能
- 支持日志持久化存储
-
主题与样式可配置化
- 通过配置文件定义颜色方案
- 支持动态切换主题
- 添加动画效果提升用户体验
-
性能监控面板
- 实时显示CPU/内存占用
- 脚本执行耗时统计
- 异常自动捕获与报告
-
插件化架构
- 核心功能与业务逻辑解耦
- 支持动态加载功能模块
- 提供扩展API供二次开发
在实际项目中,我建议根据具体需求选择优化方向。对于简单的辅助工具,当前实现已经足够;如果是复杂的商业项目,则需要考虑更完善的架构设计。