目录
上篇文章,没看的可以先看下。
autojs websocket后台保活
吞包?
上篇记录的时候,出现了“吞包”的现象,在找到有类似现象后就想当然的认为我也是遇到一样的情况。
回顾下日志的记录,
日志示例:
//正常
send clint-ping
rec server-pong
//发包被吞
send clint-ping
send clint-ping
send clint-ping
...
rec server-ping //服务端推送
send clint-pong
rec server-pong
rec server-pong
rec server-pong
这个现象是怎么出现的呢
setTimeout 锁屏不运行
起初使用setTimeout去保持客户端心跳,但锁屏一段时间就停止运行,我已经预感到休眠对setTimeout是有影响的。因而换成sleep进行测试。
使用sleep 代替 setTimeout
伪代码
let t = (new Date()).getTime()
do{
if((上次发送时间+5秒)>t && (new Date()).getTime() - t>5000){
t= (new Date()).getTime()
send_ping()
}
sleep(50)
}while(1)
对安卓sleep的误解,使用 sleep(50)
这个时候我已经找到文章
如果是笔记本的话在电池的电源管理项中可以配置。
这里提到的是不准,而非停止或者延迟,我猜是不是计时器频率变低了。
所以我 使用了一个较短的 sleep(50),甚至还尝试了一个函数
function real_sleep(ms){
let t = (new Date()).getTime()
let ft = 1000
console.time(t+'real_sleep(100)')
sleep(ft)
console.timeEnd(t+'real_sleep(100)')
let nt = (new Date()).getTime()
console.log("nt-t "+(nt-t))
let rms = ((ms/ (nt-t))*ft).toFixed(0)-ft
if(rms>0){
console.log('rms '+ rms)
console.time(t+'real_sleep(rms)')
sleep(rms)
console.timeEnd(t+'real_sleep(rms)')
}
}
结果呢
一切看起来都是对的,只是有点"吞包"。
原因 - 神奇的uptimeMillis
手机锁屏后,并经过一段时间,手机进入休眠状态。
我们的脚本也是,我们的socket还保持着链接、脚本和线程都正常运行。
直到我们运行了sleep、setTimeOut,整个线程就进入了休眠状态。
这里简单说明下情况,安卓里面的sleep,Thread.sleep(3000),Object,wait(),Timer,Handler.postDelayed,都是基于一个时间—— uptimeMillis()。
而这个时间最大的特性就是,不算休眠时间!!!
uptimeMillis() is counted in milliseconds since the system was booted. This clock stops when the system enters deep sleep (CPU off, display dark, device waiting for external input), but is not affected by clock scaling, idle, or other power saving mechanisms. This is the basis for most interval timing such as Thread.sleep(millls), Object.wait(millis), and System.nanoTime().
实际发生的事情
- 手机锁屏后,并经过一段时间,手机进入休眠状态。
- uptimeMillis停止累积,uptimeMillis()-now()>50迟迟不会满足,sleep(50)直接休眠
- 在脚本收到服务器的推送,cpu 会被激活,退出休眠状态,uptimeMillis继续累积
- sleep(50)后开始运行
- 在再次收到服务器包而更新上次发生实际之间,会每隔50毫秒连续send_ping。所以出现了"吞包"后狂发的假象。
- 随后再次sleep(50),很快再次进入休眠
回看日志
//正常 非锁屏或充电情况
send clint-ping
rec server-pong
//发包“被吞” 非锁屏或充电情况
send clint-pong
sleep(50) // 进入休眠
...
//服务端推送,cpu 会被激活,退出休眠状态
rec server-ping
send clint-pong
sleep(50)
send clint-pong
sleep(50)
send clint-pong
sleep(50)
// 更新最后一次发生时间
rec server-pong
rec server-pong
rec server-pong
...
源码看 autojs 中的sleep和setTimeOUt等延时
sleep
这里的sleep,基本就是安卓里面的sleep
global.sleep = function(t) {
if(ui.isUiThread()){
throw new Error("不能在ui线程执行阻塞操作,请使用setTimeout代替");
}
runtime.sleep(t);
}
线程中的sleep
public static void sleep(long millis) throws InterruptedException {
Thread.sleep(millis);
}
setTimeOUt
文档原文:
Auto.js 中的计时器函数实现了与 Web 浏览器提供的定时器类似的 API,除了它使用了一个不同的内部实现,它是基于 Android Looper-Handler消息循环机制构建的。其实现机制与Node.js比较相似。
源码
public int setTimeout(final Object callback, final long delay, final Object... args) {
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = () -> {
callFunction(callback, null, args);
mHandlerCallbacks.remove(id);
};
mHandlerCallbacks.put(id, r);
postDelayed(r, delay);
return id;
}
engines.execAutoFile(path[, config])
engines脚本引擎的延时任务,使用的java也是sleep
源码
protected Object doExecution(final ScriptEngine engine) {
engine.setTag(ScriptEngine.TAG_SOURCE, getSource());
getListener().onStart(this);
long delay = getConfig().getDelay();
sleep(delay);
final LoopBasedJavaScriptEngine javaScriptEngine = (LoopBasedJavaScriptEngine) engine;
final long interval = getConfig().getInterval();
javaScriptEngine.getRuntime().loopers.setMainLooperQuitHandler(new Loopers.LooperQuitHandler() {
long times = getConfig().getLoopTimes() == 0 ? Integer.MAX_VALUE : getConfig().getLoopTimes();
@Override
public boolean shouldQuit() {
times--;
if (times > 0) {
sleep(interval);
javaScriptEngine.execute(getSource());
return false;
}
javaScriptEngine.getRuntime().loopers.setMainLooperQuitHandler(null);
return true;
}
});
javaScriptEngine.execute(getSource());
return null;
}
结论
安卓机制导致在autojs 内,无法在休眠状态执行延时任务。
在这个条件下还去维持客户端心跳保活,几乎是不可以能的。
正因为如此,我在后续的修改中把代码修改为,锁屏不充电时,服务端保活。
let t = (new Date()).getTime()
do{
if( (device.isScreenOn() || device.isCharging())
&& (上次发送时间+5秒)>t && (new Date()).getTime() - t>4500){
t= (new Date()).getTime()
send_ping()
}
sleep(500)
}while(1)
关于 AlarmManger
暂时没能力测,有时间测试后另外更新
参考文章
安卓延迟执行
安卓 SystemClock