异步环境修复

This commit is contained in:
aiselp
2023-06-10 12:48:01 +08:00
parent 07cacc7909
commit 59c0c73d99
10 changed files with 513 additions and 539 deletions

View File

@@ -1,211 +0,0 @@
package org.autojs.autojs.core.looper;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import androidx.annotation.Nullable;
import org.autojs.autojs.lang.ThreadCompat;
import org.autojs.autojs.rhino.AutoJsContext;
import org.autojs.autojs.runtime.ScriptRuntime;
import org.autojs.autojs.runtime.api.Threads;
import org.autojs.autojs.runtime.api.Timers;
import org.autojs.autojs.runtime.exception.ScriptInterruptedException;
import org.mozilla.javascript.Context;
import java.util.HashSet;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by Stardust on 2017/7/29.
*/
@SuppressWarnings("ConstantConditions")
public class Loopers implements MessageQueue.IdleHandler {
private static final String LOG_TAG = "Loopers";
public interface LooperQuitHandler {
boolean shouldQuit();
}
private static final Runnable EMPTY_RUNNABLE = () -> {
};
private final ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<>() {
@Nullable
@Override
protected Boolean initialValue() {
return Looper.myLooper() == Looper.getMainLooper();
}
};
private final ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<>() {
@Nullable
@Override
protected HashSet<Integer> initialValue() {
return new HashSet<>();
}
};
private final ThreadLocal<Integer> maxWaitId = new ThreadLocal<>() {
@Nullable
@Override
protected Integer initialValue() {
return 0;
}
};
private final ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHandlers = new ThreadLocal<>();
private volatile Looper mServantLooper;
private final Timers mTimers;
private LooperQuitHandler mMainLooperQuitHandler;
private final Handler mMainHandler;
private final Looper mMainLooper;
private final Threads mThreads;
private final MessageQueue mMainMessageQueue;
public Loopers(ScriptRuntime runtime) {
mTimers = runtime.timers;
mThreads = runtime.threads;
prepare();
mMainLooper = Looper.myLooper();
mMainHandler = new Handler();
mMainMessageQueue = Looper.myQueue();
}
public Looper getMainLooper() {
return mMainLooper;
}
public void addLooperQuitHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
if (handlers == null) {
handlers = new CopyOnWriteArrayList<>();
looperQuitHandlers.set(handlers);
}
handlers.add(handler);
}
public boolean removeLooperQuitHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
return handlers != null && handlers.remove(handler);
}
private boolean shouldQuitLooper() {
if (Thread.currentThread().isInterrupted()) {
return true;
}
if (mTimers.hasPendingCallbacks()) {
return false;
}
if (waitWhenIdle.get() || !waitIds.get().isEmpty()) {
return false;
}
if (((AutoJsContext) Context.getCurrentContext()).hasPendingContinuation()) {
return false;
}
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
if (handlers == null) {
return true;
}
for (LooperQuitHandler handler : handlers) {
if (!handler.shouldQuit()) {
return false;
}
}
return true;
}
private void initServantThread() {
final Object lock = Loopers.this;
new ThreadCompat(() -> {
Looper.prepare();
mServantLooper = Looper.myLooper();
synchronized (lock) {
lock.notifyAll();
}
Looper.loop();
}).start();
}
public Looper getServantLooper() {
if (mServantLooper == null) {
initServantThread();
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
throw new ScriptInterruptedException();
}
}
}
return mServantLooper;
}
private void quitServantLooper() {
if (mServantLooper == null)
return;
mServantLooper.quit();
}
public int waitWhenIdle() {
int id = maxWaitId.get();
Log.d(LOG_TAG, "waitWhenIdle: " + id);
maxWaitId.set(id + 1);
waitIds.get().add(id);
return id;
}
public void doNotWaitWhenIdle(int waitId) {
Log.d(LOG_TAG, "doNotWaitWhenIdle: " + waitId);
waitIds.get().remove(waitId);
}
public void waitWhenIdle(boolean b) {
waitWhenIdle.set(b);
}
public void recycle() {
quitServantLooper();
mMainMessageQueue.removeIdleHandler(this);
}
public void setMainLooperQuitHandler(LooperQuitHandler mainLooperQuitHandler) {
mMainLooperQuitHandler = mainLooperQuitHandler;
}
@Override
public boolean queueIdle() {
Looper l = Looper.myLooper();
if (l == null)
return true;
if (l == mMainLooper) {
Log.d(LOG_TAG, "main looper queueIdle");
if (shouldQuitLooper() && !mThreads.hasRunningThreads() &&
mMainLooperQuitHandler != null && mMainLooperQuitHandler.shouldQuit()) {
Log.d(LOG_TAG, "main looper quit");
l.quit();
}
} else {
Log.d(LOG_TAG, "looper queueIdle: " + l);
if (shouldQuitLooper()) {
l.quit();
}
}
return true;
}
public void prepare() {
if (Looper.myLooper() == null)
LooperHelper.prepare();
Looper.myQueue().addIdleHandler(this);
}
public void notifyThreadExit(TimerThread thread) {
Log.d(LOG_TAG, "notifyThreadExit: " + thread);
//当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程),
//此时通过向主线程发送一个空的Runnable主线程执行完这个Runnable后会触发IdleHandler从而检查自身是否退出
mMainHandler.post(EMPTY_RUNNABLE);
}
}

View File

@@ -0,0 +1,182 @@
package org.autojs.autojs.core.looper
import android.os.Looper
import android.os.MessageQueue
import android.util.Log
import org.autojs.autojs.lang.ThreadCompat
import org.autojs.autojs.rhino.AutoJsContext
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.mozilla.javascript.Context
import java.util.concurrent.ConcurrentLinkedQueue
/**
* Created by Stardust on 2017/7/29.
*/
/**
* update by aiselp on 2023/6/4
* 调整内容:
* 使此类只负责单loop线程生命周期管理移除繁琐的调用链
* 调整timer由此类创建
* 通过向此类添加AsyncTask以监听线程退出事件
*/
class Loopers(val runtime: ScriptRuntime) {
@Deprecated("使用AsyncTask代替")
interface LooperQuitHandler {
fun shouldQuit(): Boolean
}
open class AsyncTask(private val describe: String) {
private val allBind = ConcurrentLinkedQueue<Loopers>()
var isEnd: Boolean = false
private set
//线程即将退出时调用返回true阻止线程退出只要有一个task返回true线程就不会退出
open fun onFinish(loopers: Loopers): Boolean {
return true
}
fun end() {
isEnd = true
}
//线程正在退出,这里应该结束任务的执行,回收资源
open fun onStop(loopers: Loopers) {}
override fun toString(): String {
return "AsyncTask: $describe"
}
}
private var waitWhenIdle: Boolean
@Volatile
private var mServantLooper: Looper? = null
private var mMainLooperQuitHandler: LooperQuitHandler? = null
private val allTasks = ConcurrentLinkedQueue<AsyncTask>()
val mTimer: Timer
init {
prepare()
val myLooper = Looper.myLooper()!!
mTimer = Timer(runtime, myLooper)
waitWhenIdle = myLooper == Looper.getMainLooper()
}
fun createAndAddAsyncTask(describe: String): AsyncTask {
val task = AsyncTask(describe)
allTasks.add(task)
return task
}
fun addAsyncTask(task: AsyncTask) {
allTasks.add(task)
}
fun removeAsyncTask(task: AsyncTask) {
allTasks.remove(task)
mTimer.post(EMPTY_RUNNABLE)
}
private fun checkTask(): Boolean {
allTasks.removeAll(allTasks.filter { it.isEnd }.toSet())
for (task in allTasks) {
if (task.onFinish(this)) return true
}
return false
}
private fun shouldQuitLooper(): Boolean {
if (Thread.currentThread().isInterrupted) return true
if (mTimer.hasPendingCallbacks()) return false
//检查是否有运行中的线程
if (checkTask()) return false
if (waitWhenIdle) return false
if ((Context.getCurrentContext() as AutoJsContext).hasPendingContinuation()) {
return false
}
return true
}
private fun initServantThread() {
ThreadCompat {
Looper.prepare()
val lock = this@Loopers as Object
mServantLooper = Looper.myLooper()
synchronized(lock) { lock.notifyAll() }
Looper.loop()
}.start()
}
val servantLooper: Looper
get() {
if (mServantLooper == null) {
initServantThread()
val lock = this as Object
synchronized(lock) {
try {
lock.wait()
} catch (e: InterruptedException) {
throw ScriptInterruptedException(e)
}
}
}
return mServantLooper!!
}
@Deprecated("使用AsyncTask代替")
fun waitWhenIdle(b: Boolean) {
waitWhenIdle = b
}
fun recycle() {
Log.d(LOG_TAG, "recycle")
for (task in allTasks.filter { !it.isEnd }) {
try {
task.onStop(this)
} catch (e: Exception) {
Log.w(LOG_TAG, e)
}
}
mServantLooper?.quit()
}
@Deprecated("使用AsyncTask代替")
fun setMainLooperQuitHandler(mainLooperQuitHandler: LooperQuitHandler?) {
mMainLooperQuitHandler = mainLooperQuitHandler
}
private fun prepare() {
if (Looper.myLooper() == null) LooperHelper.prepare()
Looper.myQueue().addIdleHandler(MessageQueue.IdleHandler {
if (this == runtime.loopers) {
Log.d(LOG_TAG, "main looper queueIdle")
if (shouldQuitLooper() &&
mMainLooperQuitHandler != null &&
mMainLooperQuitHandler!!.shouldQuit()
) {
Log.d(LOG_TAG, "main looper quit")
Looper.myLooper()!!.quitSafely()
}
}else {
Log.d(LOG_TAG, "looper queueIdle $this")
if (shouldQuitLooper()) {
Log.d(LOG_TAG, "looper quit $this")
Looper.myLooper()!!.quitSafely()
}
}
return@IdleHandler true
})
}
fun notifyThreadExit(thread: TimerThread) {
Log.d(LOG_TAG, "notifyThreadExit: $thread")
//当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程),
//此时通过向主线程发送一个空的Runnable主线程执行完这个Runnable后会触发IdleHandler从而检查自身是否退出
//mHandler.post(EMPTY_RUNNABLE)
}
companion object {
private const val LOG_TAG = "Loopers"
private val EMPTY_RUNNABLE = Runnable {}
}
}

View File

@@ -1,131 +0,0 @@
package org.autojs.autojs.core.looper;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.SparseArray;
import org.autojs.autojs.runtime.ScriptRuntime;
import org.autojs.autojs.concurrent.VolatileBox;
/**
* Created by Stardust on 2017/12/27.
*/
public class Timer {
private final SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>();
private int mCallbackMaxId = 0;
private final ScriptRuntime mRuntime;
private final Handler mHandler;
private long mMaxCallbackUptimeMillis = 0;
private final VolatileBox<Long> mMaxCallbackMillisForAllThread;
public Timer(ScriptRuntime runtime, VolatileBox<Long> maxCallbackMillisForAllThread) {
mRuntime = runtime;
mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread;
mHandler = new Handler();
}
public Timer(ScriptRuntime runtime, VolatileBox<Long> maxCallbackMillisForAllThread, Looper looper) {
mRuntime = runtime;
mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread;
mHandler = new Handler(looper);
}
public int setTimeout(final Object callback, final long delay, final Object... args) {
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = () -> {
callFunction(callback, args);
mHandlerCallbacks.remove(id);
};
mHandlerCallbacks.put(id, r);
postDelayed(r, delay);
return id;
}
private void callFunction(Object callback, Object[] args) {
if(Looper.myLooper() == Looper.getMainLooper()){
try {
mRuntime.bridges.callFunction(callback, null, args);
}catch (Exception e){
mRuntime.exit(e);
}
}else {
mRuntime.bridges.callFunction(callback, null, args);
}
}
public boolean clearTimeout(int id) {
return clearCallback(id);
}
public int setInterval(final Object listener, final long interval, final Object... args) {
mCallbackMaxId++;
final int id = mCallbackMaxId;
final Runnable r = new Runnable() {
@Override
public void run() {
if (mHandlerCallbacks.get(id) == null)
return;
callFunction(listener, args);
postDelayed(this, interval);
}
};
mHandlerCallbacks.put(id, r);
postDelayed(r, interval);
return id;
}
public void postDelayed(Runnable r, long interval) {
long uptime = SystemClock.uptimeMillis() + interval;
mHandler.postAtTime(r, uptime);
mMaxCallbackUptimeMillis = Math.max(mMaxCallbackUptimeMillis, uptime);
synchronized (mMaxCallbackMillisForAllThread) {
mMaxCallbackMillisForAllThread.set(Math.max(mMaxCallbackMillisForAllThread.get(), uptime));
}
}
public void post(Runnable r) {
}
public boolean clearInterval(int id) {
return clearCallback(id);
}
public int setImmediate(final Object listener, final Object... args) {
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = () -> {
callFunction(listener, args);
mHandlerCallbacks.remove(id);
};
mHandlerCallbacks.put(id, r);
postDelayed(r, 0);
return id;
}
public boolean clearImmediate(int id) {
return clearCallback(id);
}
private boolean clearCallback(int id) {
Runnable callback = mHandlerCallbacks.get(id);
if (callback != null) {
mHandler.removeCallbacks(callback);
mHandlerCallbacks.remove(id);
return true;
}
return false;
}
public boolean hasPendingCallbacks() {
return mMaxCallbackUptimeMillis > SystemClock.uptimeMillis();
}
public void removeAllCallbacks() {
mHandler.removeCallbacksAndMessages(null);
}
}

View File

@@ -0,0 +1,117 @@
package org.autojs.autojs.core.looper
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import org.autojs.autojs.runtime.ScriptRuntime
import java.util.concurrent.ConcurrentHashMap
import kotlin.random.Random
/**
* Created by Stardust on 2017/12/27.
*/
class Timer(
runtime: ScriptRuntime,
looper: Looper
) {
private val mHandlerCallbacks = ConcurrentHashMap<Int, Runnable?>()
private val mRuntime: ScriptRuntime = runtime
private val mHandler: Handler = Handler(looper)
private val isUiLoop: Boolean = looper == Looper.getMainLooper()
constructor(runtime: ScriptRuntime, ) : this(runtime, Looper.myLooper()!!)
fun setTimeout(callback: Any, delay: Long, vararg args: Any): Int {
val id = createTimerId()
val r = Runnable {
callFunction(callback, null, args as Array<Any>)
mHandlerCallbacks.remove(id)
}
mHandlerCallbacks[id] = r
postDelayed(r, delay)
return id
}
private fun callFunction(callback: Any, thiz: Any?, args: Array<Any>) {
try {
mRuntime.bridges.callFunction(callback, thiz, args)
} catch (e: Exception) {
if (isUiLoop) {
mRuntime.exit(e)
} else throw e
}
}
@Synchronized
private fun createTimerId(): Int {
var id: Int
do {
id = Random.nextInt()
} while (mHandlerCallbacks.containsKey(id))
mHandlerCallbacks[id] = EMPTY_RUNNABLE
return id
}
fun setInterval(listener: Any, interval: Long, vararg args: Any): Int {
val id = createTimerId()
val r: Runnable = object : Runnable {
override fun run() {
if (mHandlerCallbacks[id] == null) return
callFunction(listener, null, args as Array<Any>)
postDelayed(this, interval)
}
}
mHandlerCallbacks[id] = r
postDelayed(r, interval)
return id
}
fun postDelayed(r: Runnable, interval: Long) {
val uptime = SystemClock.uptimeMillis() + interval
mHandler.postAtTime(r, uptime)
}
fun post(r: Runnable) {
mHandler.post(r)
}
fun clearInterval(id: Int): Boolean = clearCallback(id)
fun clearImmediate(id: Int): Boolean = clearCallback(id)
fun clearTimeout(id: Int): Boolean = clearCallback(id)
fun setImmediate(listener: Any, vararg args: Any): Int {
val id = createTimerId()
val r = Runnable {
callFunction(listener, null, args as Array<Any>)
mHandlerCallbacks.remove(id)
}
mHandlerCallbacks[id] = r
post(r)
return id
}
private fun clearCallback(id: Int): Boolean {
val callback = mHandlerCallbacks[id]
if (callback != null) {
mHandler.removeCallbacks(callback)
mHandlerCallbacks.remove(id)
if (mHandlerCallbacks.isEmpty()) mHandler.post(EMPTY_RUNNABLE)
return true
}
return false
}
fun hasPendingCallbacks(): Boolean {
return mHandlerCallbacks.size > 0
}
fun removeAllCallbacks() {
mHandler.removeCallbacksAndMessages(null)
}
companion object {
private const val LOG_TAG = "Timer"
private val EMPTY_RUNNABLE = Runnable {}
}
}

View File

@@ -1,134 +0,0 @@
package org.autojs.autojs.core.looper;
import static org.autojs.autojs.util.StringUtils.str;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import org.autojs.autojs.concurrent.VolatileBox;
import org.autojs.autojs.engine.RhinoJavaScriptEngine;
import org.autojs.autojs.lang.ThreadCompat;
import org.autojs.autojs.runtime.ScriptRuntime;
import org.autojs.autojs.runtime.exception.ScriptInterruptedException;
import org.autojs.autojs6.R;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Stardust on 2017/12/27.
*/
public class TimerThread extends ThreadCompat {
private static final ConcurrentHashMap<Thread, Timer> sTimerMap = new ConcurrentHashMap<>();
private Timer mTimer;
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads;
private final ScriptRuntime mRuntime;
private final Runnable mTarget;
private boolean mRunning = false;
private final Object mRunningLock = new Object();
public TimerThread(ScriptRuntime runtime, VolatileBox<Long> maxCallbackUptimeMillisForAllThreads, Runnable target) {
super(target);
mRuntime = runtime;
mTarget = target;
mMaxCallbackUptimeMillisForAllThreads = maxCallbackUptimeMillisForAllThreads;
}
@Override
public void run() {
mRuntime.loopers.prepare();
mTimer = new Timer(mRuntime, mMaxCallbackUptimeMillisForAllThreads);
sTimerMap.put(Thread.currentThread(), mTimer);
((RhinoJavaScriptEngine) mRuntime.engines.myEngine()).enterContext();
notifyRunning();
new Handler().post(mTarget);
try {
Looper.loop();
} catch (Throwable e) {
if (!ScriptInterruptedException.causedByInterrupted(e)) {
mRuntime.console.error(Thread.currentThread() + ": ", e);
}
} finally {
onExit();
mTimer = null;
org.mozilla.javascript.Context.exit();
sTimerMap.remove(Thread.currentThread(), mTimer);
}
}
@Override
public void interrupt() {
LooperHelper.quitForThread(this);
super.interrupt();
}
private void notifyRunning() {
synchronized (mRunningLock) {
mRunning = true;
mRunningLock.notifyAll();
}
}
@CallSuper
protected void onExit() {
mRuntime.loopers.notifyThreadExit(this);
}
public static Timer getTimerForThread(Thread thread) {
return sTimerMap.get(thread);
}
public static Timer getTimerForCurrentThread() {
return getTimerForThread(Thread.currentThread());
}
public int setTimeout(Object callback, long delay, Object... args) {
return getTimer().setTimeout(callback, delay, args);
}
public Timer getTimer() {
if (mTimer == null) {
throw new IllegalStateException(str(R.string.error_thread_is_not_alive));
}
return mTimer;
}
public boolean clearTimeout(int id) {
return getTimer().clearTimeout(id);
}
public int setInterval(Object listener, long interval, Object... args) {
return getTimer().setInterval(listener, interval, args);
}
public boolean clearInterval(int id) {
return getTimer().clearInterval(id);
}
public int setImmediate(Object listener, Object... args) {
return getTimer().setImmediate(listener, args);
}
public boolean clearImmediate(int id) {
return getTimer().clearImmediate(id);
}
public void waitFor() throws InterruptedException {
synchronized (mRunningLock) {
if (mRunning)
return;
mRunningLock.wait();
}
}
@NonNull
@Override
public String toString() {
return "Thread[" + getName() + "," + getPriority() + "]";
}
}

View File

@@ -0,0 +1,126 @@
package org.autojs.autojs.core.looper
import android.os.Looper
import androidx.annotation.CallSuper
import org.autojs.autojs.engine.RhinoJavaScriptEngine
import org.autojs.autojs.lang.ThreadCompat
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.mozilla.javascript.Context
import java.util.concurrent.ConcurrentHashMap
/**
* Created by Stardust on 2017/12/27.
*/
open class TimerThread(private val mRuntime: ScriptRuntime, private val mTarget: Runnable) :
ThreadCompat(mTarget) {
private var mTimer: Timer? = null
private var mRunning = false
private val mRunningLock = Object()
private val mAsyncTask = Loopers.AsyncTask("TimerThread")
var loopers: Loopers? = null
init {
mRuntime.loopers.addAsyncTask(mAsyncTask)
}
override fun run() {
loopers = Loopers(mRuntime)
mTimer = loopers!!.mTimer
sTimerMap[currentThread()] = mTimer!!
(mRuntime.engines.myEngine() as RhinoJavaScriptEngine).enterContext()
notifyRunning()
mTimer!!.post(mTarget)
try {
Looper.loop()
} catch (e: Throwable) {
if (!ScriptInterruptedException.causedByInterrupted(e)) {
mRuntime.console.error(currentThread().toString() + ": ", e)
}
} finally {
//mRuntime.console.log("TimerThread exit");
onExit()
mTimer = null
Context.exit()
sTimerMap.remove(currentThread(), mTimer)
}
}
override fun interrupt() {
LooperHelper.quitForThread(this)
super.interrupt()
}
private fun notifyRunning() {
synchronized(mRunningLock) {
mRunning = true
mRunningLock.notifyAll()
}
}
@CallSuper
protected open fun onExit() {
mRuntime.loopers.removeAsyncTask(mAsyncTask)
mRuntime.loopers.notifyThreadExit(this)
}
fun setTimeout(callback: Any, delay: Long, vararg args: Any?): Int {
return timer.setTimeout(callback, delay, *args as Array<out Any>)
}
fun setTimeout(callback: Any): Int {
return setTimeout(callback, 1)
}
val timer: Timer
get() {
checkNotNull(mTimer) { "thread is not alive" }
return mTimer as Timer
}
fun clearTimeout(id: Int): Boolean {
return timer.clearTimeout(id)
}
fun setInterval(listener: Any?, interval: Long, vararg args: Any?): Int {
return timer.setInterval(listener!!, interval, *args as Array<out Any>)
}
fun setInterval(listener: Any?): Int {
return setInterval(listener, 1)
}
fun clearInterval(id: Int): Boolean {
return timer.clearInterval(id)
}
fun setImmediate(listener: Any, vararg args: Any?): Int {
return timer.setImmediate(listener, *args as Array<out Any>)
}
fun clearImmediate(id: Int): Boolean {
return timer.clearImmediate(id)
}
@Throws(InterruptedException::class)
fun waitFor() {
synchronized(mRunningLock) {
if (mRunning) return
mRunningLock.wait()
}
}
override fun toString(): String {
return "Thread[$name,$priority]"
}
companion object {
private val sTimerMap = ConcurrentHashMap<Thread, Timer?>()
@JvmStatic
fun getTimerForThread(thread: Thread): Timer? {
return sTimerMap[thread]
}
val timerForCurrentThread: Timer?
get() = getTimerForThread(currentThread())
}
}

View File

@@ -20,7 +20,7 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
private final Timer mTimer;
private final Loopers mLoopers;
private JsDialog mDialog;
private volatile int mWaitId = -1;
private volatile Loopers.AsyncTask task;
public JsDialogBuilder(Context context, ScriptRuntime runtime) {
@@ -55,14 +55,14 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
}
});
dismissListener(dialog -> {
mTimer.postDelayed(() -> mLoopers.doNotWaitWhenIdle(mWaitId), 0);
mTimer.postDelayed(() -> mLoopers.removeAsyncTask(task), 0);
emit("dismiss", dialog);
});
cancelListener(dialog -> emit("cancel", dialog));
}
public void onShowCalled() {
mTimer.postDelayed(() -> mWaitId = mLoopers.waitWhenIdle(), 0);
mTimer.postDelayed(() -> task = mLoopers.createAndAddAsyncTask("js-dialog"), 0);
}
public JsDialog getDialog() {

View File

@@ -22,7 +22,9 @@ import java.util.Set;
/**
* Created by Stardust on 2018/2/5.
*/
public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
public class Sensors extends EventEmitter {
public class SensorEventEmitter extends EventEmitter implements SensorEventListener {
@@ -82,6 +84,13 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
private final ScriptBridges mScriptBridges;
private final SensorEventEmitter mNoOpSensorEventEmitter;
private final ScriptRuntime mScriptRuntime;
private final Loopers.AsyncTask mAsyncTask = new Loopers.AsyncTask("Sensors"){
@Override
public boolean onFinish(@NonNull Loopers loopers) {
return !mSensorEventEmitters.isEmpty();
}
};
public Sensors(Context context, ScriptRuntime runtime) {
super(runtime.bridges);
@@ -89,7 +98,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
mScriptBridges = runtime.bridges;
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
mScriptRuntime = runtime;
runtime.loopers.addLooperQuitHandler(this);
runtime.loopers.addAsyncTask(mAsyncTask);
}
public SensorEventEmitter register(String sensorName) {
@@ -121,11 +130,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
return emitter;
}
@Override
public boolean shouldQuit() {
return mSensorEventEmitters.isEmpty();
}
public Sensor getSensor(String sensorName) {
sensorName = sensorName.toUpperCase();
Integer type = SENSORS.get(sensorName);
@@ -158,6 +162,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
mSensorEventEmitters.clear();
}
mScriptRuntime.loopers.removeLooperQuitHandler(this);
mScriptRuntime.loopers.removeAsyncTask(mAsyncTask);
}
}

View File

@@ -1,12 +1,13 @@
package org.autojs.autojs.runtime.api
import org.autojs.autojs.annotation.ScriptInterface
import org.autojs.autojs.concurrent.VolatileDispose
import org.autojs.autojs.core.looper.Loopers
import org.autojs.autojs.core.looper.MainThreadProxy
import org.autojs.autojs.core.looper.TimerThread
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.util.StringUtils.str
import org.autojs.autojs6.R
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantLock
@@ -14,58 +15,81 @@ import java.util.concurrent.locks.ReentrantLock
* Created by Stardust on 2017/12/3.
*/
class Threads(private val mRuntime: ScriptRuntime) {
private val mThreads = HashSet<Thread>()
val mainThread: Thread = Thread.currentThread()
private val mMainThreadProxy = MainThreadProxy(Thread.currentThread(), mRuntime)
private var mSpawnCount = 0
private var mTaskCount = AtomicLong(0)
private var mExit = false
private val looperTask = Loopers.AsyncTask("AsyncTaskThreadPool")
private val threadPool = Executors.newFixedThreadPool(20, ThreadFactory {
val thread = Thread(it)
thread.name = mainThread.name + " (AsyncThread)"
thread
})
val mainThread: Thread = Thread.currentThread()
fun currentThread(): Any {
val thread = Thread.currentThread()
return if (thread === mainThread) mMainThreadProxy else thread
}
fun currentThread(): Any = Thread.currentThread().let { thread ->
if (thread === mainThread) mMainThreadProxy else thread
fun runTaskForThreadPool(runnable: Runnable) {
if (mTaskCount.addAndGet(1) == 1L) mRuntime.loopers.addAsyncTask(looperTask)
threadPool.execute {
try {
runnable.run()
} catch (e: Throwable) {
if (!ScriptInterruptedException.causedByInterrupted(e)) {
mRuntime.console.error("$this: ", e)
}
} finally {
if (mTaskCount.addAndGet(-1) == 0L){
mRuntime.loopers.removeAsyncTask(looperTask)
}
}
}
}
fun start(runnable: Runnable): TimerThread {
val thread = createThread(runnable)
synchronized(mThreads) {
check(!mExit) { str(R.string.error_script_is_on_exiting) }
thread.let {
mThreads.add(it)
it.name = "${mainThread.name} (Spawn-$mSpawnCount)"
mSpawnCount++
it.start()
}
check(!mExit) { "script exiting" }
mThreads.add(thread)
thread.name = mainThread.name + " (Spawn-" + mSpawnCount + ")"
mSpawnCount++
thread.start()
}
return thread
}
private fun createThread(runnable: Runnable): TimerThread {
val millis = mRuntime.timers.maxCallbackUptimeMillisForAllThreads
return object : TimerThread(mRuntime, millis, runnable) {
return object : TimerThread(mRuntime, runnable) {
override fun onExit() {
synchronized(mThreads) { mThreads.remove(currentThread()) }
super.onExit()
}
}
}
fun disposable() = VolatileDispose<Any?>()
fun disposable(): VolatileDispose<*> {
return VolatileDispose<Any?>()
}
fun atomic(value: Long) = AtomicLong(value)
fun atomic(value: Long): AtomicLong {
return AtomicLong(value)
}
fun atomic() = AtomicLong()
fun lock() = ReentrantLock()
fun shutDownAll() {
threadPool.shutdownNow()
synchronized(mThreads) {
mThreads.apply {
forEach { it.interrupt() }
clear()
for (thread in mThreads) {
thread.interrupt()
}
mThreads.clear()
}
}
@@ -76,6 +100,7 @@ class Threads(private val mRuntime: ScriptRuntime) {
}
}
fun hasRunningThreads(): Boolean = synchronized(mThreads) { return mThreads.isNotEmpty() }
fun hasRunningThreads(): Boolean {
synchronized(mThreads) { return mThreads.isNotEmpty() }
}
}

View File

@@ -1,9 +1,6 @@
package org.autojs.autojs.runtime.api;
import android.os.Looper;
import android.os.SystemClock;
import org.autojs.autojs.concurrent.VolatileBox;
import org.autojs.autojs.core.looper.Timer;
import org.autojs.autojs.core.looper.TimerThread;
import org.autojs.autojs.runtime.ScriptRuntime;
@@ -11,28 +8,24 @@ import org.autojs.autojs.runtime.ScriptRuntime;
/**
* Created by Stardust on 2017/7/21.
*/
public class Timers {
private static final String LOG_TAG = "Timers";
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
private final Threads mThreads;
private final Timer mMainTimer;
private final Timer mUiTimer;
//private VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
private Threads mThreads;
private Timer mUiTimer;
private ScriptRuntime mRuntime;
public Timers(ScriptRuntime runtime) {
mMainTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads);
mUiTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads, Looper.getMainLooper());
mUiTimer = new Timer(runtime, Looper.getMainLooper());
mThreads = runtime.threads;
mRuntime = runtime;
}
public Timer getMainTimer() {
return mMainTimer;
}
VolatileBox<Long> getMaxCallbackUptimeMillisForAllThreads() {
return mMaxCallbackUptimeMillisForAllThreads;
return mRuntime.loopers.getMTimer();
}
public Timer getTimerForCurrentThread() {
@@ -41,19 +34,27 @@ public class Timers {
public Timer getTimerForThread(Thread thread) {
if (thread == mThreads.getMainThread()) {
return mMainTimer;
return mRuntime.loopers.getMTimer();
}
Timer timer = TimerThread.getTimerForThread(thread);
if (timer == null && Looper.myLooper() == Looper.getMainLooper()) {
return mUiTimer;
}
return timer;
if (timer == null) {
return mRuntime.loopers.getMTimer();
} else {
return timer;
}
}
public int setTimeout(Object callback, long delay, Object... args) {
return getTimerForCurrentThread().setTimeout(callback, delay, args);
}
public int setTimeout(Object callback) {
return setTimeout(callback, 1);
}
public boolean clearTimeout(int id) {
return getTimerForCurrentThread().clearTimeout(id);
}
@@ -62,6 +63,10 @@ public class Timers {
return getTimerForCurrentThread().setInterval(listener, interval, args);
}
public int setInterval(Object listener) {
return setInterval(listener, 1);
}
public boolean clearInterval(int id) {
return getTimerForCurrentThread().clearInterval(id);
}
@@ -74,17 +79,8 @@ public class Timers {
return getTimerForCurrentThread().clearImmediate(id);
}
public boolean hasPendingCallbacks() {
//如果是脚本主线程则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。
if (mThreads.getMainThread() == Thread.currentThread()) {
return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis();
}
// 否则检查当前线程的定时回调
return getTimerForCurrentThread().hasPendingCallbacks();
}
public void recycle() {
mMainTimer.removeAllCallbacks();
mRuntime.loopers.getMTimer().removeAllCallbacks();
}
}