让悬浮窗能够被拖动可以使用w.setAdjustEnabled(true);来实现。但是这样会给悬浮窗上面加三个大按钮,很不好看。有没有什么方法可以不使用这个w.setAdjustEnabled(true);来让悬浮窗可以被拖动?我看别人写的脚本可以不使用这个函数而且还能被拖动,而且可以按住窗体的任何位置来进行拖动,非常方便。
我查看了文档,events.observeTouch()这东西需要 root 权限,我没开启所i没法使用,因此我想不到可以实现此功能的方法了。
那么有没有可以判断手机是否息屏的方法?再就是如何唤醒息屏和主动息屏?如果息屏了那脚本还能检测到息屏并运行功能吗?脚本保活步骤都做了
我没有root权限,我想知道如何模拟按下手机的多任务按钮。
另一个问题是在没有root权限的情况下能否使用shell来强制终止一个应用?注意我指的不是那种切换到此应用的设置页面然后点击强制停止按钮的那种方法。可以的话给我一个例子。
回答
要实现不依赖 setAdjustEnabled(true) 且无需 Root 权限的任意位置拖动悬浮窗,可以通过为悬浮窗的根布局绑定原生触摸监听器 setOnTouchListener,记录按下时的屏幕坐标与窗口坐标,在移动时计算偏移量并调用 setPosition() 更新位置。
以下是完整可运行的示例代码(已适配 Rhino 1.7.15 引擎,未使用箭头函数与默认参数):
// 文档地址 https://docs.hamibot.com/
var w = floaty.window(
<frame id="root" bg="#44ffcc00" w="200" h="100">
<text text="按住任意位置拖动" gravity="center" textColor="#000000" />
</frame>
);
var MotionEvent = android.view.MotionEvent;
var initialX = 0;
var initialY = 0;
var windowX = 0;
var windowY = 0;
w.root.setOnTouchListener(function(view, event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = event.getRawX();
initialY = event.getRawY();
windowX = w.getX();
windowY = w.getY();
return true;
case MotionEvent.ACTION_MOVE:
var newX = windowX + (event.getRawX() - initialX);
var newY = windowY + (event.getRawY() - initialY);
w.setPosition(newX, newY);
return true;
case MotionEvent.ACTION_UP:
return true;
}
return false;
});
// 保持脚本运行,防止悬浮窗随脚本结束自动关闭
setInterval(function() {}, 1000);
核心原理说明
- 触摸事件拦截:
setOnTouchListener直接绑定在根布局root上,拦截整个窗口的触摸事件,无需 Root 权限。 - 坐标计算:
ACTION_DOWN时记录手指的绝对屏幕坐标getRawX()/getRawY()和当前窗口左上角坐标w.getX()/w.getY()。ACTION_MOVE时计算手指移动的差值,叠加到初始窗口坐标上,通过w.setPosition()实时更新位置。
- 事件消费:返回
true表示当前监听器已消费该触摸事件,悬浮窗会跟随手指移动,不会将事件传递给下层应用。
注意事项
- 脚本保活:悬浮窗的生命周期与脚本绑定,必须使用
setInterval或events.on("exit", ...)等机制保持脚本运行,否则窗口会立即销毁。 - 内部控件点击冲突:由于根布局消费了
ACTION_DOWN事件,窗口内的按钮或文本默认无法触发点击。如果窗口内需要保留可点击控件,建议将拖动监听器绑定到指定的“拖动条”或“标题栏”布局上,而不是整个根布局。 - 性能优化:
setPosition()调用频率较高,Rhino 环境下表现稳定。若拖动时出现轻微卡顿,可限制刷新频率(例如记录上次更新时间,间隔 16ms 再调用setPosition),但通常无需额外处理。 - 兼容性:代码严格遵循 Rhino 1.7.15 语法规范,未使用 ES6 特性,可在 Hamibot 环境中直接运行。
你可以直接复制上述代码到 Hamibot 脚本编辑器中运行测试。如需调整窗口大小、背景透明度或添加内部交互控件,只需修改 XML 布局部分即可。
针对你的三个问题,以下是直接可用的方法与底层机制说明:
1. 判断手机是否息屏
使用 device.isScreenOn() 即可。返回 true 表示亮屏,false 表示息屏。
注意:部分机型的“息屏时钟/AOD”虽然屏幕有微光,但系统判定为不可交互状态,该函数同样会返回 false。
2. 唤醒息屏与主动息屏
- 唤醒屏幕:
device.wakeUp():强制唤醒设备 CPU 与屏幕。device.wakeUpIfNeeded():仅在屏幕未点亮时唤醒,已亮屏则不执行,更节省资源。
- 主动息屏:
Android 系统限制无障碍服务直接关闭屏幕。常规做法是模拟电源键:
shell("input keyevent 26", true);
该命令需要 Root 权限或 Hamibot 已授予 Shell 权限。若无权限,只能调用device.cancelKeepingAwake()取消常亮,等待系统超时自动息屏。
3. 息屏后脚本能否检测并运行功能?
脚本保活 ≠ CPU 不休眠。 即使你做了 setInterval 保活,Android 的 Doze 休眠机制仍会在息屏后限制后台活动:
- UI 自动化操作(点击、滑动、找色、识图):息屏时底层 Surface 不渲染,必定失败。
- 纯逻辑/网络请求:可运行,但会被系统降频或延迟执行,定时任务可能出现较大偏差。
- 稳定后台运行方案:必须配合
device.keepScreenDim()。该函数会持有 WakeLock 保持 CPU 唤醒,同时允许屏幕变暗省电,是 Hamibot 官方推荐的定时/后台脚本唤醒方案。
完整示例代码(已适配 Rhino 1.7.15)
// 文档地址 https://docs.hamibot.com/
// 1. 判断并唤醒屏幕
if (!device.isScreenOn()) {
console.log("当前息屏,执行唤醒");
device.wakeUpIfNeeded();
sleep(800); // 等待系统完成亮屏与解锁动画
} else {
console.log("当前已亮屏");
}
// 2. 后台任务防休眠设置(允许屏幕变暗,但保持 CPU 运行)
device.keepScreenDim();
// 3. 你的核心逻辑
function runTask() {
console.log("任务执行中,屏幕状态: " + device.isScreenOn());
// 注意:息屏状态下请勿执行 click(), swipe(), images.findImage() 等 UI 操作
}
// 定时执行示例
setInterval(function() {
runTask();
}, 5000);
// 脚本退出时释放常亮锁,避免耗电
events.on("exit", function() {
device.cancelKeepingAwake();
console.log("已取消屏幕常亮");
});
// 保持脚本运行
setInterval(function() {}, 1000);
关键注意事项
- UI 操作必须亮屏:任何依赖屏幕渲染的自动化指令,执行前务必用
device.isScreenOn()校验,或提前调用device.wakeUp()。 - 电量与发热控制:长时间后台运行务必使用
device.keepScreenDim()而非device.keepScreenOn(),前者允许屏幕变暗,大幅降低功耗。 - 电池优化白名单:前往手机系统设置 → 电池 → 忽略电池优化,将 Hamibot 加入白名单。否则系统仍可能在长时间息屏后强制冻结进程。
- 主动息屏权限:若你的设备无 Root,
shell("input keyevent 26", true)会执行失败。此时建议依赖系统自动息屏,或通过device.cancelKeepingAwake()配合较短的系统休眠时间实现。
按上述结构配置后,你的脚本可在息屏/暗屏状态下稳定执行非 UI 逻辑,并在需要 UI 交互时自动唤醒屏幕。如有具体业务场景(如定时打卡、后台监控),可进一步说明,我会提供针对性优化方案。