api: threads, files

This commit is contained in:
hyb1996
2017-12-27 16:47:32 +08:00
parent 25e9c90bf4
commit 729958d419
26 changed files with 652 additions and 339 deletions

View File

@@ -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;
}

View File

@@ -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){

View File

@@ -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<StardustConsole.Log> 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();
});
}

View File

@@ -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();
}

View File

@@ -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<Thread, Looper> 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();
}
}

View File

@@ -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<Boolean> 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);
}
}

View File

@@ -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<Runnable> mHandlerCallbacks = new SparseArray<>();
private int mCallbackMaxId = 0;
private ScriptBridges mBridges;
private Handler mHandler;
private long mMaxCallbackUptimeMillis = 0;
private final VolatileBox<Long> mMaxCallbackMillisForAllThread;
public Timer(ScriptBridges bridges, VolatileBox<Long> 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();
}
}

View File

@@ -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<Thread, Timer> sTimerMap = new ConcurrentHashMap<>();
private Timer mTimer;
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads;
private final ScriptRuntime mRuntime;
private Runnable mTarget;
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.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);
}
}

View File

@@ -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();
}

View File

@@ -40,7 +40,6 @@ public interface ScriptEngine<S extends ScriptSource> {
Object getTag(String key);
/**
* @hide
*/
@@ -87,6 +86,10 @@ public interface ScriptEngine<S extends ScriptSource> {
}
}
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");

View File

@@ -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;
}
});

View File

@@ -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<AbstractShell> 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<String> 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) {

View File

@@ -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));
}
}

View File

@@ -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<Thread, Looper> 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;
}
}

View File

@@ -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<ThreadCompat> mThreads = new ArrayList<>();
private ScriptRuntime mScriptRuntime;
private final HashSet<Thread> 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();
}
}
}

View File

@@ -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<Runnable> mHandlerCallbacks = new SparseArray<>();
private int mCallbackMaxId = 0;
private ScriptBridges mBridges;
private ThreadLocal<Handler> mHandler = new ThreadLocal<>();
private long mFutureCallbackUptimeMillis = 0;
private static final String LOG_TAG = "Timers";
private VolatileBox<Long> 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<Long> 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();
}
}

View File

@@ -16,6 +16,7 @@
<string name="_app_name">AutoJs</string>
<string name="text_should_enable_key_observing">按键监听未启用,请在软件设置中开启</string>
<string name="no_write_settings_permissin">沒有修改系統设置权限</string>
<string name="exception_notification_service_disabled">通知服务未运行,请重新启用通知权限</string>
</resources>