From 729958d419f036988bb2f35367c7625e700307c5 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Wed, 27 Dec 2017 16:47:32 +0800 Subject: [PATCH] api: threads, files --- app/src/main/assets/sample/多线程/原子变量.js | 1 + .../assets/sample/多线程/变量可见性实验.js | 11 ++ .../assets/sample/多线程/多线程简单示例.js | 26 ++++ .../assets/sample/多线程/线程启动与关闭.js | 14 ++ .../scriptdroid/ui/floating/CircularMenu.java | 2 + .../scriptdroid/ui/main/MainActivity.java | 1 + autojs/src/main/assets/modules/__events__.js | 11 ++ autojs/src/main/assets/modules/__io__.js | 5 +- .../autojs/core/console/ConsoleView.java | 72 +++++----- .../autojs/core/console/StardustConsole.java | 17 +-- .../autojs/core/looper/LooperHelper.java | 30 ++++ .../stardust/autojs/core/looper/Loopers.java | 130 ++++++++++++++++++ .../stardust/autojs/core/looper/Timer.java | 112 +++++++++++++++ .../autojs/core/looper/TimerThread.java | 90 ++++++++++++ .../engine/LoopBasedJavaScriptEngine.java | 8 +- .../stardust/autojs/engine/ScriptEngine.java | 5 +- .../LoopedBasedJavaScriptExecution.java | 10 +- .../autojs/runtime/ScriptRuntime.java | 23 ++-- .../stardust/autojs/runtime/api/Events.java | 75 +++++----- .../stardust/autojs/runtime/api/Loopers.java | 119 ---------------- .../stardust/autojs/runtime/api/Threads.java | 75 ++++++---- .../stardust/autojs/runtime/api/Timers.java | 105 +++++--------- autojs/src/main/res/values/strings.xml | 1 + .../java/com/stardust/concurrent/Value.java | 27 ++++ .../java/com/stardust/lang/ThreadCompat.java | 14 +- .../main/java/com/stardust/pio/PFiles.java | 7 + 26 files changed, 652 insertions(+), 339 deletions(-) create mode 100644 app/src/main/assets/sample/多线程/原子变量.js create mode 100644 app/src/main/assets/sample/多线程/变量可见性实验.js create mode 100644 app/src/main/assets/sample/多线程/多线程简单示例.js create mode 100644 app/src/main/assets/sample/多线程/线程启动与关闭.js create mode 100644 autojs/src/main/java/com/stardust/autojs/core/looper/LooperHelper.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/looper/Timer.java create mode 100644 autojs/src/main/java/com/stardust/autojs/core/looper/TimerThread.java delete mode 100644 autojs/src/main/java/com/stardust/autojs/runtime/api/Loopers.java create mode 100644 common/src/main/java/com/stardust/concurrent/Value.java diff --git a/app/src/main/assets/sample/多线程/原子变量.js b/app/src/main/assets/sample/多线程/原子变量.js new file mode 100644 index 00000000..adbf1bc2 --- /dev/null +++ b/app/src/main/assets/sample/多线程/原子变量.js @@ -0,0 +1 @@ +var i = threads.atomic(); diff --git a/app/src/main/assets/sample/多线程/变量可见性实验.js b/app/src/main/assets/sample/多线程/变量可见性实验.js new file mode 100644 index 00000000..707225cb --- /dev/null +++ b/app/src/main/assets/sample/多线程/变量可见性实验.js @@ -0,0 +1,11 @@ +var running = true; + +threads.start(function(){ + while(running){ + log("running = true"); + } +}); + +sleep(2000); +running = false; +console.info("running = false"); diff --git a/app/src/main/assets/sample/多线程/多线程简单示例.js b/app/src/main/assets/sample/多线程/多线程简单示例.js new file mode 100644 index 00000000..d8f9586e --- /dev/null +++ b/app/src/main/assets/sample/多线程/多线程简单示例.js @@ -0,0 +1,26 @@ + +//启动一个线程 +threads.start(function(){ + //在线程中每隔1秒打印"线程1" + while(true){ + log("线程1"); + sleep(1000); + } +}); + +//启动另一个线程 +threads.start(function(){ + //在线程中每隔2秒打印"线程1" + while(true){ + log("线程2"); + sleep(2000); + } +}); + +//在主线程中每隔3秒打印"主线程" +for(var i = 0; i < 10; i++){ + log("主线程"); + sleep(3000); +} +//打印100次后退出所有线程 +threads.shutDownAll(); diff --git a/app/src/main/assets/sample/多线程/线程启动与关闭.js b/app/src/main/assets/sample/多线程/线程启动与关闭.js new file mode 100644 index 00000000..187f5d61 --- /dev/null +++ b/app/src/main/assets/sample/多线程/线程启动与关闭.js @@ -0,0 +1,14 @@ + +//启动一个无限循环的线程 +var thread = threads.start(function(){ + while(true){ + log("子线程运行中..."); + sleep(1000); + } +}); + + +//5秒后关闭线程 +sleep(5000); +thread.interrupt(); + diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/floating/CircularMenu.java b/app/src/main/java/com/stardust/scriptdroid/ui/floating/CircularMenu.java index ef920d4d..5e9b2ae6 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/floating/CircularMenu.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/floating/CircularMenu.java @@ -20,6 +20,7 @@ import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.accessibility.AccessibilityService; import com.stardust.scriptdroid.autojs.AutoJs; import com.stardust.scriptdroid.autojs.record.GlobalActionRecorder; +import com.stardust.scriptdroid.model.script.Scripts; import com.stardust.scriptdroid.storage.file.StorageFileProvider; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.scriptdroid.ui.common.NotAskAgainDialog; @@ -132,6 +133,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener { .positiveText(R.string.cancel) .build(); listView.setOnItemOperatedListener(file -> dialog.dismiss()); + listView.setOnScriptFileClickListener((view, file) -> Scripts.run(file)); DialogUtils.showDialog(dialog); } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java index 9e93e12c..4d5f3f99 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java @@ -55,6 +55,7 @@ import org.androidannotations.annotations.ViewById; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; +import java.io.File; import java.util.Arrays; @EActivity(R.layout.activity_main) diff --git a/autojs/src/main/assets/modules/__events__.js b/autojs/src/main/assets/modules/__events__.js index 44caf715..6c35b622 100644 --- a/autojs/src/main/assets/modules/__events__.js +++ b/autojs/src/main/assets/modules/__events__.js @@ -1,7 +1,18 @@ module.exports = function(__runtime__, scope){ + importClass(android.view.KeyEvent); var events = Object.create(__runtime__.events); + var keys = { + "home": KeyEvent.KEYCODE_HOME, + "menu": KeyEvent.KEYCODE_MENU, + "back": KeyEvent.KEYCODE_BACK, + "volume_up": KeyEvent.KEYCODE_VOLUME_UP, + "volume_down": KeyEvent.KEYCODE_VOLUME_DOWN + } + + scope.keys = keys; + return events; } diff --git a/autojs/src/main/assets/modules/__io__.js b/autojs/src/main/assets/modules/__io__.js index e86adf97..825765b4 100644 --- a/autojs/src/main/assets/modules/__io__.js +++ b/autojs/src/main/assets/modules/__io__.js @@ -1,6 +1,9 @@ module.exports = function(__runtime__, scope){ - var files = com.stardust.pio.PFiles; + var files = Object.create(com.stardust.pio.PFiles); + files.cwd = function(){ + return scope.engines.myEngine().cwd(); + } scope.files = files; scope.open = function(path, mode, encoding, bufferSize){ if(arguments.length == 1){ diff --git a/autojs/src/main/java/com/stardust/autojs/core/console/ConsoleView.java b/autojs/src/main/java/com/stardust/autojs/core/console/ConsoleView.java index 48932e84..cfdf153f 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/console/ConsoleView.java +++ b/autojs/src/main/java/com/stardust/autojs/core/console/ConsoleView.java @@ -24,6 +24,8 @@ import java.util.ArrayList; /** * Created by Stardust on 2017/5/2. + * + * TODO: 优化为无锁形式 */ public class ConsoleView extends FrameLayout implements StardustConsole.LogListener { @@ -77,12 +79,9 @@ public class ConsoleView extends FrameLayout implements StardustConsole.LogListe private void initSubmitButton() { final Button submit = (Button) findViewById(R.id.submit); - submit.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - CharSequence input = mEditText.getText(); - submitInput(input); - } + submit.setOnClickListener(v -> { + CharSequence input = mEditText.getText(); + submitInput(input); }); } @@ -99,13 +98,10 @@ public class ConsoleView extends FrameLayout implements StardustConsole.LogListe mEditText = (EditText) findViewById(R.id.input); mEditText.setFocusableInTouchMode(true); mInputContainer = (LinearLayout) findViewById(R.id.input_container); - OnClickListener listener = new OnClickListener() { - @Override - public void onClick(View v) { - if (mWindow != null) { - mWindow.requestWindowFocus(); - mEditText.requestFocus(); - } + OnClickListener listener = v -> { + if (mWindow != null) { + mWindow.requestWindowFocus(); + mEditText.requestFocus(); } }; mEditText.setOnClickListener(listener); @@ -146,12 +142,9 @@ public class ConsoleView extends FrameLayout implements StardustConsole.LogListe @Override public void onLogClear() { - post(new Runnable() { - @Override - public void run() { - mLogs.clear(); - mLogListRecyclerView.getAdapter().notifyDataSetChanged(); - } + post(() -> { + mLogs.clear(); + mLogListRecyclerView.getAdapter().notifyDataSetChanged(); }); } @@ -160,22 +153,24 @@ public class ConsoleView extends FrameLayout implements StardustConsole.LogListe return; int oldSize = mLogs.size(); ArrayList logs = mConsole.getAllLogs(); - final int size = logs.size(); - if (size == 0) { - return; - } - if (oldSize >= size) { - return; - } - if (oldSize == 0) { - mLogs.addAll(logs); - } else { - for (int i = oldSize; i < size; i++) { - mLogs.add(logs.get(i)); + synchronized (logs) { + final int size = logs.size(); + if (size == 0) { + return; } + if (oldSize >= size) { + return; + } + if (oldSize == 0) { + mLogs.addAll(logs); + } else { + for (int i = oldSize; i < size; i++) { + mLogs.add(logs.get(i)); + } + } + mLogListRecyclerView.getAdapter().notifyItemRangeInserted(oldSize, size - 1); + mLogListRecyclerView.scrollToPosition(size - 1); } - mLogListRecyclerView.getAdapter().notifyItemRangeInserted(oldSize, size - 1); - mLogListRecyclerView.scrollToPosition(size - 1); } public void setWindow(ResizableExpandableFloatyWindow window) { @@ -183,13 +178,10 @@ public class ConsoleView extends FrameLayout implements StardustConsole.LogListe } public void showEditText() { - post(new Runnable() { - @Override - public void run() { - mWindow.requestWindowFocus(); - mInputContainer.setVisibility(VISIBLE); - mEditText.requestFocus(); - } + post(() -> { + mWindow.requestWindowFocus(); + mInputContainer.setVisibility(VISIBLE); + mEditText.requestFocus(); }); } diff --git a/autojs/src/main/java/com/stardust/autojs/core/console/StardustConsole.java b/autojs/src/main/java/com/stardust/autojs/core/console/StardustConsole.java index 7a2583d9..0f2f1cd0 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/console/StardustConsole.java +++ b/autojs/src/main/java/com/stardust/autojs/core/console/StardustConsole.java @@ -122,7 +122,9 @@ public class StardustConsole extends AbstractConsole { @Override public String println(int level, CharSequence charSequence) { Log log = new Log(mIdCounter.getAndIncrement(), level, charSequence, true); - mLogs.add(log); + synchronized (mLogs) { + mLogs.add(log); + } if (mGlobalConsole != null) { mGlobalConsole.println(level, charSequence); } @@ -135,20 +137,15 @@ public class StardustConsole extends AbstractConsole { @Override public void write(int level, CharSequence charSequence) { - Log log = new Log(mIdCounter.getAndIncrement(), level, charSequence); - mLogs.add(log); - if (mGlobalConsole != null) { - mGlobalConsole.print(level, charSequence); - } - if (mLogListener != null && mLogListener.get() != null) { - mLogListener.get().onNewLog(log); - } + println(level, charSequence); } @Override public void clear() { - mLogs.clear(); + synchronized (mLogs) { + mLogs.clear(); + } if (mLogListener != null && mLogListener.get() != null) { mLogListener.get().onLogClear(); } diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/LooperHelper.java b/autojs/src/main/java/com/stardust/autojs/core/looper/LooperHelper.java new file mode 100644 index 00000000..5bb2857a --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/LooperHelper.java @@ -0,0 +1,30 @@ +package com.stardust.autojs.core.looper; + +import android.os.Looper; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by Stardust on 2017/12/27. + */ + +public class LooperHelper { + + private static volatile ConcurrentHashMap sLoopers = new ConcurrentHashMap<>(); + + public static void prepare() { + if (Looper.myLooper() == Looper.getMainLooper()) + return; + if (Looper.myLooper() == null) + Looper.prepare(); + Looper l = Looper.myLooper(); + if (l != null) + sLoopers.put(Thread.currentThread(), l); + } + + public static void quitForThread(Thread thread) { + Looper looper = sLoopers.remove(thread); + if (looper != null) + looper.quit(); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java b/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java new file mode 100644 index 00000000..ea200943 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/Loopers.java @@ -0,0 +1,130 @@ +package com.stardust.autojs.core.looper; + +import android.os.Handler; +import android.os.Looper; + +import com.stardust.autojs.runtime.ScriptRuntime; +import com.stardust.autojs.runtime.api.Threads; +import com.stardust.autojs.runtime.api.Timers; +import com.stardust.autojs.runtime.exception.ScriptInterruptedException; +import com.stardust.lang.ThreadCompat; + +/** + * Created by Stardust on 2017/7/29. + */ + +public class Loopers { + + public interface LooperQuitHandler { + boolean shouldQuit(); + } + + private static final Runnable EMPTY_RUNNABLE = () -> { + }; + + private volatile ThreadLocal waitWhenIdle = new ThreadLocal<>(); + private volatile Looper mServantLooper; + private Timers mTimers; + private ScriptRuntime mScriptRuntime; + private LooperQuitHandler mMainLooperQuitHandler; + private Handler mMainHandler; + private Looper mMainLooper; + private Threads mThreads; + + public Loopers(ScriptRuntime runtime) { + mTimers = runtime.timers; + mThreads = runtime.threads; + mScriptRuntime = runtime; + prepare(); + mMainLooper = Looper.myLooper(); + mMainHandler = new Handler(); + } + + + public Looper getMainLooper() { + return mMainLooper; + } + + private boolean shouldQuitLooper() { + if (Thread.currentThread().isInterrupted()) { + return true; + } + if (mTimers.hasPendingCallbacks()) { + return false; + } + return !waitWhenIdle.get(); + } + + + private void initServantThread() { + new ThreadCompat(() -> { + Looper.prepare(); + final Object lock = Loopers.this; + 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; + } + + public void quitServantLooper() { + if (mServantLooper == null) + return; + mServantLooper.quit(); + } + + public void waitWhenIdle(boolean b) { + waitWhenIdle.set(b); + } + + public void quitAll() { + quitServantLooper(); + } + + public void setMainLooperQuitHandler(LooperQuitHandler mainLooperQuitHandler) { + mMainLooperQuitHandler = mainLooperQuitHandler; + } + + public void prepare() { + if (Looper.myLooper() == null) + Looper.prepare(); + Looper.myQueue().addIdleHandler(() -> { + Looper l = Looper.myLooper(); + if (l == null) + return true; + if (l == mMainLooper) { + if (shouldQuitLooper() && !mThreads.hasRunningThreads() && + mMainLooperQuitHandler != null && mMainLooperQuitHandler.shouldQuit()) { + l.quit(); + } + } else { + if (shouldQuitLooper()) { + l.quit(); + } + } + return true; + }); + waitWhenIdle.set(Looper.myLooper() == Looper.getMainLooper()); + } + + public void notifyThreadExit(TimerThread thread) { + //当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程), + //此时通过向主线程发送一个空的Runnable,主线程执行完这个Runnable后会触发IdleHandler,从而检查自身是否退出 + mMainHandler.post(EMPTY_RUNNABLE); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/Timer.java b/autojs/src/main/java/com/stardust/autojs/core/looper/Timer.java new file mode 100644 index 00000000..a537354b --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/Timer.java @@ -0,0 +1,112 @@ +package com.stardust.autojs.core.looper; + +import android.os.Handler; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; + +import com.stardust.autojs.runtime.ScriptBridges; +import com.stardust.concurrent.VolatileBox; + +/** + * Created by Stardust on 2017/12/27. + */ + +public class Timer { + + private static final String LOG_TAG = "Timer"; + + private SparseArray mHandlerCallbacks = new SparseArray<>(); + private int mCallbackMaxId = 0; + private ScriptBridges mBridges; + private Handler mHandler; + private long mMaxCallbackUptimeMillis = 0; + private final VolatileBox mMaxCallbackMillisForAllThread; + + public Timer(ScriptBridges bridges, VolatileBox maxCallbackMillisForAllThread) { + mBridges = bridges; + mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread; + mHandler = new Handler(); + Log.d(LOG_TAG, "Timer: handler = " + mHandler + ", thread = " + Thread.currentThread()); + } + + public int setTimeout(final Object callback, final long delay, final Object... args) { + Log.d(LOG_TAG, "setTimeout: handler = " + mHandler); + mCallbackMaxId++; + final int id = mCallbackMaxId; + Runnable r = () -> { + Log.d(LOG_TAG, "callFunction: handler = " + mHandler); + mBridges.callFunction(callback, null, args); + mHandlerCallbacks.remove(id); + }; + mHandlerCallbacks.put(id, r); + postDelayed(r, delay); + return id; + } + + 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; + mBridges.callFunction(listener, null, args); + postDelayed(this, interval); + } + }; + mHandlerCallbacks.put(id, r); + postDelayed(r, interval); + return id; + } + + private 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 boolean clearInterval(int id) { + return clearCallback(id); + } + + public int setImmediate(final Object listener, final Object... args) { + mCallbackMaxId++; + final int id = mCallbackMaxId; + Runnable r = () -> { + mBridges.callFunction(listener, null, 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() { + Log.d(LOG_TAG, "[thread]hasPendingCallbacks:" + (mMaxCallbackUptimeMillis > SystemClock.uptimeMillis())); + Log.d(LOG_TAG, "mMaxCallbackUptimeMillisForAllThreads:" + mMaxCallbackUptimeMillis); + return mMaxCallbackUptimeMillis > SystemClock.uptimeMillis(); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/TimerThread.java b/autojs/src/main/java/com/stardust/autojs/core/looper/TimerThread.java new file mode 100644 index 00000000..65b48dc6 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/TimerThread.java @@ -0,0 +1,90 @@ +package com.stardust.autojs.core.looper; + +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.CallSuper; + +import com.stardust.autojs.engine.RhinoJavaScriptEngine; +import com.stardust.autojs.runtime.ScriptBridges; +import com.stardust.autojs.runtime.ScriptRuntime; +import com.stardust.autojs.runtime.exception.ScriptInterruptedException; +import com.stardust.concurrent.VolatileBox; +import com.stardust.lang.ThreadCompat; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by Stardust on 2017/12/27. + */ + +public class TimerThread extends ThreadCompat { + + private static ConcurrentHashMap sTimerMap = new ConcurrentHashMap<>(); + + private Timer mTimer; + private final VolatileBox mMaxCallbackUptimeMillisForAllThreads; + private final ScriptRuntime mRuntime; + private Runnable mTarget; + + 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.bridges, mMaxCallbackUptimeMillisForAllThreads); + sTimerMap.put(Thread.currentThread(), mTimer); + new Handler().post(mTarget); + try { + Looper.loop(); + } catch (Exception e) { + if (!ScriptInterruptedException.causedByInterrupted(e)) { + mRuntime.console.error(Thread.currentThread().toString() + ": " + e); + } + } finally { + onExit(); + sTimerMap.remove(Thread.currentThread(), mTimer); + } + } + + @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 mTimer.setTimeout(callback, delay, args); + } + + public boolean clearTimeout(int id) { + return mTimer.clearTimeout(id); + } + + public int setInterval(Object listener, long interval, Object... args) { + return mTimer.setInterval(listener, interval, args); + } + + public boolean clearInterval(int id) { + return mTimer.clearInterval(id); + } + + public int setImmediate(Object listener, Object... args) { + return mTimer.setImmediate(listener, args); + } + + public boolean clearImmediate(int id) { + return mTimer.clearImmediate(id); + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java index 83ba5028..04984b45 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java @@ -5,7 +5,7 @@ import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; -import com.stardust.autojs.runtime.api.Loopers; +import com.stardust.autojs.core.looper.LooperHelper; import com.stardust.autojs.script.JavaScriptSource; import com.stardust.autojs.script.ScriptSource; import com.stardust.util.Callback; @@ -62,7 +62,7 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { @Override public void forceStop() { - Loopers.quitForThread(getThread()); + LooperHelper.quitForThread(getThread()); super.forceStop(); } @@ -70,13 +70,13 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { public synchronized void destroy() { Thread thread = getThread(); if (thread != null) - Loopers.quitForThread(thread); + LooperHelper.quitForThread(thread); super.destroy(); } @Override public void init() { - Loopers.prepare(); + LooperHelper.prepare(); mHandler = new Handler(); super.init(); } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java index 41e486c2..868ccb60 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java @@ -40,7 +40,6 @@ public interface ScriptEngine { Object getTag(String key); - /** * @hide */ @@ -87,6 +86,10 @@ public interface ScriptEngine { } } + public String cwd() { + return (String) getTag(TAG_PATH); + } + public void setOnDestroyListener(OnDestroyListener onDestroyListener) { if (mOnDestroyListener != null) throw new SecurityException("setOnDestroyListener can be called only once"); diff --git a/autojs/src/main/java/com/stardust/autojs/execution/LoopedBasedJavaScriptExecution.java b/autojs/src/main/java/com/stardust/autojs/execution/LoopedBasedJavaScriptExecution.java index 9cb9cb19..dc8f4894 100644 --- a/autojs/src/main/java/com/stardust/autojs/execution/LoopedBasedJavaScriptExecution.java +++ b/autojs/src/main/java/com/stardust/autojs/execution/LoopedBasedJavaScriptExecution.java @@ -1,14 +1,10 @@ package com.stardust.autojs.execution; -import android.os.MessageQueue; -import android.util.Log; - import com.stardust.autojs.engine.LoopBasedJavaScriptEngine; import com.stardust.autojs.engine.ScriptEngine; import com.stardust.autojs.engine.ScriptEngineManager; -import com.stardust.autojs.runtime.api.Loopers; +import com.stardust.autojs.core.looper.Loopers; import com.stardust.autojs.script.JavaScriptSource; -import com.stardust.autojs.script.ScriptSource; /** * Created by Stardust on 2017/10/27. @@ -28,7 +24,7 @@ public class LoopedBasedJavaScriptExecution extends RunnableScriptExecution { sleep(delay); final LoopBasedJavaScriptEngine javaScriptEngine = (LoopBasedJavaScriptEngine) engine; final long interval = getConfig().interval; - javaScriptEngine.getRuntime().loopers.setLooperQuitHandler(new Loopers.LooperQuitHandler() { + javaScriptEngine.getRuntime().loopers.setMainLooperQuitHandler(new Loopers.LooperQuitHandler() { long times = getConfig().loopTimes == 0 ? Integer.MAX_VALUE : getConfig().loopTimes; @Override @@ -39,7 +35,7 @@ public class LoopedBasedJavaScriptExecution extends RunnableScriptExecution { javaScriptEngine.execute(getSource()); return false; } - javaScriptEngine.getRuntime().loopers.setLooperQuitHandler(null); + javaScriptEngine.getRuntime().loopers.setMainLooperQuitHandler(null); return true; } }); diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java index a4a5e4bf..441741a7 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java @@ -17,7 +17,7 @@ import com.stardust.autojs.runtime.api.Device; import com.stardust.autojs.runtime.api.Engines; import com.stardust.autojs.runtime.api.Events; import com.stardust.autojs.runtime.api.Floaty; -import com.stardust.autojs.runtime.api.Loopers; +import com.stardust.autojs.core.looper.Loopers; import com.stardust.autojs.runtime.api.Threads; import com.stardust.autojs.runtime.api.Timers; import com.stardust.autojs.core.accessibility.UiSelector; @@ -168,6 +168,7 @@ public class ScriptRuntime { private AbstractShell mRootShell; private Supplier mShellSupplier; private ScreenMetrics mScreenMetrics = new ScreenMetrics(); + private Thread mThread; protected ScriptRuntime(Builder builder) { @@ -197,6 +198,7 @@ public class ScriptRuntime { timers = new Timers(bridges); loopers = new Loopers(this); events = new Events(uiHandler.getContext(), accessibilityBridge, this); + mThread = Thread.currentThread(); } public static void setApplicationContext(Context context) { @@ -248,12 +250,7 @@ public class ScriptRuntime { return ClipboardUtil.getClipOrEmpty(uiHandler.getContext()).toString(); } final VolatileDispose clip = new VolatileDispose<>(); - uiHandler.post(new Runnable() { - @Override - public void run() { - clip.setAndNotify(ClipboardUtil.getClipOrEmpty(uiHandler.getContext()).toString()); - } - }); + uiHandler.post(() -> clip.setAndNotify(ClipboardUtil.getClipOrEmpty(uiHandler.getContext()).toString())); return clip.blockedGetOrThrow(ScriptInterruptedException.class); } @@ -297,7 +294,7 @@ public class ScriptRuntime { } public void exit() { - Thread.currentThread().interrupt(); + mThread.interrupt(); throw new ScriptInterruptedException(); } @@ -320,6 +317,8 @@ public class ScriptRuntime { } public void onExit() { + //清除interrupt状态 + Thread.interrupted(); //悬浮窗需要第一时间关闭以免出现恶意脚本全屏悬浮窗屏蔽屏幕并且在exit中写死循环的问题 ignoresException(floaty::closeAll); try { @@ -335,11 +334,9 @@ public class ScriptRuntime { mRootShell = null; mShellSupplier = null; }); - ignoresException(() -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - images.releaseScreenCapturer(); - } - }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + ignoresException(images::releaseScreenCapturer); + } } private void ignoresException(Runnable r) { diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Events.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Events.java index 50245c72..dca6fef6 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Events.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Events.java @@ -2,19 +2,21 @@ package com.stardust.autojs.runtime.api; import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; +import android.content.Intent; import android.graphics.Point; import android.os.Build; import android.os.Handler; +import android.provider.Settings; +import android.support.annotation.RequiresApi; import android.view.KeyEvent; -import android.view.accessibility.AccessibilityEvent; import com.stardust.autojs.R; import com.stardust.autojs.core.accessibility.AccessibilityBridge; import com.stardust.autojs.core.eventloop.EventEmitter; +import com.stardust.autojs.core.looper.Loopers; import com.stardust.autojs.runtime.ScriptRuntime; import com.stardust.notification.Notification; import com.stardust.notification.NotificationListenerService; -import com.stardust.autojs.runtime.ScriptBridges; import com.stardust.autojs.runtime.exception.ScriptException; import com.stardust.autojs.core.inputevent.InputEventObserver; import com.stardust.autojs.core.inputevent.TouchObserver; @@ -41,6 +43,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver private Loopers mLoopers; private Handler mHandler; private boolean mListeningNotification = false; + private boolean mListeningToast = false; private ScriptRuntime mScriptRuntime; public Events(Context context, AccessibilityBridge accessibilityBridge, ScriptRuntime runtime) { @@ -136,20 +139,30 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver mTouchEventTimeout = touchEventTimeout; } - + @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void observeNotification() { + mScriptRuntime.requiresApi(18); if (mListeningNotification) return; - mAccessibilityBridge.ensureServiceEnabled(); mListeningNotification = true; ensureHandler(); mLoopers.waitWhenIdle(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 - && NotificationListenerService.getInstance() != null) { - NotificationListenerService.getInstance().addListener(this); - } else { - mAccessibilityBridge.getNotificationObserver().addNotificationListener(this); + if (NotificationListenerService.getInstance() == null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + mContext.startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)); + } + throw new ScriptException(mContext.getString(R.string.exception_notification_service_disabled)); } + NotificationListenerService.getInstance().addListener(this); + } + + public void observeToast() { + if (mListeningToast) + return; + mAccessibilityBridge.ensureServiceEnabled(); + mListeningToast = true; + ensureHandler(); + mLoopers.waitWhenIdle(true); mAccessibilityBridge.getNotificationObserver().addToastListener(this); } @@ -187,20 +200,17 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver @Override public void onKeyEvent(final int keyCode, final KeyEvent event) { - mHandler.post(new Runnable() { - @Override - public void run() { - String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase(); - emit(keyName, event); - if (event.getAction() == KeyEvent.ACTION_DOWN) { - emit(PREFIX_KEY_DOWN + keyName, event); - emit("key_down", keyCode, event); - } else if (event.getAction() == KeyEvent.ACTION_UP) { - emit(PREFIX_KEY_UP + keyName, event); - emit("key_up", keyCode, event); - } - emit("key", keyCode, event); + mHandler.post(() -> { + String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase(); + emit(keyName, event); + if (event.getAction() == KeyEvent.ACTION_DOWN) { + emit(PREFIX_KEY_DOWN + keyName, event); + emit("key_down", keyCode, event); + } else if (event.getAction() == KeyEvent.ACTION_UP) { + emit(PREFIX_KEY_UP + keyName, event); + emit("key_up", keyCode, event); } + emit("key", keyCode, event); }); } @@ -210,31 +220,16 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver return; } mLastTouchEventMillis = System.currentTimeMillis(); - mHandler.post(new Runnable() { - @Override - public void run() { - emit("touch", new Point(x, y)); - } - }); + mHandler.post(() -> emit("touch", new Point(x, y))); } public void onNotification(final Notification notification) { - mHandler.post(new Runnable() { - @Override - public void run() { - emit("notification", notification); - } - }); + mHandler.post(() -> emit("notification", notification)); } @Override public void onToast(final AccessibilityNotificationObserver.Toast toast) { - mHandler.post(new Runnable() { - @Override - public void run() { - emit("toast", toast); - } - }); + mHandler.post(() -> emit("toast", toast)); } } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Loopers.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Loopers.java deleted file mode 100644 index b54f436e..00000000 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Loopers.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.stardust.autojs.runtime.api; - -import android.os.Looper; -import android.os.MessageQueue; - -import com.stardust.autojs.runtime.ScriptRuntime; -import com.stardust.autojs.runtime.exception.ScriptInterruptedException; -import com.stardust.lang.ThreadCompat; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * Created by Stardust on 2017/7/29. - */ - -public class Loopers { - - public interface LooperQuitHandler { - boolean shouldQuit(); - } - - public volatile boolean waitWhenIdle = false; - private volatile Looper mServantLooper; - private static volatile ConcurrentHashMap sLoopers = new ConcurrentHashMap<>(); - private Timers mTimers; - private ScriptRuntime mScriptRuntime; - private LooperQuitHandler mLooperQuitHandler; - - public Loopers(ScriptRuntime runtime) { - mTimers = runtime.timers; - mScriptRuntime = runtime; - if (Looper.myLooper() == Looper.getMainLooper()) { - waitWhenIdle = true; - } - Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { - @Override - public boolean queueIdle() { - Looper l = Looper.myLooper(); - if (l != null && shouldQuitLooper()) { - if (mLooperQuitHandler != null && mLooperQuitHandler.shouldQuit()) { - l.quit(); - } - } - return true; - } - }); - } - - private boolean shouldQuitLooper() { - if (mTimers.hasPendingCallback()) { - return false; - } - return !waitWhenIdle; - } - - - private void initServantThread() { - new ThreadCompat(new Runnable() { - @Override - public void run() { - Looper.prepare(); - final Object lock = Loopers.this; - 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; - } - - public void quitServantLooper() { - if (mServantLooper == null) - return; - mServantLooper.quit(); - } - - public void waitWhenIdle(boolean b) { - waitWhenIdle = b; - } - - public void quitAll() { - quitServantLooper(); - } - - public static void prepare() { - if (Looper.myLooper() == Looper.getMainLooper()) - return; - if (Looper.myLooper() == null) - Looper.prepare(); - Looper l = Looper.myLooper(); - if (l != null) - sLoopers.put(Thread.currentThread(), l); - } - - public static void quitForThread(Thread thread) { - Looper looper = sLoopers.remove(thread); - if (looper != null) - looper.quit(); - } - - public void setLooperQuitHandler(LooperQuitHandler looperQuitHandler) { - mLooperQuitHandler = looperQuitHandler; - } -} diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Threads.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Threads.java index 02c86874..9e6d2a62 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Threads.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Threads.java @@ -1,21 +1,23 @@ package com.stardust.autojs.runtime.api; +import android.support.annotation.NonNull; + +import com.stardust.autojs.core.looper.TimerThread; import com.stardust.autojs.engine.RhinoJavaScriptEngine; import com.stardust.autojs.runtime.ScriptRuntime; -import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.concurrent.VolatileBox; import com.stardust.concurrent.VolatileDispose; -import com.stardust.lang.ThreadCompat; -import com.stardust.pio.PFile; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; /** * Created by Stardust on 2017/12/3. @@ -23,30 +25,38 @@ import java.util.concurrent.atomic.AtomicInteger; public class Threads { - private List mThreads = new ArrayList<>(); - private ScriptRuntime mScriptRuntime; + private final HashSet mThreads = new HashSet<>(); + private ScriptRuntime mRuntime; - public Threads(ScriptRuntime scriptRuntime) { - mScriptRuntime = scriptRuntime; + public Threads(ScriptRuntime runtime) { + mRuntime = runtime; } - public void start(Runnable runnable) { - ThreadCompat threadCompat = new ThreadCompat(() -> { - try { - ((RhinoJavaScriptEngine) mScriptRuntime.engines.myEngine()).createContext(); - runnable.run(); - } catch (Exception e) { - if (!ScriptInterruptedException.causedByInterrupted(e)) { - mScriptRuntime.console.error(e); + public TimerThread start(Runnable runnable) { + TimerThread thread = startThread(runnable); + synchronized (mThreads) { + mThreads.add(thread); + } + thread.start(); + return thread; + } + + @NonNull + private TimerThread startThread(Runnable runnable) { + return new TimerThread(mRuntime, mRuntime.timers.getMaxCallbackUptimeMillisForAllThreads(), + () -> { + ((RhinoJavaScriptEngine) mRuntime.engines.myEngine()).createContext(); + runnable.run(); } + ) { + @Override + protected void onExit() { + synchronized (mThreads) { + mThreads.remove(Thread.currentThread()); + } + super.onExit(); } - }); - mThreads.add(threadCompat); - threadCompat.start(); - } - - public VolatileBox variable() { - return new VolatileBox(); + }; } public VolatileDispose disposable() { @@ -65,16 +75,27 @@ public class Threads { return new ConcurrentHashMap(); } - public AtomicInteger atomicInt() { - return new AtomicInteger(); + public AtomicLong atomic() { + return new AtomicLong(); + } + + public AtomicLong atomic(long value) { + return new AtomicLong(value); } public void shutDownAll() { - for (ThreadCompat threadCompat : mThreads) { - threadCompat.interrupt(); + synchronized (mThreads) { + for (Thread thread : mThreads) { + thread.interrupt(); + } + mThreads.clear(); } - mThreads.clear(); } + public boolean hasRunningThreads() { + synchronized (mThreads) { + return !mThreads.isEmpty(); + } + } } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java index c18713e2..f3a06cb1 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java @@ -5,7 +5,10 @@ import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; +import com.stardust.autojs.core.looper.Timer; +import com.stardust.autojs.core.looper.TimerThread; import com.stardust.autojs.runtime.ScriptBridges; +import com.stardust.concurrent.VolatileBox; /** * Created by Stardust on 2017/7/21. @@ -13,99 +16,61 @@ import com.stardust.autojs.runtime.ScriptBridges; public class Timers { - private SparseArray mHandlerCallbacks = new SparseArray<>(); - private int mCallbackMaxId = 0; - private ScriptBridges mBridges; - private ThreadLocal mHandler = new ThreadLocal<>(); - private long mFutureCallbackUptimeMillis = 0; + private static final String LOG_TAG = "Timers"; + + private VolatileBox mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); + private Thread mMainThread; + private Timer mMainTimer; public Timers(ScriptBridges bridges) { - mBridges = bridges; + mMainThread = Thread.currentThread(); + mMainTimer = new Timer(bridges, mMaxCallbackUptimeMillisForAllThreads); } - private void ensureHandler() { - if (mHandler.get() == null) { - mHandler.set(new Handler()); + public VolatileBox getMaxCallbackUptimeMillisForAllThreads() { + return mMaxCallbackUptimeMillisForAllThreads; + } + + private Timer getTimerForCurrentThread() { + if (Thread.currentThread() == mMainThread) { + return mMainTimer; } + return TimerThread.getTimerForCurrentThread(); } - public int setTimeout(final Object callback, final long delay, final Object... args) { - ensureHandler(); - mCallbackMaxId++; - final int id = mCallbackMaxId; - Runnable r = () -> { - mBridges.callFunction(callback, null, args); - mHandlerCallbacks.remove(id); - }; - mHandlerCallbacks.put(id, r); - postDelayed(r, delay); - return id; + public int setTimeout(Object callback, long delay, Object... args) { + return getTimerForCurrentThread().setTimeout(callback, delay, args); } public boolean clearTimeout(int id) { - return clearCallback(id); + return getTimerForCurrentThread().clearTimeout(id); } - public int setInterval(final Object listener, final long interval, final Object... args) { - ensureHandler(); - mCallbackMaxId++; - final int id = mCallbackMaxId; - final Runnable r = new Runnable() { - @Override - public void run() { - if (mHandlerCallbacks.get(id) == null) - return; - mBridges.callFunction(listener, null, args); - postDelayed(this, interval); - } - }; - mHandlerCallbacks.put(id, r); - postDelayed(r, interval); - return id; - } - - private void postDelayed(Runnable r, long interval) { - long uptime = SystemClock.uptimeMillis() + interval; - mHandler.get().postAtTime(r, uptime); - mFutureCallbackUptimeMillis = Math.max(mFutureCallbackUptimeMillis, uptime); + public int setInterval(Object listener, long interval, Object... args) { + return getTimerForCurrentThread().setInterval(listener, interval, args); } public boolean clearInterval(int id) { - return clearCallback(id); + return getTimerForCurrentThread().clearInterval(id); } - public int setImmediate(final Object listener, final Object... args) { - ensureHandler(); - mCallbackMaxId++; - final int id = mCallbackMaxId; - Runnable r = new Runnable() { - @Override - public void run() { - mBridges.callFunction(listener, null, args); - mHandlerCallbacks.remove(id); - } - }; - mHandlerCallbacks.put(id, r); - postDelayed(r, 0); - return id; + public int setImmediate(Object listener, Object... args) { + return getTimerForCurrentThread().setImmediate(listener, args); } public boolean clearImmediate(int id) { - return clearCallback(id); + return getTimerForCurrentThread().clearImmediate(id); } - private boolean clearCallback(int id) { - Runnable callback = mHandlerCallbacks.get(id); - if (callback != null) { - mHandler.get().removeCallbacks(callback); - mHandlerCallbacks.remove(id); - return true; + public boolean hasPendingCallbacks() { + //如果是脚本主线程,则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。 + if (mMainThread == Thread.currentThread()) { + Log.d(LOG_TAG, "[main thread]hasPendingCallbacks:" + (mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis())); + Log.d(LOG_TAG, "mMaxCallbackUptimeMillisForAllThreads:" + mMaxCallbackUptimeMillisForAllThreads.get()); + return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis(); } - return false; + //否则检查当前线程的定时回调 + return getTimerForCurrentThread().hasPendingCallbacks(); } - - public boolean hasPendingCallback() { - return mFutureCallbackUptimeMillis > SystemClock.uptimeMillis(); - } } diff --git a/autojs/src/main/res/values/strings.xml b/autojs/src/main/res/values/strings.xml index 6da7f8ed..b4727fa2 100644 --- a/autojs/src/main/res/values/strings.xml +++ b/autojs/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ AutoJs 按键监听未启用,请在软件设置中开启 沒有修改系統设置权限 + 通知服务未运行,请重新启用通知权限 diff --git a/common/src/main/java/com/stardust/concurrent/Value.java b/common/src/main/java/com/stardust/concurrent/Value.java new file mode 100644 index 00000000..628e3729 --- /dev/null +++ b/common/src/main/java/com/stardust/concurrent/Value.java @@ -0,0 +1,27 @@ +package com.stardust.concurrent; + +/** + * Created by Stardust on 2017/12/27. + */ + +public class Value { + + private T mValue; + + public Value(T value) { + mValue = value; + } + + public Value() { + } + + public T get() { + return mValue; + } + + public void set(T value) { + mValue = value; + } + + +} diff --git a/common/src/main/java/com/stardust/lang/ThreadCompat.java b/common/src/main/java/com/stardust/lang/ThreadCompat.java index c6e97dd5..3bac30f3 100644 --- a/common/src/main/java/com/stardust/lang/ThreadCompat.java +++ b/common/src/main/java/com/stardust/lang/ThreadCompat.java @@ -10,14 +10,14 @@ import java.util.WeakHashMap; public class ThreadCompat extends Thread { - private static volatile WeakHashMap interruptStatus = new WeakHashMap<>(); + // private static volatile WeakHashMap interruptStatus = new WeakHashMap<>(); public ThreadCompat() { init(); } private void init() { - interruptStatus.put(this, false); + // interruptStatus.put(this, false); } public ThreadCompat(Runnable target) { @@ -59,23 +59,23 @@ public class ThreadCompat extends Thread { public void run() { try { super.run(); - interruptStatus.remove(this); + // interruptStatus.remove(this); } catch (Throwable e) { - interruptStatus.remove(this); + // interruptStatus.remove(this); throw e; } } @Override public boolean isInterrupted() { - Boolean isInterrupted = interruptStatus.get(this); - return super.isInterrupted() || isInterrupted == null || isInterrupted; + //Boolean isInterrupted = interruptStatus.get(this); + return super.isInterrupted();// || isInterrupted == null || isInterrupted; } @Override public void interrupt() { super.interrupt(); - interruptStatus.remove(this); + //interruptStatus.put(this, true); } } diff --git a/common/src/main/java/com/stardust/pio/PFiles.java b/common/src/main/java/com/stardust/pio/PFiles.java index 17a40abb..e77ca558 100644 --- a/common/src/main/java/com/stardust/pio/PFiles.java +++ b/common/src/main/java/com/stardust/pio/PFiles.java @@ -76,6 +76,10 @@ public class PFiles { return false; } + public static boolean createWithDirs(String path) { + return createIfNotExists(path); + } + public static boolean exists(String path) { return new File(path).exists(); } @@ -212,6 +216,7 @@ public class PFiles { } public static void append(String path, String text) { + create(path); try { write(new FileOutputStream(path, true), text); } catch (FileNotFoundException e) { @@ -221,6 +226,7 @@ public class PFiles { public static void append(String path, String text, String encoding) { + create(path); try { write(new FileOutputStream(path, true), text, encoding); } catch (FileNotFoundException e) { @@ -238,6 +244,7 @@ public class PFiles { } public static void appendBytes(String path, byte[] bytes) { + create(path); try { writeBytes(new FileOutputStream(path, true), bytes); } catch (IOException e) {