6.3.3 - pre - 修复 VSCode 插件问题
This commit is contained in:
@@ -4,6 +4,18 @@
|
||||
|
||||
******
|
||||
|
||||
# v6.3.3
|
||||
|
||||
###### 2023/07/12
|
||||
|
||||
* `修复` VSCode 插件在脚本字符总长度超过四位十进制数时无法解析数据的问题 _[`issue #91`](http://issues.autojs6.com/91)_ _[`issue #93`](http://issues.autojs6.com/93)_
|
||||
* `修复` VSCode 插件无法正常保存文件的问题 _[`issue #92`](http://issues.autojs6.com/91)_ _[`issue #94`](http://issues.autojs6.com/93)_
|
||||
* `修复` 异步环境 (by [aiselp](https://github.com/aiselp)) _[`pr #75`](http://pr.autojs6.com/75)_
|
||||
* `优化` 调整模块作用域 (by [aiselp](https://github.com/aiselp)) _[`pr #75`](http://pr.autojs6.com/75)_ _[`pr #78`](http://pr.autojs6.com/78)_
|
||||
* `优化` 定时器调用性能 (by [aiselp](https://github.com/aiselp)) _[`pr #75`](http://pr.autojs6.com/75)_ _[`pr #78`](http://pr.autojs6.com/78)_
|
||||
* `优化` 移除发行版本应用启动时的签名校验 (by [LZX284](https://github.com/LZX284)) _[`pr #81`](http://pr.autojs6.com/81)_
|
||||
* `优化` 升级 Gradle 版本 8.2 -> 8.2.1
|
||||
|
||||
# v6.3.2
|
||||
|
||||
###### 2023/07/06
|
||||
@@ -34,6 +46,7 @@
|
||||
* `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_
|
||||
* `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_
|
||||
* `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭)
|
||||
* `优化` findMultiColors 方法内部实现 (by [LYS](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_
|
||||
* `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件
|
||||
* `优化` 升级 Leakcanary 版本 2.11 -> 2.12
|
||||
* `优化` 升级 Android Analytics 版本 14.2.0 -> 14.3.0
|
||||
@@ -382,7 +395,7 @@
|
||||
* `修复` toast.dismiss() 可能无效的问题
|
||||
* `修复` 客户端模式及服务端模式开关可能无法正常工作的问题
|
||||
* `修复` 客户端模式及服务端模式开关状态不能正常刷新的问题
|
||||
* `修复` Android 7.x 解析 UI 模式 text 元素异常 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) _[`issue #4`](http://issues.autojs6.com/4)_ _[`#9`](http://issues.autojs6.com/9)_
|
||||
* `修复` Android 7.x 解析 UI 模式 text 元素异常 (Ref to [TonyJiangWJ](https://github.com/TonyJiangWJ)) _[`issue #4`](http://issues.autojs6.com/4)_ _[`issue #9`](http://issues.autojs6.com/9)_
|
||||
* `优化` 忽略 sleep() 的 ScriptInterruptedException 异常
|
||||
* `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2
|
||||
* `优化` 升级 Androidx AppCompat 版本 1.4.0 -> 1.4.1
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
package org.autojs.autojs.core.looper;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.autojs.autojs.lang.ThreadCompat;
|
||||
import org.autojs.autojs.rhino.AutoJsContext;
|
||||
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||
import org.autojs.autojs.runtime.api.Threads;
|
||||
import org.autojs.autojs.runtime.api.Timers;
|
||||
import org.autojs.autojs.runtime.exception.ScriptInterruptedException;
|
||||
import org.mozilla.javascript.Context;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/7/29.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public class Loopers implements MessageQueue.IdleHandler {
|
||||
|
||||
private static final String LOG_TAG = "Loopers";
|
||||
|
||||
public interface LooperQuitHandler {
|
||||
boolean shouldQuit();
|
||||
}
|
||||
|
||||
private static final Runnable EMPTY_RUNNABLE = () -> {
|
||||
};
|
||||
|
||||
private final ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<>() {
|
||||
@Nullable
|
||||
@Override
|
||||
protected Boolean initialValue() {
|
||||
return Looper.myLooper() == Looper.getMainLooper();
|
||||
}
|
||||
};
|
||||
private final ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<>() {
|
||||
@Nullable
|
||||
@Override
|
||||
protected HashSet<Integer> initialValue() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
};
|
||||
private final ThreadLocal<Integer> maxWaitId = new ThreadLocal<>() {
|
||||
@Nullable
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
private final ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHandlers = new ThreadLocal<>();
|
||||
private volatile Looper mServantLooper;
|
||||
private final Timers mTimers;
|
||||
private LooperQuitHandler mMainLooperQuitHandler;
|
||||
private final Handler mMainHandler;
|
||||
private final Looper mMainLooper;
|
||||
private final Threads mThreads;
|
||||
private final MessageQueue mMainMessageQueue;
|
||||
|
||||
public Loopers(ScriptRuntime runtime) {
|
||||
mTimers = runtime.timers;
|
||||
mThreads = runtime.threads;
|
||||
prepare();
|
||||
mMainLooper = Looper.myLooper();
|
||||
mMainHandler = new Handler();
|
||||
mMainMessageQueue = Looper.myQueue();
|
||||
}
|
||||
|
||||
|
||||
public Looper getMainLooper() {
|
||||
return mMainLooper;
|
||||
}
|
||||
|
||||
public void addLooperQuitHandler(LooperQuitHandler handler) {
|
||||
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||
if (handlers == null) {
|
||||
handlers = new CopyOnWriteArrayList<>();
|
||||
looperQuitHandlers.set(handlers);
|
||||
}
|
||||
handlers.add(handler);
|
||||
}
|
||||
|
||||
public boolean removeLooperQuitHandler(LooperQuitHandler handler) {
|
||||
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||
return handlers != null && handlers.remove(handler);
|
||||
}
|
||||
|
||||
private boolean shouldQuitLooper() {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
return true;
|
||||
}
|
||||
if (mTimers.hasPendingCallbacks()) {
|
||||
return false;
|
||||
}
|
||||
if (waitWhenIdle.get() || !waitIds.get().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (((AutoJsContext) Context.getCurrentContext()).hasPendingContinuation()) {
|
||||
return false;
|
||||
}
|
||||
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||
if (handlers == null) {
|
||||
return true;
|
||||
}
|
||||
for (LooperQuitHandler handler : handlers) {
|
||||
if (!handler.shouldQuit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void initServantThread() {
|
||||
final Object lock = Loopers.this;
|
||||
new ThreadCompat(() -> {
|
||||
Looper.prepare();
|
||||
mServantLooper = Looper.myLooper();
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
Looper.loop();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public Looper getServantLooper() {
|
||||
if (mServantLooper == null) {
|
||||
initServantThread();
|
||||
synchronized (this) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new ScriptInterruptedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mServantLooper;
|
||||
}
|
||||
|
||||
private void quitServantLooper() {
|
||||
if (mServantLooper == null)
|
||||
return;
|
||||
mServantLooper.quit();
|
||||
}
|
||||
|
||||
public int waitWhenIdle() {
|
||||
int id = maxWaitId.get();
|
||||
Log.d(LOG_TAG, "waitWhenIdle: " + id);
|
||||
maxWaitId.set(id + 1);
|
||||
waitIds.get().add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
public void doNotWaitWhenIdle(int waitId) {
|
||||
Log.d(LOG_TAG, "doNotWaitWhenIdle: " + waitId);
|
||||
waitIds.get().remove(waitId);
|
||||
}
|
||||
|
||||
public void waitWhenIdle(boolean b) {
|
||||
waitWhenIdle.set(b);
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
quitServantLooper();
|
||||
mMainMessageQueue.removeIdleHandler(this);
|
||||
}
|
||||
|
||||
public void setMainLooperQuitHandler(LooperQuitHandler mainLooperQuitHandler) {
|
||||
mMainLooperQuitHandler = mainLooperQuitHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean queueIdle() {
|
||||
Looper l = Looper.myLooper();
|
||||
if (l == null)
|
||||
return true;
|
||||
if (l == mMainLooper) {
|
||||
Log.d(LOG_TAG, "main looper queueIdle");
|
||||
if (shouldQuitLooper() && !mThreads.hasRunningThreads() &&
|
||||
mMainLooperQuitHandler != null && mMainLooperQuitHandler.shouldQuit()) {
|
||||
Log.d(LOG_TAG, "main looper quit");
|
||||
l.quit();
|
||||
}
|
||||
} else {
|
||||
Log.d(LOG_TAG, "looper queueIdle: " + l);
|
||||
if (shouldQuitLooper()) {
|
||||
l.quit();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
if (Looper.myLooper() == null)
|
||||
LooperHelper.prepare();
|
||||
Looper.myQueue().addIdleHandler(this);
|
||||
}
|
||||
|
||||
public void notifyThreadExit(TimerThread thread) {
|
||||
Log.d(LOG_TAG, "notifyThreadExit: " + thread);
|
||||
//当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程),
|
||||
//此时通过向主线程发送一个空的Runnable,主线程执行完这个Runnable后会触发IdleHandler,从而检查自身是否退出
|
||||
mMainHandler.post(EMPTY_RUNNABLE);
|
||||
}
|
||||
}
|
||||
190
app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt
Normal file
190
app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt
Normal file
@@ -0,0 +1,190 @@
|
||||
package org.autojs.autojs.core.looper
|
||||
|
||||
import android.os.Looper
|
||||
import android.os.MessageQueue
|
||||
import android.util.Log
|
||||
import org.autojs.autojs.lang.ThreadCompat
|
||||
import org.autojs.autojs.rhino.AutoJsContext
|
||||
import org.autojs.autojs.runtime.ScriptRuntime
|
||||
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
|
||||
import org.mozilla.javascript.Context
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/7/29.
|
||||
* Transformed by aiselp on Jul 4, 2023.
|
||||
*/
|
||||
/**
|
||||
* update by aiselp on 2023/6/4
|
||||
* 调整内容:
|
||||
* 使此类只负责单loop线程生命周期管理,移除繁琐的调用链
|
||||
* 调整timer由此类创建
|
||||
* 通过向此类添加AsyncTask以监听线程退出事件
|
||||
*/
|
||||
class Loopers(val runtime: ScriptRuntime) {
|
||||
@Deprecated("使用AsyncTask代替")
|
||||
interface LooperQuitHandler {
|
||||
fun shouldQuit(): Boolean
|
||||
}
|
||||
|
||||
open class AsyncTask(private val describe: String) {
|
||||
private val allBind = ConcurrentLinkedQueue<Loopers>()
|
||||
var isEnd: Boolean = false
|
||||
private set
|
||||
|
||||
//线程即将退出时调用,返回true阻止线程退出,只要有一个task返回true线程就不会退出
|
||||
open fun onFinish(loopers: Loopers): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun end() {
|
||||
isEnd = true
|
||||
}
|
||||
|
||||
//线程正在退出,这里应该结束任务的执行,回收资源
|
||||
open fun onStop(loopers: Loopers) {}
|
||||
override fun toString(): String {
|
||||
return "AsyncTask: $describe"
|
||||
}
|
||||
}
|
||||
|
||||
private var waitWhenIdle: Boolean
|
||||
|
||||
@Volatile
|
||||
private var mServantLooper: Looper? = null
|
||||
private var mMainLooperQuitHandler: LooperQuitHandler? = null
|
||||
private val allTasks = ConcurrentLinkedQueue<AsyncTask>()
|
||||
val mTimer: Timer
|
||||
val myLooper: Looper
|
||||
|
||||
init {
|
||||
prepare()
|
||||
myLooper = Looper.myLooper()!!
|
||||
mTimer = Timer(runtime, myLooper)
|
||||
waitWhenIdle = myLooper == Looper.getMainLooper()
|
||||
}
|
||||
|
||||
fun createAndAddAsyncTask(describe: String): AsyncTask {
|
||||
val task = AsyncTask(describe)
|
||||
allTasks.add(task)
|
||||
return task
|
||||
}
|
||||
|
||||
fun addAsyncTask(task: AsyncTask) {
|
||||
synchronized(myLooper) {
|
||||
allTasks.add(task)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAsyncTask(task: AsyncTask) {
|
||||
synchronized(myLooper) {
|
||||
allTasks.remove(task)
|
||||
mTimer.post(EMPTY_RUNNABLE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTask(): Boolean {
|
||||
allTasks.removeAll(allTasks.filter { it.isEnd }.toSet())
|
||||
for (task in allTasks) {
|
||||
if (task.onFinish(this)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun shouldQuitLooper(): Boolean {
|
||||
synchronized(myLooper) {
|
||||
if (Thread.currentThread().isInterrupted) return true
|
||||
if (mTimer.hasPendingCallbacks()) return false
|
||||
//检查是否有运行中的线程
|
||||
if (checkTask()) return false
|
||||
if (waitWhenIdle) return false
|
||||
if ((Context.getCurrentContext() as AutoJsContext).hasPendingContinuation()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun initServantThread() {
|
||||
ThreadCompat {
|
||||
Looper.prepare()
|
||||
val lock = this@Loopers as Object
|
||||
mServantLooper = Looper.myLooper()
|
||||
synchronized(lock) { lock.notifyAll() }
|
||||
Looper.loop()
|
||||
}.start()
|
||||
}
|
||||
|
||||
val servantLooper: Looper
|
||||
get() {
|
||||
if (mServantLooper == null) {
|
||||
initServantThread()
|
||||
val lock = this as java.lang.Object
|
||||
synchronized(lock) {
|
||||
try {
|
||||
lock.wait()
|
||||
} catch (e: InterruptedException) {
|
||||
throw ScriptInterruptedException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mServantLooper!!
|
||||
}
|
||||
|
||||
@Deprecated("使用AsyncTask代替")
|
||||
fun waitWhenIdle(b: Boolean) {
|
||||
waitWhenIdle = b
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
Log.d(LOG_TAG, "recycle")
|
||||
for (task in allTasks.filter { !it.isEnd }) {
|
||||
try {
|
||||
task.onStop(this)
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, e)
|
||||
}
|
||||
}
|
||||
mServantLooper?.quit()
|
||||
}
|
||||
|
||||
@Deprecated("使用AsyncTask代替")
|
||||
fun setMainLooperQuitHandler(mainLooperQuitHandler: LooperQuitHandler?) {
|
||||
mMainLooperQuitHandler = mainLooperQuitHandler
|
||||
}
|
||||
|
||||
private fun prepare() {
|
||||
if (Looper.myLooper() == null) LooperHelper.prepare()
|
||||
Looper.myQueue().addIdleHandler(MessageQueue.IdleHandler {
|
||||
if (this == runtime.loopers) {
|
||||
Log.d(LOG_TAG, "main looper queueIdle")
|
||||
if (shouldQuitLooper() &&
|
||||
mMainLooperQuitHandler != null &&
|
||||
mMainLooperQuitHandler!!.shouldQuit()
|
||||
) {
|
||||
Log.d(LOG_TAG, "main looper quit")
|
||||
Looper.myLooper()!!.quitSafely()
|
||||
}
|
||||
} else {
|
||||
Log.d(LOG_TAG, "looper queueIdle $this")
|
||||
if (shouldQuitLooper()) {
|
||||
Log.d(LOG_TAG, "looper quit $this")
|
||||
Looper.myLooper()!!.quitSafely()
|
||||
}
|
||||
}
|
||||
return@IdleHandler true
|
||||
})
|
||||
}
|
||||
|
||||
fun notifyThreadExit(thread: TimerThread) {
|
||||
Log.d(LOG_TAG, "notifyThreadExit: $thread")
|
||||
//当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程),
|
||||
//此时通过向主线程发送一个空的Runnable,主线程执行完这个Runnable后会触发IdleHandler,从而检查自身是否退出
|
||||
//mHandler.post(EMPTY_RUNNABLE)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LOG_TAG = "Loopers"
|
||||
private val EMPTY_RUNNABLE = Runnable {}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package org.autojs.autojs.core.looper;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||
import org.autojs.autojs.concurrent.VolatileBox;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/12/27.
|
||||
*/
|
||||
public class Timer {
|
||||
|
||||
private final SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>();
|
||||
private int mCallbackMaxId = 0;
|
||||
private final ScriptRuntime mRuntime;
|
||||
private final Handler mHandler;
|
||||
private long mMaxCallbackUptimeMillis = 0;
|
||||
private final VolatileBox<Long> mMaxCallbackMillisForAllThread;
|
||||
|
||||
public Timer(ScriptRuntime runtime, VolatileBox<Long> maxCallbackMillisForAllThread) {
|
||||
mRuntime = runtime;
|
||||
mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread;
|
||||
mHandler = new Handler();
|
||||
}
|
||||
|
||||
public Timer(ScriptRuntime runtime, VolatileBox<Long> maxCallbackMillisForAllThread, Looper looper) {
|
||||
mRuntime = runtime;
|
||||
mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread;
|
||||
mHandler = new Handler(looper);
|
||||
}
|
||||
|
||||
public int setTimeout(final Object callback, final long delay, final Object... args) {
|
||||
mCallbackMaxId++;
|
||||
final int id = mCallbackMaxId;
|
||||
Runnable r = () -> {
|
||||
callFunction(callback, args);
|
||||
mHandlerCallbacks.remove(id);
|
||||
};
|
||||
mHandlerCallbacks.put(id, r);
|
||||
postDelayed(r, delay);
|
||||
return id;
|
||||
}
|
||||
|
||||
private void callFunction(Object callback, Object[] args) {
|
||||
if(Looper.myLooper() == Looper.getMainLooper()){
|
||||
try {
|
||||
mRuntime.bridges.callFunction(callback, null, args);
|
||||
}catch (Exception e){
|
||||
mRuntime.exit(e);
|
||||
}
|
||||
}else {
|
||||
mRuntime.bridges.callFunction(callback, null, args);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean clearTimeout(int id) {
|
||||
return clearCallback(id);
|
||||
}
|
||||
|
||||
public int setInterval(final Object listener, final long interval, final Object... args) {
|
||||
mCallbackMaxId++;
|
||||
final int id = mCallbackMaxId;
|
||||
final Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mHandlerCallbacks.get(id) == null)
|
||||
return;
|
||||
callFunction(listener, args);
|
||||
postDelayed(this, interval);
|
||||
}
|
||||
};
|
||||
mHandlerCallbacks.put(id, r);
|
||||
postDelayed(r, interval);
|
||||
return id;
|
||||
}
|
||||
|
||||
public void postDelayed(Runnable r, long interval) {
|
||||
long uptime = SystemClock.uptimeMillis() + interval;
|
||||
mHandler.postAtTime(r, uptime);
|
||||
mMaxCallbackUptimeMillis = Math.max(mMaxCallbackUptimeMillis, uptime);
|
||||
synchronized (mMaxCallbackMillisForAllThread) {
|
||||
mMaxCallbackMillisForAllThread.set(Math.max(mMaxCallbackMillisForAllThread.get(), uptime));
|
||||
}
|
||||
}
|
||||
|
||||
public void post(Runnable r) {
|
||||
|
||||
}
|
||||
|
||||
public boolean clearInterval(int id) {
|
||||
return clearCallback(id);
|
||||
}
|
||||
|
||||
public int setImmediate(final Object listener, final Object... args) {
|
||||
mCallbackMaxId++;
|
||||
final int id = mCallbackMaxId;
|
||||
Runnable r = () -> {
|
||||
callFunction(listener, args);
|
||||
mHandlerCallbacks.remove(id);
|
||||
};
|
||||
mHandlerCallbacks.put(id, r);
|
||||
postDelayed(r, 0);
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean clearImmediate(int id) {
|
||||
return clearCallback(id);
|
||||
}
|
||||
|
||||
private boolean clearCallback(int id) {
|
||||
Runnable callback = mHandlerCallbacks.get(id);
|
||||
if (callback != null) {
|
||||
mHandler.removeCallbacks(callback);
|
||||
mHandlerCallbacks.remove(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasPendingCallbacks() {
|
||||
return mMaxCallbackUptimeMillis > SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
public void removeAllCallbacks() {
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
}
|
||||
136
app/src/main/java/org/autojs/autojs/core/looper/Timer.kt
Normal file
136
app/src/main/java/org/autojs/autojs/core/looper/Timer.kt
Normal file
@@ -0,0 +1,136 @@
|
||||
package org.autojs.autojs.core.looper
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import org.autojs.autojs.runtime.ScriptRuntime
|
||||
import org.mozilla.javascript.BaseFunction
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import org.mozilla.javascript.Undefined
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/12/27.
|
||||
* Transformed by aiselp on Jun 4, 2023.
|
||||
*/
|
||||
class Timer(
|
||||
runtime: ScriptRuntime,
|
||||
looper: Looper
|
||||
) {
|
||||
private val myLooper: Looper = looper
|
||||
private val mHandlerCallbacks = ConcurrentHashMap<Int, Runnable?>()
|
||||
private val mRuntime: ScriptRuntime = runtime
|
||||
private val mHandler: Handler = Handler(looper)
|
||||
private val isUiLoop: Boolean = looper == Looper.getMainLooper()
|
||||
private val context: Context? by lazy { Context.getCurrentContext() }
|
||||
|
||||
constructor(runtime: ScriptRuntime) : this(runtime, Looper.myLooper()!!)
|
||||
|
||||
fun setTimeout(callback: Any, delay: Long, vararg args: Any?): Int {
|
||||
val id = createTimerId()
|
||||
val r = Runnable {
|
||||
callFunction(callback, null, args)
|
||||
mHandlerCallbacks.remove(id)
|
||||
}
|
||||
mHandlerCallbacks[id] = r
|
||||
postDelayed(r, delay)
|
||||
return id
|
||||
}
|
||||
|
||||
private fun callFunction(callback: Any, thisArg: Any?, args: Any?) {
|
||||
val func = callback as BaseFunction
|
||||
val map: Array<Any> =
|
||||
(args as? Array<*>)?.map { Context.javaToJS(it, callback.parentScope) }
|
||||
?.toTypedArray() ?: emptyArray()
|
||||
try {
|
||||
func.call(
|
||||
context ?: Context.enter(), func.parentScope,
|
||||
thisArg as? Scriptable ?: Undefined.SCRIPTABLE_UNDEFINED, map
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (isUiLoop) {
|
||||
mRuntime.exit(e)
|
||||
} else throw e
|
||||
} finally {
|
||||
context ?: Context.exit()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun createTimerId(): Int {
|
||||
var id: Int
|
||||
do {
|
||||
id = Random.nextInt()
|
||||
} while (mHandlerCallbacks.containsKey(id))
|
||||
mHandlerCallbacks[id] = EMPTY_RUNNABLE
|
||||
return id
|
||||
}
|
||||
|
||||
fun setInterval(listener: Any, interval: Long, vararg args: Any?): Int {
|
||||
val id = createTimerId()
|
||||
val r: Runnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (mHandlerCallbacks[id] == null) return
|
||||
callFunction(listener, null, args)
|
||||
postDelayed(this, interval)
|
||||
}
|
||||
}
|
||||
mHandlerCallbacks[id] = r
|
||||
postDelayed(r, interval)
|
||||
return id
|
||||
}
|
||||
|
||||
fun postDelayed(r: Runnable, interval: Long) {
|
||||
synchronized(myLooper) {
|
||||
val uptime = SystemClock.uptimeMillis() + interval
|
||||
mHandler.postAtTime(r, uptime)
|
||||
}
|
||||
}
|
||||
|
||||
fun post(r: Runnable) {
|
||||
synchronized(myLooper) {
|
||||
mHandler.post(r)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearInterval(id: Int): Boolean = clearCallback(id)
|
||||
fun clearImmediate(id: Int): Boolean = clearCallback(id)
|
||||
fun clearTimeout(id: Int): Boolean = clearCallback(id)
|
||||
|
||||
fun setImmediate(listener: Any, vararg args: Any?): Int {
|
||||
val id = createTimerId()
|
||||
val r = Runnable {
|
||||
callFunction(listener, null, args)
|
||||
mHandlerCallbacks.remove(id)
|
||||
}
|
||||
mHandlerCallbacks[id] = r
|
||||
post(r)
|
||||
return id
|
||||
}
|
||||
|
||||
|
||||
private fun clearCallback(id: Int): Boolean {
|
||||
val callback = mHandlerCallbacks[id]
|
||||
if (callback != null) {
|
||||
mHandler.removeCallbacks(callback)
|
||||
mHandlerCallbacks.remove(id)
|
||||
if (mHandlerCallbacks.isEmpty()) mHandler.post(EMPTY_RUNNABLE)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun hasPendingCallbacks(): Boolean {
|
||||
return mHandlerCallbacks.size > 0
|
||||
}
|
||||
|
||||
fun removeAllCallbacks() {
|
||||
mHandler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EMPTY_RUNNABLE = Runnable {}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package org.autojs.autojs.core.looper;
|
||||
|
||||
import static org.autojs.autojs.util.StringUtils.str;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.autojs.autojs.concurrent.VolatileBox;
|
||||
import org.autojs.autojs.engine.RhinoJavaScriptEngine;
|
||||
import org.autojs.autojs.lang.ThreadCompat;
|
||||
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||
import org.autojs.autojs.runtime.exception.ScriptInterruptedException;
|
||||
import org.autojs.autojs6.R;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/12/27.
|
||||
*/
|
||||
public class TimerThread extends ThreadCompat {
|
||||
|
||||
private static final ConcurrentHashMap<Thread, Timer> sTimerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Timer mTimer;
|
||||
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads;
|
||||
private final ScriptRuntime mRuntime;
|
||||
private final Runnable mTarget;
|
||||
private boolean mRunning = false;
|
||||
private final Object mRunningLock = new Object();
|
||||
|
||||
public TimerThread(ScriptRuntime runtime, VolatileBox<Long> maxCallbackUptimeMillisForAllThreads, Runnable target) {
|
||||
super(target);
|
||||
mRuntime = runtime;
|
||||
mTarget = target;
|
||||
mMaxCallbackUptimeMillisForAllThreads = maxCallbackUptimeMillisForAllThreads;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mRuntime.loopers.prepare();
|
||||
mTimer = new Timer(mRuntime, mMaxCallbackUptimeMillisForAllThreads);
|
||||
sTimerMap.put(Thread.currentThread(), mTimer);
|
||||
((RhinoJavaScriptEngine) mRuntime.engines.myEngine()).enterContext();
|
||||
notifyRunning();
|
||||
new Handler().post(mTarget);
|
||||
try {
|
||||
Looper.loop();
|
||||
} catch (Throwable e) {
|
||||
if (!ScriptInterruptedException.causedByInterrupted(e)) {
|
||||
mRuntime.console.error(Thread.currentThread() + ": ", e);
|
||||
}
|
||||
} finally {
|
||||
onExit();
|
||||
mTimer = null;
|
||||
org.mozilla.javascript.Context.exit();
|
||||
sTimerMap.remove(Thread.currentThread(), mTimer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
LooperHelper.quitForThread(this);
|
||||
super.interrupt();
|
||||
}
|
||||
|
||||
private void notifyRunning() {
|
||||
synchronized (mRunningLock) {
|
||||
mRunning = true;
|
||||
mRunningLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected void onExit() {
|
||||
mRuntime.loopers.notifyThreadExit(this);
|
||||
}
|
||||
|
||||
public static Timer getTimerForThread(Thread thread) {
|
||||
return sTimerMap.get(thread);
|
||||
}
|
||||
|
||||
public static Timer getTimerForCurrentThread() {
|
||||
return getTimerForThread(Thread.currentThread());
|
||||
}
|
||||
|
||||
public int setTimeout(Object callback, long delay, Object... args) {
|
||||
return getTimer().setTimeout(callback, delay, args);
|
||||
}
|
||||
|
||||
public Timer getTimer() {
|
||||
if (mTimer == null) {
|
||||
throw new IllegalStateException(str(R.string.error_thread_is_not_alive));
|
||||
}
|
||||
return mTimer;
|
||||
}
|
||||
|
||||
public boolean clearTimeout(int id) {
|
||||
return getTimer().clearTimeout(id);
|
||||
}
|
||||
|
||||
public int setInterval(Object listener, long interval, Object... args) {
|
||||
return getTimer().setInterval(listener, interval, args);
|
||||
}
|
||||
|
||||
public boolean clearInterval(int id) {
|
||||
return getTimer().clearInterval(id);
|
||||
}
|
||||
|
||||
public int setImmediate(Object listener, Object... args) {
|
||||
return getTimer().setImmediate(listener, args);
|
||||
}
|
||||
|
||||
public boolean clearImmediate(int id) {
|
||||
return getTimer().clearImmediate(id);
|
||||
}
|
||||
|
||||
public void waitFor() throws InterruptedException {
|
||||
synchronized (mRunningLock) {
|
||||
if (!mRunning) {
|
||||
mRunningLock.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Thread[" + getName() + "," + getPriority() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
129
app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt
Normal file
129
app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt
Normal file
@@ -0,0 +1,129 @@
|
||||
package org.autojs.autojs.core.looper
|
||||
|
||||
import android.os.Looper
|
||||
import androidx.annotation.CallSuper
|
||||
import org.autojs.autojs.engine.RhinoJavaScriptEngine
|
||||
import org.autojs.autojs.lang.ThreadCompat
|
||||
import org.autojs.autojs.runtime.ScriptRuntime
|
||||
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
|
||||
import org.mozilla.javascript.Context
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/12/27.
|
||||
* Transformed by aiselp on Jun 4, 2023.
|
||||
*/
|
||||
open class TimerThread(private val mRuntime: ScriptRuntime, private val mTarget: Runnable) :
|
||||
ThreadCompat(mTarget) {
|
||||
private var mTimer: Timer? = null
|
||||
private var mRunning = false
|
||||
private val mRunningLock = Object()
|
||||
private val mAsyncTask = Loopers.AsyncTask("TimerThread")
|
||||
var loopers: Loopers? = null
|
||||
|
||||
init {
|
||||
mRuntime.loopers.addAsyncTask(mAsyncTask)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
loopers = Loopers(mRuntime)
|
||||
mTimer = loopers!!.mTimer
|
||||
sTimerMap[currentThread()] = mTimer!!
|
||||
(mRuntime.engines.myEngine() as RhinoJavaScriptEngine).enterContext()
|
||||
notifyRunning()
|
||||
mTimer!!.post(mTarget)
|
||||
try {
|
||||
Looper.loop()
|
||||
} catch (e: Throwable) {
|
||||
if (!ScriptInterruptedException.causedByInterrupted(e)) {
|
||||
mRuntime.console.error(currentThread().toString() + ": ", e)
|
||||
}
|
||||
} finally {
|
||||
//mRuntime.console.log("TimerThread exit");
|
||||
onExit()
|
||||
mTimer = null
|
||||
Context.exit()
|
||||
sTimerMap.remove(currentThread(), mTimer)
|
||||
}
|
||||
}
|
||||
|
||||
override fun interrupt() {
|
||||
LooperHelper.quitForThread(this)
|
||||
super.interrupt()
|
||||
}
|
||||
|
||||
private fun notifyRunning() {
|
||||
synchronized(mRunningLock) {
|
||||
mRunning = true
|
||||
mRunningLock.notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
protected open fun onExit() {
|
||||
mRuntime.loopers.removeAsyncTask(mAsyncTask)
|
||||
mRuntime.loopers.notifyThreadExit(this)
|
||||
}
|
||||
|
||||
fun setTimeout(callback: Any, delay: Long, vararg args: Any?): Int {
|
||||
return timer.setTimeout(callback, delay, *args as Array<out Any>)
|
||||
}
|
||||
|
||||
fun setTimeout(callback: Any): Int {
|
||||
return setTimeout(callback, 1)
|
||||
}
|
||||
|
||||
val timer: Timer
|
||||
get() {
|
||||
checkNotNull(mTimer) { "thread is not alive" }
|
||||
return mTimer as Timer
|
||||
}
|
||||
|
||||
fun clearTimeout(id: Int): Boolean {
|
||||
return timer.clearTimeout(id)
|
||||
}
|
||||
|
||||
fun setInterval(listener: Any?, interval: Long, vararg args: Any?): Int {
|
||||
return timer.setInterval(listener!!, interval, *args as Array<out Any>)
|
||||
}
|
||||
|
||||
fun setInterval(listener: Any?): Int {
|
||||
return setInterval(listener, 1)
|
||||
}
|
||||
|
||||
fun clearInterval(id: Int): Boolean {
|
||||
return timer.clearInterval(id)
|
||||
}
|
||||
|
||||
fun setImmediate(listener: Any, vararg args: Any?): Int {
|
||||
return timer.setImmediate(listener, *args as Array<out Any>)
|
||||
}
|
||||
|
||||
fun clearImmediate(id: Int): Boolean {
|
||||
return timer.clearImmediate(id)
|
||||
}
|
||||
|
||||
@Throws(InterruptedException::class)
|
||||
fun waitFor() {
|
||||
synchronized(mRunningLock) {
|
||||
if (mRunning) return
|
||||
mRunningLock.wait()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Thread[$name,$priority]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val sTimerMap = ConcurrentHashMap<Thread, Timer?>()
|
||||
|
||||
@JvmStatic
|
||||
fun getTimerForThread(thread: Thread): Timer? {
|
||||
return sTimerMap[thread]
|
||||
}
|
||||
|
||||
val timerForCurrentThread: Timer?
|
||||
get() = getTimerForThread(currentThread())
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import org.autojs.autojs.core.eventloop.EventEmitter;
|
||||
import org.autojs.autojs.core.looper.Loopers;
|
||||
import org.autojs.autojs.core.looper.Timer;
|
||||
@@ -20,7 +21,7 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
|
||||
private final Timer mTimer;
|
||||
private final Loopers mLoopers;
|
||||
private JsDialog mDialog;
|
||||
private volatile int mWaitId = -1;
|
||||
private volatile Loopers.AsyncTask task;
|
||||
|
||||
|
||||
public JsDialogBuilder(Context context, ScriptRuntime runtime) {
|
||||
@@ -55,14 +56,14 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
|
||||
}
|
||||
});
|
||||
dismissListener(dialog -> {
|
||||
mTimer.postDelayed(() -> mLoopers.doNotWaitWhenIdle(mWaitId), 0);
|
||||
mTimer.postDelayed(() -> mLoopers.removeAsyncTask(task), 0);
|
||||
emit("dismiss", dialog);
|
||||
});
|
||||
cancelListener(dialog -> emit("cancel", dialog));
|
||||
}
|
||||
|
||||
public void onShowCalled() {
|
||||
mTimer.postDelayed(() -> mWaitId = mLoopers.waitWhenIdle(), 0);
|
||||
mTimer.postDelayed(() -> task = mLoopers.createAndAddAsyncTask("js-dialog"), 0);
|
||||
}
|
||||
|
||||
public JsDialog getDialog() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.View
|
||||
import org.autojs.autojs.core.automator.UiObjectCollection
|
||||
import org.autojs.autojs.core.ui.ViewExtras
|
||||
import org.autojs.autojs.engine.module.AssetAndUrlModuleSourceProvider
|
||||
import org.autojs.autojs.engine.module.ScopeRequire
|
||||
import org.autojs.autojs.execution.ExecutionConfig
|
||||
import org.autojs.autojs.pio.UncheckedIOException
|
||||
import org.autojs.autojs.project.ScriptConfig
|
||||
@@ -17,7 +18,6 @@ import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.Script
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import org.mozilla.javascript.ScriptableObject
|
||||
import org.mozilla.javascript.commonjs.module.RequireBuilder
|
||||
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@@ -121,11 +121,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
|
||||
mAndroidContext, MODULES_PATH,
|
||||
listOf<URI>(File("/").toURI())
|
||||
)
|
||||
RequireBuilder()
|
||||
.setModuleScriptProvider(SoftCachingModuleScriptProvider(provider))
|
||||
.setSandboxed(true)
|
||||
.createRequire(context, scope)
|
||||
.install(scope)
|
||||
ScopeRequire(context, scope, SoftCachingModuleScriptProvider(provider)).install(scope)
|
||||
}
|
||||
|
||||
protected fun createScope(context: Context): TopLevelScope {
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.autojs.autojs.engine.module;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import org.autojs.autojs.engine.encryption.ScriptEncryption;
|
||||
import org.autojs.autojs.script.EncryptedScriptFileHeader;
|
||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLConnection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/9.
|
||||
*/
|
||||
public class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider {
|
||||
|
||||
private final android.content.Context mContext;
|
||||
private final URI mBaseURI;
|
||||
private final String mAssetDirPath;
|
||||
private final AssetManager mAssetManager;
|
||||
|
||||
public AssetAndUrlModuleSourceProvider(android.content.Context context, String assetDirPath, List<URI> list) {
|
||||
super(list, null);
|
||||
mContext = context;
|
||||
mAssetDirPath = assetDirPath;
|
||||
mBaseURI = URI.create("file:///android_asset/" + assetDirPath);
|
||||
mAssetManager = mContext.getAssets();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModuleSource loadFromPrivilegedLocations(String moduleId, Object validator) throws IOException, URISyntaxException {
|
||||
String moduleIdWithExtension = moduleId;
|
||||
if (!moduleIdWithExtension.endsWith(".js")) {
|
||||
moduleIdWithExtension += ".js";
|
||||
}
|
||||
try {
|
||||
return new ModuleSource(new InputStreamReader(mAssetManager.open(mAssetDirPath + "/" + moduleIdWithExtension)), null,
|
||||
new URI(mBaseURI.toString() + "/" + moduleIdWithExtension), mBaseURI, validator);
|
||||
} catch (FileNotFoundException e) {
|
||||
return super.loadFromPrivilegedLocations(moduleId, validator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Reader getReader(URLConnection urlConnection) throws IOException {
|
||||
InputStream stream = urlConnection.getInputStream();
|
||||
byte[] bytes = new byte[stream.available()];
|
||||
stream.read(bytes);
|
||||
stream.close();
|
||||
if (EncryptedScriptFileHeader.isValidFile(bytes)) {
|
||||
byte[] clearText = ScriptEncryption.decrypt(bytes, EncryptedScriptFileHeader.BLOCK_SIZE, bytes.length);
|
||||
return new InputStreamReader(new ByteArrayInputStream(clearText));
|
||||
}
|
||||
return new InputStreamReader(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package org.autojs.autojs.engine.module
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.google.gson.Gson
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.autojs.autojs.engine.encryption.ScriptEncryption.decrypt
|
||||
import org.autojs.autojs.script.EncryptedScriptFileHeader
|
||||
import org.autojs.autojs.script.EncryptedScriptFileHeader.isValidFile
|
||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSource
|
||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSourceProviderBase
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.URI
|
||||
|
||||
class AssetAndUrlModuleSourceProvider(
|
||||
context: Context,
|
||||
assetDirPath: String,
|
||||
list: List<URI>? = null
|
||||
) : ModuleSourceProviderBase() {
|
||||
val mContext = context
|
||||
private val okHttpClient = OkHttpClient.Builder().followRedirects(true).build()
|
||||
private val contentResolver: ContentResolver = context.contentResolver
|
||||
private val moduleSources: ArrayList<URI> = arrayListOf(mBaseURI, npmModuleSource)
|
||||
|
||||
companion object {
|
||||
val mBaseURI: URI = URI.create("file:/android_asset/modules")
|
||||
val npmModuleSource: URI = URI.create("file:/android_asset/modules/npm")
|
||||
}
|
||||
|
||||
// 初始化脚本以及启动文件只会从此方法加载模块,子模块加载没有以"./"或"../"开头的模块也会从此方法加载
|
||||
override fun loadFromPrivilegedLocations(moduleId: String, validator: Any?): ModuleSource? {
|
||||
// println("加载私有模块:$moduleId")
|
||||
val uri = if (moduleId.startsWith("/")) {
|
||||
File(moduleId).toURI()
|
||||
} else if (moduleId.startsWith("http://") || moduleId.startsWith("https://")) {
|
||||
URI.create(moduleId)
|
||||
} else null
|
||||
if (uri != null) {
|
||||
return loadFromUri(uri, File(uri.path).parentFile?.toURI(), validator)
|
||||
}
|
||||
for (baseUri in moduleSources) {
|
||||
val sourceUri = URI.create("$baseUri/$moduleId")
|
||||
val moduleSource = loadFromUri(sourceUri, baseUri, validator)
|
||||
if (moduleSource != null) {
|
||||
return moduleSource
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 这里处理node_module目录的模块
|
||||
override fun loadFromFallbackLocations(moduleId: String, validator: Any?): ModuleSource? {
|
||||
return super.loadFromFallbackLocations(moduleId, validator)
|
||||
}
|
||||
|
||||
// 子模块以相对路径加载时调用此方法
|
||||
override fun loadFromUri(uri: URI, base: URI?, validator: Any?): ModuleSource? {
|
||||
var uri = uri
|
||||
if (uri.scheme == null) uri = File(uri.path).toURI()
|
||||
// println("加载模块:$uri")
|
||||
if (uri.scheme == "http" || uri.scheme == "https") {
|
||||
return loadFromHttp(uri, base, validator)
|
||||
}
|
||||
val moduleSource = loadAt(uri, base, validator) ?: loadAt(
|
||||
File(uri.path + ".js").toURI(), base, validator
|
||||
)
|
||||
if (moduleSource != null) {
|
||||
return moduleSource
|
||||
}
|
||||
// 尝试从目录加载
|
||||
// 尝试读取package.json指定的文件
|
||||
val mainFile: URI? = try {
|
||||
val packageFile = File(uri.path, "package.json")
|
||||
val json = Gson().fromJson<Map<String, Any>>(
|
||||
InputStreamReader(packageFile.inputStream()),
|
||||
Map::class.java
|
||||
)
|
||||
val main = json["main"] as String
|
||||
packageFile.toURI().resolve(main)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
val main: URI = mainFile ?: File(uri.path, "index.js").toURI()
|
||||
return loadAt(main, uri, validator)
|
||||
}
|
||||
|
||||
private fun loadAt(uri: URI, base: URI?, validator: Any?): ModuleSource? {
|
||||
if (uri.scheme == "http" || uri.scheme == "https") {
|
||||
return loadFromHttp(uri, base, validator)
|
||||
}
|
||||
return try {
|
||||
val inputStream = if (uri.path.startsWith("/android_asset/")) {
|
||||
mContext.assets.open(uri.path.replace("/android_asset/", ""))
|
||||
} else contentResolver.openInputStream(Uri.parse(uri.toString()))
|
||||
if (inputStream != null) {
|
||||
createModuleEncryptionSource(inputStream, uri, base, validator)
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createModuleEncryptionSource(
|
||||
inputStream: InputStream,
|
||||
uri: URI,
|
||||
base: URI?,
|
||||
validator: Any?,
|
||||
): ModuleSource {
|
||||
val bytes = ByteArray(inputStream.available())
|
||||
inputStream.read(bytes)
|
||||
inputStream.close()
|
||||
val i = if (isValidFile(bytes)) {
|
||||
val clearText = decrypt(bytes, EncryptedScriptFileHeader.BLOCK_SIZE, bytes.size)
|
||||
ByteArrayInputStream(clearText)
|
||||
} else ByteArrayInputStream(bytes)
|
||||
return createModuleSource(i, uri, base, validator)
|
||||
}
|
||||
|
||||
private fun loadFromHttp(uri: URI, base: URI?, validator: Any?): ModuleSource? {
|
||||
return try {
|
||||
Request.Builder().url(uri.toString()).build().let { request ->
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
if (!response.isSuccessful) {
|
||||
response.close()
|
||||
return null
|
||||
}
|
||||
response.body?.let {
|
||||
val charset = it.contentType()?.charset()?.toString() ?: "utf-8"
|
||||
return createModuleSource(it.byteStream(), uri, base, validator, charset)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createModuleSource(
|
||||
inputStream: InputStream,
|
||||
uri: URI,
|
||||
base: URI?,
|
||||
validator: Any?,
|
||||
charset: String? = null
|
||||
): ModuleSource {
|
||||
val id = if (uri.scheme == "file") {
|
||||
URI.create(uri.path)
|
||||
} else uri
|
||||
return ModuleSource(
|
||||
InputStreamReader(inputStream, charset ?: "utf-8"),
|
||||
null,
|
||||
id,
|
||||
base,
|
||||
validator
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package org.autojs.autojs.engine.module
|
||||
|
||||
import org.mozilla.javascript.*
|
||||
import org.mozilla.javascript.commonjs.module.ModuleScope
|
||||
import org.mozilla.javascript.commonjs.module.ModuleScript
|
||||
import org.mozilla.javascript.commonjs.module.ModuleScriptProvider
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
open class ScopeRequire(
|
||||
cx: Context, private val nativeScope: Scriptable,
|
||||
private val moduleScriptProvider: ModuleScriptProvider, private val preExec: Script?,
|
||||
private val postExec: Script?, private val sandboxed: Boolean = true
|
||||
) : BaseFunction() {
|
||||
private var paths: Scriptable? = null
|
||||
private var mainModuleId: String? = null
|
||||
private var mainExports: Scriptable? = null
|
||||
|
||||
// Modules that completed loading; visible to all threads
|
||||
private val exportedModuleInterfaces: MutableMap<String, Scriptable?> = ConcurrentHashMap()
|
||||
private val loadLock = Any()
|
||||
|
||||
constructor(cx: Context, nativeScope: Scriptable, moduleScriptProvider: ModuleScriptProvider)
|
||||
: this(cx, nativeScope, moduleScriptProvider, null, null, false)
|
||||
|
||||
init {
|
||||
prototype = getFunctionPrototype(nativeScope)
|
||||
if (!sandboxed) {
|
||||
paths = cx.newArray(nativeScope, 0)
|
||||
defineReadOnlyProperty(this, "paths", paths)
|
||||
} else paths = null
|
||||
}
|
||||
|
||||
|
||||
fun requireMain(cx: Context, mainModuleId: String): Scriptable? {
|
||||
if (this.mainModuleId != null) {
|
||||
if (this.mainModuleId != mainModuleId) {
|
||||
throw IllegalStateException("Main module already set to " + this.mainModuleId)
|
||||
}
|
||||
return mainExports
|
||||
}
|
||||
val moduleScript: ModuleScript? = try {
|
||||
moduleScriptProvider.getModuleScript(cx, mainModuleId, null, null, paths)
|
||||
} catch (x: RuntimeException) {
|
||||
throw x
|
||||
} catch (x: Exception) {
|
||||
throw RuntimeException(x)
|
||||
}
|
||||
if (moduleScript != null) {
|
||||
mainExports = getExportedModuleInterface(
|
||||
cx, mainModuleId,
|
||||
null, null, true
|
||||
)
|
||||
} else if (!sandboxed) {
|
||||
var mainUri: URI? = try {
|
||||
URI(mainModuleId)
|
||||
} catch (_: URISyntaxException) {
|
||||
null
|
||||
}
|
||||
if (mainUri == null || !mainUri.isAbsolute) {
|
||||
val file = File(mainModuleId)
|
||||
if (!file.isFile) {
|
||||
throw ScriptRuntime.throwError(
|
||||
cx, nativeScope,
|
||||
"Module \"$mainModuleId\" not found."
|
||||
)
|
||||
}
|
||||
mainUri = file.toURI()
|
||||
}
|
||||
mainExports = getExportedModuleInterface(
|
||||
cx, mainUri.toString(),
|
||||
mainUri, null, true
|
||||
)
|
||||
}
|
||||
this.mainModuleId = mainModuleId
|
||||
return mainExports
|
||||
}
|
||||
|
||||
fun install(scope: Scriptable?) {
|
||||
putProperty(scope, "require", this)
|
||||
}
|
||||
|
||||
override fun call(cx: Context, scope: Scriptable, thisObj: Scriptable, args: Array<Any>?): Any {
|
||||
if (args == null || args.isEmpty()) {
|
||||
throw ScriptRuntime.throwError(
|
||||
cx, scope,
|
||||
"require() needs one argument"
|
||||
)
|
||||
}
|
||||
var id = Context.jsToJava(args[0], String::class.java) as String
|
||||
var uri: URI? = null
|
||||
var base: URI? = null
|
||||
if (id.startsWith("./") || id.startsWith("../")) {
|
||||
if (thisObj !is ModuleScope) {
|
||||
throw ScriptRuntime.throwError(
|
||||
cx, scope,
|
||||
"Can't resolve relative module ID \"" + id +
|
||||
"\" when require() is used outside of a module"
|
||||
)
|
||||
}
|
||||
base = thisObj.base
|
||||
val current = thisObj.uri
|
||||
uri = current.resolve(id)
|
||||
if (base == null) {
|
||||
id = uri.toString()
|
||||
} else {
|
||||
id = base.relativize(current).resolve(id).toString()
|
||||
if (id[0] == '.') {
|
||||
if (sandboxed) {
|
||||
throw ScriptRuntime.throwError(
|
||||
cx, scope,
|
||||
"Module \"$id\" is not contained in sandbox."
|
||||
)
|
||||
}
|
||||
id = uri.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
return (getExportedModuleInterface(cx, id, uri, base, false))!!
|
||||
}
|
||||
|
||||
override fun construct(cx: Context, scope: Scriptable, args: Array<Any>): Scriptable {
|
||||
throw ScriptRuntime.throwError(
|
||||
cx, scope,
|
||||
"require() can not be invoked as a constructor"
|
||||
)
|
||||
}
|
||||
|
||||
private fun getExportedModuleInterface(
|
||||
cx: Context, id: String, uri: URI?, base: URI?, isMain: Boolean
|
||||
): Scriptable? {
|
||||
// Check if the requested module is already completely loaded
|
||||
var exports = exportedModuleInterfaces[id]
|
||||
if (exports != null) {
|
||||
if (isMain) {
|
||||
throw IllegalStateException("Attempt to set main module after it was loaded")
|
||||
} else
|
||||
return exports
|
||||
}
|
||||
var threadLoadingModules: MutableMap<String, Scriptable>? =
|
||||
loadingModuleInterfaces.get() as? MutableMap<String, Scriptable>
|
||||
exports = threadLoadingModules?.get(id)
|
||||
if (exports != null) return exports
|
||||
|
||||
synchronized(loadLock) {
|
||||
exports = exportedModuleInterfaces[id]
|
||||
if (exports != null) return exports
|
||||
|
||||
val moduleScript: ModuleScript = getModule(cx, id, uri, base)
|
||||
if (sandboxed && !moduleScript.isSandboxed) {
|
||||
throw ScriptRuntime.throwError(
|
||||
cx, nativeScope, ("Module \"$id\" is not contained in sandbox.")
|
||||
)
|
||||
}
|
||||
exports = cx.newObject(nativeScope)
|
||||
val outermostLocked: Boolean = threadLoadingModules == null
|
||||
if (outermostLocked) {
|
||||
threadLoadingModules = HashMap()
|
||||
loadingModuleInterfaces.set(threadLoadingModules)
|
||||
}
|
||||
|
||||
threadLoadingModules?.set(id, exports!!)
|
||||
try {
|
||||
val newExports: Scriptable = executeModuleScript(
|
||||
cx, id, exports,
|
||||
moduleScript, isMain
|
||||
)
|
||||
if (exports !== newExports) {
|
||||
threadLoadingModules?.put(id, newExports)
|
||||
exports = newExports
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
threadLoadingModules?.remove(id)
|
||||
throw e
|
||||
} finally {
|
||||
if (outermostLocked) {
|
||||
exportedModuleInterfaces.putAll((threadLoadingModules!!))
|
||||
loadingModuleInterfaces.set(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return exports
|
||||
}
|
||||
|
||||
private fun executeModuleScript(
|
||||
cx: Context, id: String,
|
||||
exports: Scriptable?, moduleScript: ModuleScript, isMain: Boolean
|
||||
): Scriptable {
|
||||
val moduleObject = cx.newObject(nativeScope) as ScriptableObject
|
||||
val uri = moduleScript.uri
|
||||
val base = moduleScript.base
|
||||
defineReadOnlyProperty(moduleObject, "id", id)
|
||||
if (!sandboxed) {
|
||||
defineReadOnlyProperty(moduleObject, "uri", uri.toString())
|
||||
}
|
||||
val executionScope: Scriptable = ModuleScope(nativeScope, uri, base)
|
||||
executionScope.put("__filename", executionScope, File(uri.path).path)
|
||||
executionScope.put("__dirname", executionScope, File(uri.path).parent)
|
||||
executionScope.put("exports", executionScope, exports)
|
||||
executionScope.put("module", executionScope, moduleObject)
|
||||
moduleObject.put("exports", moduleObject, exports)
|
||||
install(executionScope)
|
||||
if (isMain) {
|
||||
defineReadOnlyProperty(this, "main", moduleObject)
|
||||
}
|
||||
//创建新作用域
|
||||
val funScope = cx.newObject(executionScope)
|
||||
funScope.parentScope = executionScope
|
||||
|
||||
executeOptionalScript(preExec, cx, funScope)
|
||||
moduleScript.script.exec(cx, funScope)
|
||||
executeOptionalScript(postExec, cx, funScope)
|
||||
return ScriptRuntime.toObject(
|
||||
cx, nativeScope,
|
||||
getProperty(moduleObject, "exports")
|
||||
)
|
||||
}
|
||||
|
||||
private fun getModule(cx: Context, id: String, uri: URI?, base: URI?): ModuleScript {
|
||||
try {
|
||||
return moduleScriptProvider.getModuleScript(cx, id, uri, base, paths)
|
||||
?: throw ScriptRuntime.throwError(
|
||||
cx, nativeScope, ("Module \"$id\" not found.")
|
||||
)
|
||||
} catch (e: RuntimeException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw Context.throwAsScriptRuntimeEx(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFunctionName() = "require"
|
||||
override fun getArity() = 1
|
||||
override fun getLength() = 1
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 1L
|
||||
|
||||
private val loadingModuleInterfaces = ThreadLocal<Map<String, Scriptable>>()
|
||||
private fun executeOptionalScript(
|
||||
script: Script?, cx: Context,
|
||||
executionScope: Scriptable
|
||||
) {
|
||||
script?.exec(cx, executionScope)
|
||||
}
|
||||
|
||||
private fun defineReadOnlyProperty(
|
||||
obj: ScriptableObject,
|
||||
name: String, value: Any?
|
||||
) {
|
||||
putProperty(obj, name, value)
|
||||
obj.setAttributes(
|
||||
name, READONLY or
|
||||
PERMANENT
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
package org.autojs.autojs.engine.module;
|
||||
|
||||
import org.mozilla.javascript.commonjs.module.provider.DefaultUrlConnectionExpiryCalculator;
|
||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
|
||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSourceProviderBase;
|
||||
import org.mozilla.javascript.commonjs.module.provider.ParsedContentType;
|
||||
import org.mozilla.javascript.commonjs.module.provider.UrlConnectionExpiryCalculator;
|
||||
import org.mozilla.javascript.commonjs.module.provider.UrlConnectionSecurityDomainProvider;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* A URL-based script provider that can load modules against a set of base
|
||||
* privileged and fallback URIs. It is deliberately not named "URI provider"
|
||||
* but a "URL provider" since it actually only works against those URIs that
|
||||
* are URLs (and the JRE has a protocol handler for them). It creates cache
|
||||
* validators that are suitable for use with both file: and http: URL
|
||||
* protocols. Specifically, it is able to use both last-modified timestamps and
|
||||
* ETags for cache revalidation, and follows the HTTP cache expiry calculation
|
||||
* model, and allows for fallback heuristic expiry calculation when no server
|
||||
* specified expiry is provided.
|
||||
*
|
||||
* @author Attila Szegedi
|
||||
* @version $Id: UrlModuleSourceProvider.java,v 1.4 2011/04/07 20:26:12 hannes%helma.at Exp $
|
||||
*/
|
||||
public class UrlModuleSourceProvider extends ModuleSourceProviderBase {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Iterable<URI> privilegedUris;
|
||||
private final Iterable<URI> fallbackUris;
|
||||
private final UrlConnectionSecurityDomainProvider
|
||||
urlConnectionSecurityDomainProvider;
|
||||
private final UrlConnectionExpiryCalculator urlConnectionExpiryCalculator;
|
||||
|
||||
/**
|
||||
* Creates a new module script provider that loads modules against a set of
|
||||
* privileged and fallback URIs. It will use a fixed default cache expiry
|
||||
* of 60 seconds, and provide no security domain objects for the resource.
|
||||
*
|
||||
* @param privilegedUris an iterable providing the privileged URIs. Can be
|
||||
* null if no privileged URIs are used.
|
||||
* @param fallbackUris an iterable providing the fallback URIs. Can be
|
||||
* null if no fallback URIs are used.
|
||||
*/
|
||||
public UrlModuleSourceProvider(Iterable<URI> privilegedUris,
|
||||
Iterable<URI> fallbackUris) {
|
||||
this(privilegedUris, fallbackUris,
|
||||
new DefaultUrlConnectionExpiryCalculator(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new module script provider that loads modules against a set of
|
||||
* privileged and fallback URIs. It will use the specified heuristic cache
|
||||
* expiry calculator and security domain provider.
|
||||
*
|
||||
* @param privilegedUris an iterable providing the privileged URIs. Can be
|
||||
* null if no privileged URIs are used.
|
||||
* @param fallbackUris an iterable providing the fallback URIs. Can be
|
||||
* null if no fallback URIs are used.
|
||||
* @param urlConnectionExpiryCalculator the calculator object for heuristic
|
||||
* calculation of the resource expiry, used when no expiry is provided by
|
||||
* the server of the resource. Can be null, in which case the maximum age
|
||||
* of cached entries without validation will be zero.
|
||||
* @param urlConnectionSecurityDomainProvider object that provides security
|
||||
* domain objects for the loaded sources. Can be null, in which case the
|
||||
* loaded sources will have no security domain associated with them.
|
||||
*/
|
||||
public UrlModuleSourceProvider(Iterable<URI> privilegedUris,
|
||||
Iterable<URI> fallbackUris,
|
||||
UrlConnectionExpiryCalculator urlConnectionExpiryCalculator,
|
||||
UrlConnectionSecurityDomainProvider urlConnectionSecurityDomainProvider) {
|
||||
this.privilegedUris = privilegedUris;
|
||||
this.fallbackUris = fallbackUris;
|
||||
this.urlConnectionExpiryCalculator = urlConnectionExpiryCalculator;
|
||||
this.urlConnectionSecurityDomainProvider =
|
||||
urlConnectionSecurityDomainProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModuleSource loadFromPrivilegedLocations(
|
||||
String moduleId, Object validator)
|
||||
throws IOException, URISyntaxException {
|
||||
return loadFromPathList(moduleId, validator, privilegedUris);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModuleSource loadFromFallbackLocations(
|
||||
String moduleId, Object validator)
|
||||
throws IOException, URISyntaxException {
|
||||
return loadFromPathList(moduleId, validator, fallbackUris);
|
||||
}
|
||||
|
||||
private ModuleSource loadFromPathList(String moduleId,
|
||||
Object validator, Iterable<URI> paths)
|
||||
throws IOException, URISyntaxException {
|
||||
if (paths == null) {
|
||||
return null;
|
||||
}
|
||||
for (URI path : paths) {
|
||||
final ModuleSource moduleSource = loadFromUri(
|
||||
path.resolve(moduleId), path, validator);
|
||||
if (moduleSource != null) {
|
||||
return moduleSource;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModuleSource loadFromUri(URI uri, URI base, Object validator)
|
||||
throws IOException, URISyntaxException {
|
||||
// We expect modules to have a ".js" file name extension ...
|
||||
URI fullUri = new URI(uri + ".js");
|
||||
ModuleSource source = loadFromActualUri(fullUri, base, validator);
|
||||
// ... but for compatibility we support modules without extension,
|
||||
// or ids with explicit extension.
|
||||
return source != null ?
|
||||
source : loadFromActualUri(uri, base, validator);
|
||||
}
|
||||
|
||||
protected ModuleSource loadFromActualUri(URI uri, URI base, Object validator)
|
||||
throws IOException {
|
||||
final URL url = new URL(base == null ? null : base.toURL(), uri.toString());
|
||||
final long request_time = System.currentTimeMillis();
|
||||
final URLConnection urlConnection = openUrlConnection(url);
|
||||
final URLValidator applicableValidator;
|
||||
if (validator instanceof final URLValidator uriValidator) {
|
||||
applicableValidator = uriValidator.appliesTo(uri) ? uriValidator :
|
||||
null;
|
||||
} else {
|
||||
applicableValidator = null;
|
||||
}
|
||||
if (applicableValidator != null) {
|
||||
applicableValidator.applyConditionals(urlConnection);
|
||||
}
|
||||
try {
|
||||
urlConnection.connect();
|
||||
if (applicableValidator != null &&
|
||||
applicableValidator.updateValidator(urlConnection,
|
||||
request_time, urlConnectionExpiryCalculator)) {
|
||||
close(urlConnection);
|
||||
return NOT_MODIFIED;
|
||||
}
|
||||
|
||||
return new ModuleSource(getReader(urlConnection),
|
||||
getSecurityDomain(urlConnection), uri, base,
|
||||
new URLValidator(uri, urlConnection, request_time,
|
||||
urlConnectionExpiryCalculator));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
} catch (RuntimeException | IOException e) {
|
||||
close(urlConnection);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected Reader getReader(URLConnection urlConnection)
|
||||
throws IOException {
|
||||
return new InputStreamReader(urlConnection.getInputStream(),
|
||||
getCharacterEncoding(urlConnection));
|
||||
}
|
||||
|
||||
protected String getCharacterEncoding(URLConnection urlConnection) {
|
||||
final ParsedContentType pct = new ParsedContentType(
|
||||
urlConnection.getContentType());
|
||||
final String encoding = pct.getEncoding();
|
||||
if (encoding != null) {
|
||||
return encoding;
|
||||
}
|
||||
final String contentType = pct.getContentType();
|
||||
if (contentType != null && contentType.startsWith("text/")) {
|
||||
return "8859_1";
|
||||
}
|
||||
return "utf-8";
|
||||
}
|
||||
|
||||
protected Object getSecurityDomain(URLConnection urlConnection) {
|
||||
return urlConnectionSecurityDomainProvider == null ? null :
|
||||
urlConnectionSecurityDomainProvider.getSecurityDomain(
|
||||
urlConnection);
|
||||
}
|
||||
|
||||
private void close(URLConnection urlConnection) {
|
||||
try {
|
||||
urlConnection.getInputStream().close();
|
||||
} catch (IOException e) {
|
||||
onFailedClosingUrlConnection(urlConnection, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override if you want to get notified if the URL connection fails to
|
||||
* close. Does nothing by default.
|
||||
*
|
||||
* @param urlConnection the connection
|
||||
* @param cause the cause it failed to close.
|
||||
*/
|
||||
protected void onFailedClosingUrlConnection(URLConnection urlConnection,
|
||||
IOException cause) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden in subclasses to customize the URL connection opening
|
||||
* process. By default, just calls {@link URL#openConnection()}.
|
||||
*
|
||||
* @param url the URL
|
||||
* @return a connection to the URL.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
protected URLConnection openUrlConnection(URL url) throws IOException {
|
||||
return url.openConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean entityNeedsRevalidation(Object validator) {
|
||||
return !(validator instanceof URLValidator)
|
||||
|| ((URLValidator) validator).entityNeedsRevalidation();
|
||||
}
|
||||
|
||||
private static class URLValidator implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final URI uri;
|
||||
private final long lastModified;
|
||||
private final String entityTags;
|
||||
private long expiry;
|
||||
|
||||
public URLValidator(URI uri, URLConnection urlConnection,
|
||||
long request_time, UrlConnectionExpiryCalculator
|
||||
urlConnectionExpiryCalculator) {
|
||||
this.uri = uri;
|
||||
this.lastModified = urlConnection.getLastModified();
|
||||
this.entityTags = getEntityTags(urlConnection);
|
||||
expiry = calculateExpiry(urlConnection, request_time,
|
||||
urlConnectionExpiryCalculator);
|
||||
}
|
||||
|
||||
boolean updateValidator(URLConnection urlConnection, long request_time,
|
||||
UrlConnectionExpiryCalculator urlConnectionExpiryCalculator)
|
||||
throws IOException {
|
||||
boolean isResourceChanged = isResourceChanged(urlConnection);
|
||||
if (!isResourceChanged) {
|
||||
expiry = calculateExpiry(urlConnection, request_time,
|
||||
urlConnectionExpiryCalculator);
|
||||
}
|
||||
return isResourceChanged;
|
||||
}
|
||||
|
||||
private boolean isResourceChanged(URLConnection urlConnection)
|
||||
throws IOException {
|
||||
if (urlConnection instanceof HttpURLConnection) {
|
||||
return ((HttpURLConnection) urlConnection).getResponseCode() ==
|
||||
HttpURLConnection.HTTP_NOT_MODIFIED;
|
||||
}
|
||||
return lastModified != urlConnection.getLastModified();
|
||||
}
|
||||
|
||||
private long calculateExpiry(URLConnection urlConnection,
|
||||
long request_time, UrlConnectionExpiryCalculator
|
||||
urlConnectionExpiryCalculator) {
|
||||
if ("no-cache".equals(urlConnection.getHeaderField("Pragma"))) {
|
||||
return 0L;
|
||||
}
|
||||
final String cacheControl = urlConnection.getHeaderField(
|
||||
"Cache-Control");
|
||||
if (cacheControl != null) {
|
||||
if (cacheControl.contains("no-cache")) {
|
||||
return 0L;
|
||||
}
|
||||
final int max_age = getMaxAge(cacheControl);
|
||||
if (-1 != max_age) {
|
||||
final long response_time = System.currentTimeMillis();
|
||||
final long apparent_age = Math.max(0, response_time -
|
||||
urlConnection.getDate());
|
||||
final long corrected_received_age = Math.max(apparent_age,
|
||||
urlConnection.getHeaderFieldInt("Age", 0) * 1000L);
|
||||
final long response_delay = response_time - request_time;
|
||||
final long corrected_initial_age = corrected_received_age +
|
||||
response_delay;
|
||||
final long creation_time = response_time -
|
||||
corrected_initial_age;
|
||||
return max_age * 1000L + creation_time;
|
||||
}
|
||||
}
|
||||
final long explicitExpiry = urlConnection.getHeaderFieldDate(
|
||||
"Expires", -1L);
|
||||
if (explicitExpiry != -1L) {
|
||||
return explicitExpiry;
|
||||
}
|
||||
return urlConnectionExpiryCalculator == null ? 0L :
|
||||
urlConnectionExpiryCalculator.calculateExpiry(urlConnection);
|
||||
}
|
||||
|
||||
private int getMaxAge(String cacheControl) {
|
||||
final int maxAgeIndex = cacheControl.indexOf("max-age");
|
||||
if (maxAgeIndex == -1) {
|
||||
return -1;
|
||||
}
|
||||
final int eq = cacheControl.indexOf('=', maxAgeIndex + 7);
|
||||
if (eq == -1) {
|
||||
return -1;
|
||||
}
|
||||
final int comma = cacheControl.indexOf(',', eq + 1);
|
||||
final String strAge;
|
||||
if (comma == -1) {
|
||||
strAge = cacheControl.substring(eq + 1);
|
||||
} else {
|
||||
strAge = cacheControl.substring(eq + 1, comma);
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(strAge);
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private String getEntityTags(URLConnection urlConnection) {
|
||||
final List<String> etags = urlConnection.getHeaderFields().get("ETag");
|
||||
if (etags == null || etags.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder b = new StringBuilder();
|
||||
final Iterator<String> it = etags.iterator();
|
||||
b.append(it.next());
|
||||
while (it.hasNext()) {
|
||||
b.append(", ").append(it.next());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
boolean appliesTo(URI uri) {
|
||||
return this.uri.equals(uri);
|
||||
}
|
||||
|
||||
void applyConditionals(URLConnection urlConnection) {
|
||||
if (lastModified != 0L) {
|
||||
urlConnection.setIfModifiedSince(lastModified);
|
||||
}
|
||||
if (entityTags != null && entityTags.length() > 0) {
|
||||
urlConnection.addRequestProperty("If-None-Match", entityTags);
|
||||
}
|
||||
}
|
||||
|
||||
boolean entityNeedsRevalidation() {
|
||||
return System.currentTimeMillis() > expiry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ abstract public class JsonSocket extends Socket {
|
||||
|
||||
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static final int HEADER_SIZE = 8;
|
||||
public static final int HEADER_SIZE = 16;
|
||||
public static final int HANDSHAKE_TIMEOUT = 5 * 1000;
|
||||
|
||||
public static final String TYPE_HELLO = DevPluginService.TYPE_HELLO;
|
||||
@@ -196,10 +196,10 @@ abstract public class JsonSocket extends Socket {
|
||||
} else {
|
||||
String header = new String(Arrays.copyOfRange(bytes, 0, HEADER_SIZE));
|
||||
|
||||
int dataSize = parseHeaderInt(header, 0);
|
||||
int dataSize = parseHeaderInt(header, 0, HEADER_SIZE - 2);
|
||||
Log.d(TAG, "Data length from header: " + dataSize);
|
||||
|
||||
int dataType = parseHeaderInt(header, 4);
|
||||
int dataType = parseHeaderInt(header, HEADER_SIZE - 2, 2);
|
||||
Log.d(TAG, "Data type from header: " + dataType);
|
||||
|
||||
mFragment = new Fragment(dataSize, dataType);
|
||||
@@ -226,9 +226,9 @@ abstract public class JsonSocket extends Socket {
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseHeaderInt(String header, int offset) {
|
||||
private static int parseHeaderInt(String header, int offset, int length) {
|
||||
try {
|
||||
return Integer.parseInt(new String(header.getBytes(UTF_8), offset, 4).replaceAll("\\D", ""));
|
||||
return Integer.parseInt(new String(header.getBytes(UTF_8), offset, length).replaceAll("\\D", ""));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
|
||||
@@ -16,12 +16,14 @@ import org.autojs.autojs.pref.Pref.getBoolean
|
||||
import org.autojs.autojs.pref.Pref.putBoolean
|
||||
import org.autojs.autojs.util.StringUtils.key
|
||||
import org.autojs.autojs.util.ViewUtils
|
||||
import org.autojs.autojs6.BuildConfig
|
||||
import org.autojs.autojs6.R
|
||||
import java.io.IOException
|
||||
import java.net.Socket
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : JsonSocket(service) {
|
||||
|
||||
private val jsonSocketExecutor = Executors.newSingleThreadExecutor()
|
||||
@@ -83,7 +85,7 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
|
||||
|
||||
private fun onHello(message: JsonObject) {
|
||||
var currentVersion: String? = null
|
||||
val requiredVersion = REQUIRED_SERVER_VERSION
|
||||
val requiredVersion = BuildConfig.VSCODE_EXT_REQUIRED_VERSION
|
||||
Log.i(TAG, "onHello: $message")
|
||||
val data = message["data"]
|
||||
if (data != null && data.isJsonObject) {
|
||||
@@ -121,18 +123,20 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
|
||||
""".trimIndent()
|
||||
|
||||
if (activity is Activity) {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title(activity.getString(R.string.text_connection_cannot_be_established))
|
||||
.content(msg)
|
||||
.positiveText(R.string.dialog_button_back)
|
||||
.build()
|
||||
.also {
|
||||
it.contentView?.apply {
|
||||
autoLinkMask = Linkify.WEB_URLS
|
||||
text = text
|
||||
activity.runOnUiThread {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title(activity.getString(R.string.text_connection_cannot_be_established))
|
||||
.content(msg)
|
||||
.positiveText(R.string.dialog_button_back)
|
||||
.build()
|
||||
.also {
|
||||
it.contentView?.apply {
|
||||
autoLinkMask = Linkify.WEB_URLS
|
||||
text = text
|
||||
}
|
||||
mHandler.post { it.show() }
|
||||
}
|
||||
mHandler.post { it.show() }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val toastMsg = """
|
||||
${activity.getString(R.string.text_min_version_of_vscode_vsc_ext)}:
|
||||
@@ -168,6 +172,7 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
|
||||
sRequiredBytesCommands[md5] = obj
|
||||
}
|
||||
}
|
||||
|
||||
else -> service.responseHandler.handle(obj)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -215,7 +220,6 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
|
||||
|
||||
companion object {
|
||||
|
||||
private const val REQUIRED_SERVER_VERSION = "1.0.5"
|
||||
private val TAG = JsonSocketClient::class.java.simpleName
|
||||
|
||||
var serverAddressHistories: LinkedHashSet<String>
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.autojs.autojs.core.eventloop.EventEmitter;
|
||||
import org.autojs.autojs.core.looper.Loopers;
|
||||
import org.autojs.autojs.pref.Language;
|
||||
import org.autojs.autojs.runtime.ScriptBridges;
|
||||
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||
import org.autojs.autojs.tool.MapBuilder;
|
||||
@@ -23,7 +22,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Created by Stardust on 2018/2/5.
|
||||
*/
|
||||
public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
||||
public class Sensors extends EventEmitter {
|
||||
|
||||
public class SensorEventEmitter extends EventEmitter implements SensorEventListener {
|
||||
|
||||
@@ -83,6 +82,13 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
||||
private final ScriptBridges mScriptBridges;
|
||||
private final SensorEventEmitter mNoOpSensorEventEmitter;
|
||||
private final ScriptRuntime mScriptRuntime;
|
||||
private final Loopers.AsyncTask mAsyncTask = new Loopers.AsyncTask("Sensors"){
|
||||
@Override
|
||||
public boolean onFinish(@NonNull Loopers loopers) {
|
||||
return !mSensorEventEmitters.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public Sensors(Context context, ScriptRuntime runtime) {
|
||||
super(runtime.bridges);
|
||||
@@ -90,7 +96,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
||||
mScriptBridges = runtime.bridges;
|
||||
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
|
||||
mScriptRuntime = runtime;
|
||||
runtime.loopers.addLooperQuitHandler(this);
|
||||
runtime.loopers.addAsyncTask(mAsyncTask);
|
||||
}
|
||||
|
||||
public SensorEventEmitter register(String sensorName) {
|
||||
@@ -122,13 +128,8 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldQuit() {
|
||||
return mSensorEventEmitters.isEmpty();
|
||||
}
|
||||
|
||||
public Sensor getSensor(String sensorName) {
|
||||
sensorName = sensorName.toUpperCase(Language.getPrefLanguage().getLocale());
|
||||
sensorName = sensorName.toUpperCase();
|
||||
Integer type = SENSORS.get(sensorName);
|
||||
type = type == null ? getSensorTypeByReflect(sensorName) : type;
|
||||
return type == null ? null : mSensorManager.getDefaultSensor(type);
|
||||
@@ -159,6 +160,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
||||
}
|
||||
mSensorEventEmitters.clear();
|
||||
}
|
||||
mScriptRuntime.loopers.removeLooperQuitHandler(this);
|
||||
mScriptRuntime.loopers.removeAsyncTask(mAsyncTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.autojs.autojs.runtime.api
|
||||
|
||||
import org.autojs.autojs.concurrent.VolatileDispose
|
||||
import org.autojs.autojs.core.looper.Loopers
|
||||
import org.autojs.autojs.core.looper.MainThreadProxy
|
||||
import org.autojs.autojs.core.looper.TimerThread
|
||||
import org.autojs.autojs.runtime.ScriptRuntime
|
||||
import org.autojs.autojs.util.StringUtils.str
|
||||
import org.autojs.autojs6.R
|
||||
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
|
||||
import org.mozilla.javascript.BaseFunction
|
||||
import org.mozilla.javascript.Context
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ThreadFactory
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
@@ -13,58 +17,91 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
* Created by Stardust on 2017/12/3.
|
||||
*/
|
||||
class Threads(private val mRuntime: ScriptRuntime) {
|
||||
|
||||
private val mThreads = HashSet<Thread>()
|
||||
val mainThread: Thread = Thread.currentThread()
|
||||
private val mMainThreadProxy = MainThreadProxy(Thread.currentThread(), mRuntime)
|
||||
private var mSpawnCount = 0
|
||||
private var mTaskCount = AtomicLong(0)
|
||||
private var mExit = false
|
||||
private val looperTask = Loopers.AsyncTask("AsyncTaskThreadPool")
|
||||
private val threadPool = Executors.newFixedThreadPool(20, ThreadFactory {
|
||||
val thread = Thread(fun() {
|
||||
Context.enter()
|
||||
try {
|
||||
it.run()
|
||||
} finally {
|
||||
Context.exit()
|
||||
}
|
||||
})
|
||||
thread.name = mainThread.name + " (AsyncThread)"
|
||||
thread
|
||||
})
|
||||
|
||||
val mainThread: Thread = Thread.currentThread()
|
||||
fun currentThread(): Any {
|
||||
val thread = Thread.currentThread()
|
||||
return if (thread === mainThread) mMainThreadProxy else thread
|
||||
}
|
||||
|
||||
fun currentThread(): Any = Thread.currentThread().let { thread ->
|
||||
if (thread === mainThread) mMainThreadProxy else thread
|
||||
fun runTaskForThreadPool(runnable: BaseFunction) {
|
||||
if (mTaskCount.addAndGet(1) == 1L) mRuntime.loopers.addAsyncTask(looperTask)
|
||||
threadPool.execute {
|
||||
try {
|
||||
runnable.call(
|
||||
Context.getCurrentContext(), runnable.parentScope, runnable,
|
||||
emptyArray()
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
if (!ScriptInterruptedException.causedByInterrupted(e)) {
|
||||
mRuntime.console.error("$this: ", e)
|
||||
}
|
||||
} finally {
|
||||
if (mTaskCount.addAndGet(-1) == 0L) {
|
||||
mRuntime.loopers.removeAsyncTask(looperTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start(runnable: Runnable): TimerThread {
|
||||
val thread = createThread(runnable)
|
||||
synchronized(mThreads) {
|
||||
check(!mExit) { str(R.string.error_script_is_on_exiting) }
|
||||
thread.let {
|
||||
mThreads.add(it)
|
||||
it.name = "${mainThread.name} (Spawn-$mSpawnCount)"
|
||||
mSpawnCount++
|
||||
it.start()
|
||||
}
|
||||
check(!mExit) { "script exiting" }
|
||||
mThreads.add(thread)
|
||||
thread.name = mainThread.name + " (Spawn-" + mSpawnCount + ")"
|
||||
mSpawnCount++
|
||||
thread.start()
|
||||
}
|
||||
return thread
|
||||
}
|
||||
|
||||
private fun createThread(runnable: Runnable): TimerThread {
|
||||
val millis = mRuntime.timers.maxCallbackUptimeMillisForAllThreads
|
||||
return object : TimerThread(mRuntime, millis, runnable) {
|
||||
|
||||
return object : TimerThread(mRuntime, runnable) {
|
||||
override fun onExit() {
|
||||
synchronized(mThreads) { mThreads.remove(currentThread()) }
|
||||
super.onExit()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun disposable() = VolatileDispose<Any?>()
|
||||
fun disposable(): VolatileDispose<*> {
|
||||
return VolatileDispose<Any?>()
|
||||
}
|
||||
|
||||
fun atomic(value: Long) = AtomicLong(value)
|
||||
fun atomic(value: Long): AtomicLong {
|
||||
return AtomicLong(value)
|
||||
}
|
||||
|
||||
fun atomic() = AtomicLong()
|
||||
|
||||
fun lock() = ReentrantLock()
|
||||
|
||||
fun shutDownAll() {
|
||||
threadPool.shutdownNow()
|
||||
synchronized(mThreads) {
|
||||
mThreads.apply {
|
||||
forEach { it.interrupt() }
|
||||
clear()
|
||||
for (thread in mThreads) {
|
||||
thread.interrupt()
|
||||
}
|
||||
mThreads.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +112,7 @@ class Threads(private val mRuntime: ScriptRuntime) {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasRunningThreads(): Boolean = synchronized(mThreads) { return mThreads.isNotEmpty() }
|
||||
|
||||
fun hasRunningThreads(): Boolean {
|
||||
synchronized(mThreads) { return mThreads.isNotEmpty() }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.autojs.autojs.runtime.api;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.autojs.autojs.concurrent.VolatileBox;
|
||||
import org.autojs.autojs.core.looper.Timer;
|
||||
import org.autojs.autojs.core.looper.TimerThread;
|
||||
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||
@@ -11,28 +8,24 @@ import org.autojs.autojs.runtime.ScriptRuntime;
|
||||
/**
|
||||
* Created by Stardust on 2017/7/21.
|
||||
*/
|
||||
|
||||
public class Timers {
|
||||
|
||||
private static final String LOG_TAG = "Timers";
|
||||
|
||||
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
|
||||
private final Threads mThreads;
|
||||
private final Timer mMainTimer;
|
||||
private final Timer mUiTimer;
|
||||
|
||||
//private VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
|
||||
private Threads mThreads;
|
||||
private Timer mUiTimer;
|
||||
private ScriptRuntime mRuntime;
|
||||
|
||||
public Timers(ScriptRuntime runtime) {
|
||||
mMainTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads);
|
||||
mUiTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads, Looper.getMainLooper());
|
||||
mUiTimer = new Timer(runtime, Looper.getMainLooper());
|
||||
mThreads = runtime.threads;
|
||||
mRuntime = runtime;
|
||||
}
|
||||
|
||||
public Timer getMainTimer() {
|
||||
return mMainTimer;
|
||||
}
|
||||
|
||||
VolatileBox<Long> getMaxCallbackUptimeMillisForAllThreads() {
|
||||
return mMaxCallbackUptimeMillisForAllThreads;
|
||||
return mRuntime.loopers.getMTimer();
|
||||
}
|
||||
|
||||
public Timer getTimerForCurrentThread() {
|
||||
@@ -41,19 +34,27 @@ public class Timers {
|
||||
|
||||
public Timer getTimerForThread(Thread thread) {
|
||||
if (thread == mThreads.getMainThread()) {
|
||||
return mMainTimer;
|
||||
return mRuntime.loopers.getMTimer();
|
||||
}
|
||||
Timer timer = TimerThread.getTimerForThread(thread);
|
||||
if (timer == null && Looper.myLooper() == Looper.getMainLooper()) {
|
||||
return mUiTimer;
|
||||
}
|
||||
return timer;
|
||||
if (timer == null) {
|
||||
return mRuntime.loopers.getMTimer();
|
||||
} else {
|
||||
return timer;
|
||||
}
|
||||
}
|
||||
|
||||
public int setTimeout(Object callback, long delay, Object... args) {
|
||||
return getTimerForCurrentThread().setTimeout(callback, delay, args);
|
||||
}
|
||||
|
||||
public int setTimeout(Object callback) {
|
||||
return setTimeout(callback, 1);
|
||||
}
|
||||
|
||||
public boolean clearTimeout(int id) {
|
||||
return getTimerForCurrentThread().clearTimeout(id);
|
||||
}
|
||||
@@ -62,6 +63,10 @@ public class Timers {
|
||||
return getTimerForCurrentThread().setInterval(listener, interval, args);
|
||||
}
|
||||
|
||||
public int setInterval(Object listener) {
|
||||
return setInterval(listener, 1);
|
||||
}
|
||||
|
||||
public boolean clearInterval(int id) {
|
||||
return getTimerForCurrentThread().clearInterval(id);
|
||||
}
|
||||
@@ -74,17 +79,8 @@ public class Timers {
|
||||
return getTimerForCurrentThread().clearImmediate(id);
|
||||
}
|
||||
|
||||
public boolean hasPendingCallbacks() {
|
||||
// 如果是脚本主线程,则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。
|
||||
if (mThreads.getMainThread() == Thread.currentThread()) {
|
||||
return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis();
|
||||
}
|
||||
// 否则检查当前线程的定时回调
|
||||
return getTimerForCurrentThread().hasPendingCallbacks();
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
mMainTimer.removeAllCallbacks();
|
||||
mRuntime.loopers.getMTimer().removeAllCallbacks();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,13 +54,11 @@ import org.autojs.autojs.ui.pager.ViewPager
|
||||
import org.autojs.autojs.ui.settings.PreferencesActivity
|
||||
import org.autojs.autojs.ui.widget.DrawerAutoClose
|
||||
import org.autojs.autojs.ui.widget.SearchViewItem
|
||||
import org.autojs.autojs.util.DeveloperUtils
|
||||
import org.autojs.autojs.util.ForegroundServiceUtils
|
||||
import org.autojs.autojs.util.StringUtils
|
||||
import org.autojs.autojs.util.UpdateUtils.autoCheckForUpdatesIfNeededWithSnackbar
|
||||
import org.autojs.autojs.util.ViewUtils
|
||||
import org.autojs.autojs.util.WorkingDirectoryUtils
|
||||
import org.autojs.autojs6.BuildConfig
|
||||
import org.autojs.autojs6.R
|
||||
import org.autojs.autojs6.databinding.ActivityMainBinding
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@@ -246,17 +244,6 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
|
||||
return if (i < 0) 2 else grantResults[i]
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// verifyApkIfNeeded()
|
||||
}
|
||||
|
||||
private fun verifyApkIfNeeded() {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
DeveloperUtils.verifyApk(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnActivityResultDelegateMediator() = mActivityResultMediator
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
|
||||
Reference in New Issue
Block a user