From 59c0c73d99b26c7d56c57d9518ac50e1968f1065 Mon Sep 17 00:00:00 2001 From: aiselp Date: Sat, 10 Jun 2023 12:48:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=82=E6=AD=A5=E7=8E=AF=E5=A2=83=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../autojs/autojs/core/looper/Loopers.java | 211 ------------------ .../org/autojs/autojs/core/looper/Loopers.kt | 182 +++++++++++++++ .../org/autojs/autojs/core/looper/Timer.java | 131 ----------- .../org/autojs/autojs/core/looper/Timer.kt | 117 ++++++++++ .../autojs/core/looper/TimerThread.java | 134 ----------- .../autojs/autojs/core/looper/TimerThread.kt | 126 +++++++++++ .../core/ui/dialog/JsDialogBuilder.java | 6 +- .../autojs/autojs/runtime/api/Sensors.java | 20 +- .../org/autojs/autojs/runtime/api/Threads.kt | 75 ++++--- .../org/autojs/autojs/runtime/api/Timers.java | 50 ++--- 10 files changed, 513 insertions(+), 539 deletions(-) delete mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Loopers.java create mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt delete mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Timer.java create mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Timer.kt delete mode 100644 app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java create mode 100644 app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.java b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.java deleted file mode 100644 index 62324a57..00000000 --- a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.java +++ /dev/null @@ -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 waitWhenIdle = new ThreadLocal<>() { - @Nullable - @Override - protected Boolean initialValue() { - return Looper.myLooper() == Looper.getMainLooper(); - } - }; - private final ThreadLocal> waitIds = new ThreadLocal<>() { - @Nullable - @Override - protected HashSet initialValue() { - return new HashSet<>(); - } - }; - private final ThreadLocal maxWaitId = new ThreadLocal<>() { - @Nullable - @Override - protected Integer initialValue() { - return 0; - } - }; - private final ThreadLocal> 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 handlers = looperQuitHandlers.get(); - if (handlers == null) { - handlers = new CopyOnWriteArrayList<>(); - looperQuitHandlers.set(handlers); - } - handlers.add(handler); - } - - public boolean removeLooperQuitHandler(LooperQuitHandler handler) { - CopyOnWriteArrayList 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 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); - } -} diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt new file mode 100644 index 00000000..53d3f73b --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt @@ -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() + 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() + 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 {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Timer.java b/app/src/main/java/org/autojs/autojs/core/looper/Timer.java deleted file mode 100644 index dae315b6..00000000 --- a/app/src/main/java/org/autojs/autojs/core/looper/Timer.java +++ /dev/null @@ -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 mHandlerCallbacks = new SparseArray<>(); - private int mCallbackMaxId = 0; - private final ScriptRuntime mRuntime; - private final Handler mHandler; - private long mMaxCallbackUptimeMillis = 0; - private final VolatileBox mMaxCallbackMillisForAllThread; - - public Timer(ScriptRuntime runtime, VolatileBox maxCallbackMillisForAllThread) { - mRuntime = runtime; - mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread; - mHandler = new Handler(); - } - - public Timer(ScriptRuntime runtime, VolatileBox 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); - } - -} diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt b/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt new file mode 100644 index 00000000..14dbd0ad --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt @@ -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() + 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) + mHandlerCallbacks.remove(id) + } + mHandlerCallbacks[id] = r + postDelayed(r, delay) + return id + } + + private fun callFunction(callback: Any, thiz: Any?, args: Array) { + 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) + 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) + 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 {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java deleted file mode 100644 index 97fe478c..00000000 --- a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java +++ /dev/null @@ -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 sTimerMap = new ConcurrentHashMap<>(); - - private Timer mTimer; - private final VolatileBox mMaxCallbackUptimeMillisForAllThreads; - private final ScriptRuntime mRuntime; - private final Runnable mTarget; - private boolean mRunning = false; - private final Object mRunningLock = new Object(); - - public TimerThread(ScriptRuntime runtime, VolatileBox 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() + "]"; - } - -} diff --git a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt new file mode 100644 index 00000000..8006f4f4 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt @@ -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) + } + + 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) + } + + 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) + } + + 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() + + @JvmStatic + fun getTimerForThread(thread: Thread): Timer? { + return sTimerMap[thread] + } + + val timerForCurrentThread: Timer? + get() = getTimerForThread(currentThread()) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java b/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java index 3cade410..0ced880e 100644 --- a/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java +++ b/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java @@ -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() { diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java b/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java index 7275494b..58b781f9 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java @@ -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); } } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt b/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt index 77b8df62..ac3ae7ff 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt @@ -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() + 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() + fun disposable(): VolatileDispose<*> { + return VolatileDispose() + } - 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() } + } } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java b/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java index 37f0977b..56e5628a 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java @@ -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 mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); - private final Threads mThreads; - private final Timer mMainTimer; - private final Timer mUiTimer; - + //private VolatileBox 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 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(); } }