From 3647cef81b296311419c6f1dcdcdbe1be81635fb Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Thu, 28 Dec 2017 21:21:41 +0800 Subject: [PATCH] api: threads, events --- .../assets/sample/多线程/多线程按键监听.js | 23 ++ .../scriptdroid/ui/edit/EditActivity.java | 7 + .../scriptdroid/ui/edit/EditorView.java | 20 +- autojs/src/main/assets/init.js | 3 +- autojs/src/main/assets/modules/__threads__.js | 12 + .../autojs/core/eventloop/EventEmitter.java | 14 +- .../autojs/core/looper/MainThreadProxy.java | 225 ++++++++++++++++++ .../stardust/autojs/core/looper/Timer.java | 1 + .../autojs/core/looper/TimerThread.java | 45 +++- .../autojs/runtime/ScriptRuntime.java | 6 +- .../stardust/autojs/runtime/api/Events.java | 11 + .../stardust/autojs/runtime/api/Threads.java | 40 +++- .../stardust/autojs/runtime/api/Timers.java | 21 +- 13 files changed, 394 insertions(+), 34 deletions(-) create mode 100644 app/src/main/assets/sample/多线程/多线程按键监听.js create mode 100644 autojs/src/main/assets/modules/__threads__.js create mode 100644 autojs/src/main/java/com/stardust/autojs/core/looper/MainThreadProxy.java diff --git a/app/src/main/assets/sample/多线程/多线程按键监听.js b/app/src/main/assets/sample/多线程/多线程按键监听.js new file mode 100644 index 00000000..88f07921 --- /dev/null +++ b/app/src/main/assets/sample/多线程/多线程按键监听.js @@ -0,0 +1,23 @@ +auto(); + +threads.start(function(){ + //在子线程中调用observeKey()从而使按键事件处理在子线程执行 + events.observeKey(); + events.on("key_down", function(keyCode, events){ + //音量键关闭脚本 + if(keyCode == keys.volume_up){ + exit(); + } + }); +}); + +toast("音量上键关闭脚本"); + +events.on("exit", function(){ + toast("脚本已结束"); +}); + +while(true){ + log("脚本运行中..."); + sleep(2000); +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java index 9a6e54bc..05826e79 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java @@ -88,6 +88,13 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega super.onActionModeStarted(mode); } + @Override + public void onBackPressed() { + if (!mEditor.onBackPressed()) { + super.onBackPressed(); + } + } + @Override public void finish() { if (mEditor.isTextChanged()) { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditorView.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditorView.java index 3b7185c3..591874b0 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditorView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditorView.java @@ -91,7 +91,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC FunctionsKeyboardView mFunctionsKeyboard; @ViewById(R.id.docs) - EWebView mEWebView; + EWebView mDocsWebView; @ViewById(R.id.drawer_layout) DrawerLayout mDrawerLayout; @@ -201,8 +201,8 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC setUpInputMethodEnhancedBar(); setUpFunctionsKeyboard(); setMenuItemStatus(R.id.save, false); - mEWebView.getWebView().getSettings().setDisplayZoomControls(true); - mEWebView.getWebView().loadUrl(Pref.getDocumentationUrl() + "index.html"); + mDocsWebView.getWebView().getSettings().setDisplayZoomControls(true); + mDocsWebView.getWebView().loadUrl(Pref.getDocumentationUrl() + "index.html"); } @@ -257,6 +257,18 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC mShowFunctionsButton.setColorFilter(textColor); } + public boolean onBackPressed() { + if (mDrawerLayout.isDrawerOpen(Gravity.START)) { + if (mDocsWebView.getWebView().canGoBack()) { + mDocsWebView.getWebView().goBack(); + } else { + mDrawerLayout.closeDrawer(Gravity.START); + } + return true; + } + return false; + } + @Click(R.id.run) public void runAndSaveFileIfNeeded() { @@ -414,7 +426,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC .title(title) .url(absUrl) .pinToLeft(v -> { - mEWebView.getWebView().loadUrl(absUrl); + mDocsWebView.getWebView().loadUrl(absUrl); mDrawerLayout.openDrawer(Gravity.START); }) .show(); diff --git a/autojs/src/main/assets/init.js b/autojs/src/main/assets/init.js index 68cc23cb..e86b6e00 100644 --- a/autojs/src/main/assets/init.js +++ b/autojs/src/main/assets/init.js @@ -49,7 +49,6 @@ var __that__ = this; var Promise = require('promise.js'); var JSON = require('__json2__.js'); var util = require('__util__.js'); -var threads = __runtime__.threads; var device = __runtime__.device; var __asGlobal__ = function(obj, functions){ @@ -65,7 +64,7 @@ require("__general__")(__runtime__, this); (function(scope){ var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui', - "images", "timers", "events", "engines", "RootAutomator", "http", "storages", "floaty"]; + "images", "timers", "threads", "events", "engines", "RootAutomator", "http", "storages", "floaty"]; var len = modules.length; for(var i = 0; i < len; i++) { var m = modules[i]; diff --git a/autojs/src/main/assets/modules/__threads__.js b/autojs/src/main/assets/modules/__threads__.js new file mode 100644 index 00000000..01bc05f4 --- /dev/null +++ b/autojs/src/main/assets/modules/__threads__.js @@ -0,0 +1,12 @@ + +module.exports = function(__runtime__, scope){ + var threads = Object.create(__runtime__.threads); + + + scope.sync = function(func, lock){ + lock = lock || null; + return new org.mozilla.javascript.Synchronizer(func, lock); + } + + return threads; +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/core/eventloop/EventEmitter.java b/autojs/src/main/java/com/stardust/autojs/core/eventloop/EventEmitter.java index 701dc541..7d6262e0 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/eventloop/EventEmitter.java +++ b/autojs/src/main/java/com/stardust/autojs/core/eventloop/EventEmitter.java @@ -3,6 +3,7 @@ package com.stardust.autojs.core.eventloop; import android.support.annotation.NonNull; +import com.stardust.autojs.core.looper.Timer; import com.stardust.autojs.runtime.ScriptBridges; import com.stardust.autojs.runtime.exception.ScriptException; @@ -19,6 +20,7 @@ import java.util.concurrent.CopyOnWriteArrayList; public class EventEmitter { + private static class ListenerWrapper { Object listener; boolean isOnce; @@ -52,7 +54,11 @@ public class EventEmitter { Iterator listenerIterator = mListenerWrappers.iterator(); while (listenerIterator.hasNext()) { ListenerWrapper listenerWrapper = listenerIterator.next(); - mBridges.callFunction(listenerWrapper.listener, EventEmitter.this, args); + if (mTimer != null) { + mTimer.setImmediate(listenerWrapper.listener, args); + } else { + mBridges.callFunction(listenerWrapper.listener, EventEmitter.this, args); + } if (listenerWrapper.isOnce) { listenerIterator.remove(); } @@ -93,11 +99,17 @@ public class EventEmitter { public static int defaultMaxListeners = 10; private int mMaxListeners = defaultMaxListeners; protected ScriptBridges mBridges; + private Timer mTimer; public EventEmitter(ScriptBridges bridges) { mBridges = bridges; } + public EventEmitter(ScriptBridges bridges, Timer timer) { + mTimer = timer; + mBridges = bridges; + } + public EventEmitter once(String eventName, Object listener) { getListeners(eventName).add(listener, true); return this; diff --git a/autojs/src/main/java/com/stardust/autojs/core/looper/MainThreadProxy.java b/autojs/src/main/java/com/stardust/autojs/core/looper/MainThreadProxy.java new file mode 100644 index 00000000..44b033c6 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/MainThreadProxy.java @@ -0,0 +1,225 @@ +package com.stardust.autojs.core.looper; + +import android.content.pm.PackageManager; + +import com.stardust.autojs.runtime.ScriptRuntime; + +import java.util.Map; + +/** + * Created by Stardust on 2017/12/28. + */ + +public class MainThreadProxy { + + private final Thread mThread; + private ScriptRuntime mRuntime; + + public MainThreadProxy(Thread thread, ScriptRuntime runtime) { + mThread = thread; + mRuntime = runtime; + } + + public int setTimeout(Object callback, long delay, Object... args) { + return getMainTimer().setTimeout(callback, delay, args); + } + + private Timer getMainTimer() { + return mRuntime.timers.getMainTimer(); + } + + public boolean clearTimeout(int id) { + return getMainTimer().clearTimeout(id); + } + + public int setInterval(Object listener, long interval, Object... args) { + return getMainTimer().setInterval(listener, interval, args); + } + + public boolean clearInterval(int id) { + return getMainTimer().clearInterval(id); + } + + public int setImmediate(Object listener, Object... args) { + return getMainTimer().setImmediate(listener, args); + } + + public boolean clearImmediate(int id) { + return getMainTimer().clearImmediate(id); + } + + public static Thread currentThread() { + return Thread.currentThread(); + } + + public static void yield() { + Thread.yield(); + } + + public static void sleep(long millis) throws InterruptedException { + Thread.sleep(millis); + } + + public static void sleep(long millis, int nanos) throws InterruptedException { + Thread.sleep(millis, nanos); + } + + public void start() { + mThread.start(); + } + + public void run() { + mThread.run(); + } + + @Deprecated + public void stop() { + mThread.stop(); + } + + @Deprecated + public void stop(Throwable obj) { + mThread.stop(obj); + } + + public void interrupt() { + mThread.interrupt(); + } + + public static boolean interrupted() { + return Thread.interrupted(); + } + + public boolean isInterrupted() { + return mThread.isInterrupted(); + } + + @Deprecated + public void destroy() { + mThread.destroy(); + } + + public boolean isAlive() { + return mThread.isAlive(); + } + + @Deprecated + public void suspend() { + mThread.suspend(); + } + + @Deprecated + public void resume() { + mThread.resume(); + } + + public void setPriority(int newPriority) { + mThread.setPriority(newPriority); + } + + public int getPriority() { + return mThread.getPriority(); + } + + public void setName(String name) { + mThread.setName(name); + } + + public String getName() { + return mThread.getName(); + } + + public ThreadGroup getThreadGroup() { + return mThread.getThreadGroup(); + } + + public static int activeCount() { + return Thread.activeCount(); + } + + public static int enumerate(Thread[] tarray) { + return Thread.enumerate(tarray); + } + + @Deprecated + public int countStackFrames() { + return mThread.countStackFrames(); + } + + public void join(long millis) throws InterruptedException { + mThread.join(millis); + } + + public void join(long millis, int nanos) throws InterruptedException { + mThread.join(millis, nanos); + } + + public void join() throws InterruptedException { + mThread.join(); + } + + public static void dumpStack() { + Thread.dumpStack(); + } + + public void setDaemon(boolean on) { + mThread.setDaemon(on); + } + + public boolean isDaemon() { + return mThread.isDaemon(); + } + + public void checkAccess() { + mThread.checkAccess(); + } + + public ClassLoader getContextClassLoader() { + return mThread.getContextClassLoader(); + } + + public void setContextClassLoader(ClassLoader cl) { + mThread.setContextClassLoader(cl); + } + + public static boolean holdsLock(Object obj) { + return Thread.holdsLock(obj); + } + + public StackTraceElement[] getStackTrace() { + return mThread.getStackTrace(); + } + + public static Map getAllStackTraces() { + return Thread.getAllStackTraces(); + } + + public long getId() { + return mThread.getId(); + } + + public Thread.State getState() { + return mThread.getState(); + } + + @Override + public String toString() { + return mThread.toString(); + } + + public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + Thread.setDefaultUncaughtExceptionHandler(eh); + } + + public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { + return Thread.getDefaultUncaughtExceptionHandler(); + } + + public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + return mThread.getUncaughtExceptionHandler(); + } + + public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + mThread.setUncaughtExceptionHandler(eh); + } +} 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 index a537354b..765318a0 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/looper/Timer.java +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/Timer.java @@ -109,4 +109,5 @@ public class Timer { 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 index 65b48dc6..a1cfcd26 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/looper/TimerThread.java +++ b/autojs/src/main/java/com/stardust/autojs/core/looper/TimerThread.java @@ -11,6 +11,8 @@ import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.concurrent.VolatileBox; import com.stardust.lang.ThreadCompat; +import org.mozilla.javascript.NativeArray; + import java.util.concurrent.ConcurrentHashMap; /** @@ -25,6 +27,8 @@ public class TimerThread extends ThreadCompat { private final VolatileBox mMaxCallbackUptimeMillisForAllThreads; private final ScriptRuntime mRuntime; private Runnable mTarget; + private boolean mRunning = false; + private final Object mRunningLock = new Object(); public TimerThread(ScriptRuntime runtime, VolatileBox maxCallbackUptimeMillisForAllThreads, Runnable target) { super(target); @@ -38,6 +42,7 @@ public class TimerThread extends ThreadCompat { mRuntime.loopers.prepare(); mTimer = new Timer(mRuntime.bridges, mMaxCallbackUptimeMillisForAllThreads); sTimerMap.put(Thread.currentThread(), mTimer); + notifyRunning(); new Handler().post(mTarget); try { Looper.loop(); @@ -47,10 +52,18 @@ public class TimerThread extends ThreadCompat { } } finally { onExit(); + mTimer = null; sTimerMap.remove(Thread.currentThread(), mTimer); } } + private void notifyRunning() { + synchronized (mRunningLock) { + mRunning = true; + mRunningLock.notifyAll(); + } + } + @CallSuper protected void onExit() { mRuntime.loopers.notifyThreadExit(this); @@ -65,26 +78,46 @@ public class TimerThread extends ThreadCompat { } public int setTimeout(Object callback, long delay, Object... args) { - return mTimer.setTimeout(callback, delay, args); + return getTimer().setTimeout(callback, delay, args); + } + + private Timer getTimer() { + if (mTimer == null) { + throw new IllegalStateException("thread is not alive"); + } + return mTimer; } public boolean clearTimeout(int id) { - return mTimer.clearTimeout(id); + return getTimer().clearTimeout(id); } public int setInterval(Object listener, long interval, Object... args) { - return mTimer.setInterval(listener, interval, args); + return getTimer().setInterval(listener, interval, args); } public boolean clearInterval(int id) { - return mTimer.clearInterval(id); + return getTimer().clearInterval(id); } public int setImmediate(Object listener, Object... args) { - return mTimer.setImmediate(listener, args); + return getTimer().setImmediate(listener, args); } public boolean clearImmediate(int id) { - return mTimer.clearImmediate(id); + return getTimer().clearImmediate(id); + } + + public void waitFor() throws InterruptedException { + synchronized (mRunningLock) { + if (mRunning) + return; + mRunningLock.wait(); + } + } + + @Override + public String toString() { + return "Thread[" + getName() + "," + getPriority() + "]"; } } 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 441741a7..51dd3ea1 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java @@ -153,7 +153,7 @@ public class ScriptRuntime { public final Engines engines; @ScriptVariable - public final Threads threads; + public Threads threads; @ScriptVariable public final Floaty floaty; @@ -187,7 +187,6 @@ public class ScriptRuntime { } engines = new Engines(builder.mEngineService); dialogs = new Dialogs(app, uiHandler, bridges); - threads = new Threads(this); device = new Device(uiHandler.getContext()); floaty = new Floaty(uiHandler, ui); } @@ -195,7 +194,8 @@ public class ScriptRuntime { public void init() { if (loopers != null) throw new IllegalStateException("already initialized"); - timers = new Timers(bridges); + threads = new Threads(this); + timers = new Timers(bridges, threads); loopers = new Loopers(this); events = new Events(uiHandler.getContext(), accessibilityBridge, this); mThread = Thread.currentThread(); 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 dca6fef6..d864d0ef 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 @@ -14,6 +14,8 @@ 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.core.looper.MainThreadProxy; +import com.stardust.autojs.core.looper.Timer; import com.stardust.autojs.runtime.ScriptRuntime; import com.stardust.notification.Notification; import com.stardust.notification.NotificationListenerService; @@ -58,6 +60,15 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver return new EventEmitter(mBridges); } + public EventEmitter emitter(Thread thread) { + Timer timer = mScriptRuntime.timers.getTimerForThread(thread); + return new EventEmitter(mBridges, timer); + } + + public EventEmitter emitter(MainThreadProxy mainThreadProxy) { + return new EventEmitter(mBridges, mScriptRuntime.timers.getMainTimer()); + } + public void observeKey() { if (mListeningKey) return; 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 9e6d2a62..0e2e1b06 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 @@ -2,6 +2,7 @@ package com.stardust.autojs.runtime.api; import android.support.annotation.NonNull; +import com.stardust.autojs.core.looper.MainThreadProxy; import com.stardust.autojs.core.looper.TimerThread; import com.stardust.autojs.engine.RhinoJavaScriptEngine; import com.stardust.autojs.runtime.ScriptRuntime; @@ -14,10 +15,15 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Created by Stardust on 2017/12/3. @@ -27,15 +33,33 @@ public class Threads { private final HashSet mThreads = new HashSet<>(); private ScriptRuntime mRuntime; + private final Thread mMainThread; + private MainThreadProxy mMainThreadProxy; + private int mSpawnCount = 0; public Threads(ScriptRuntime runtime) { mRuntime = runtime; + mMainThread = Thread.currentThread(); + mMainThreadProxy = new MainThreadProxy(Thread.currentThread(), mRuntime); + } + + public Thread getMainThread() { + return mMainThread; + } + + public Object currentThread() { + Thread thread = Thread.currentThread(); + if (thread == mMainThread) + return mMainThreadProxy; + return thread; } public TimerThread start(Runnable runnable) { TimerThread thread = startThread(runnable); synchronized (mThreads) { mThreads.add(thread); + thread.setName(thread.getName() + " (Spawn-" + mSpawnCount + ")"); + mSpawnCount++; } thread.start(); return thread; @@ -63,24 +87,16 @@ public class Threads { return new VolatileDispose(); } - public List list() { - return Collections.synchronizedList(new ArrayList<>()); - } - - public Set set() { - return new ConcurrentSkipListSet(); - } - - public Map map() { - return new ConcurrentHashMap(); + public AtomicLong atomic(long value) { + return new AtomicLong(value); } public AtomicLong atomic() { return new AtomicLong(); } - public AtomicLong atomic(long value) { - return new AtomicLong(value); + public Lock lock() { + return new ReentrantLock(); } public void shutDownAll() { 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 f3a06cb1..bce4b6e1 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 @@ -19,12 +19,17 @@ public class Timers { private static final String LOG_TAG = "Timers"; private VolatileBox mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); - private Thread mMainThread; + private Threads mThreads; private Timer mMainTimer; - public Timers(ScriptBridges bridges) { - mMainThread = Thread.currentThread(); + + public Timers(ScriptBridges bridges, Threads threads) { mMainTimer = new Timer(bridges, mMaxCallbackUptimeMillisForAllThreads); + mThreads = threads; + } + + public Timer getMainTimer() { + return mMainTimer; } public VolatileBox getMaxCallbackUptimeMillisForAllThreads() { @@ -32,10 +37,14 @@ public class Timers { } private Timer getTimerForCurrentThread() { - if (Thread.currentThread() == mMainThread) { + return getTimerForThread(Thread.currentThread()); + } + + public Timer getTimerForThread(Thread thread) { + if (thread == mThreads.getMainThread()) { return mMainTimer; } - return TimerThread.getTimerForCurrentThread(); + return TimerThread.getTimerForThread(thread); } public int setTimeout(Object callback, long delay, Object... args) { @@ -64,7 +73,7 @@ public class Timers { public boolean hasPendingCallbacks() { //如果是脚本主线程,则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。 - if (mMainThread == Thread.currentThread()) { + if (mThreads.getMainThread() == 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();