From 26eb5a6db5188e3c0f65e8ee02be138d9af1505e Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Wed, 12 Jul 2023 15:20:05 +0800 Subject: [PATCH] =?UTF-8?q?6.3.3=20-=20pre=20-=20=E4=BF=AE=E5=A4=8D=20VSCo?= =?UTF-8?q?de=20=E6=8F=92=E4=BB=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 62 +-- app/build.gradle.kts | 2 + app/src/main/assets/doc/CHANGELOG.md | 15 +- .../autojs/autojs/core/looper/Loopers.java | 211 ---------- .../org/autojs/autojs/core/looper/Loopers.kt | 190 +++++++++ .../org/autojs/autojs/core/looper/Timer.java | 131 ------- .../org/autojs/autojs/core/looper/Timer.kt | 136 +++++++ .../autojs/core/looper/TimerThread.java | 134 ------- .../autojs/autojs/core/looper/TimerThread.kt | 129 +++++++ .../core/ui/dialog/JsDialogBuilder.java | 7 +- .../autojs/engine/RhinoJavaScriptEngine.kt | 8 +- .../AssetAndUrlModuleSourceProvider.java | 64 ---- .../module/AssetAndUrlModuleSourceProvider.kt | 160 ++++++++ .../autojs/engine/module/ScopeRequire.kt | 261 +++++++++++++ .../module/UrlModuleSourceProvider.java | 360 ------------------ .../autojs/pluginclient/JsonSocket.java | 10 +- .../autojs/pluginclient/JsonSocketClient.kt | 30 +- .../autojs/autojs/runtime/api/Sensors.java | 21 +- .../org/autojs/autojs/runtime/api/Threads.kt | 86 +++-- .../org/autojs/autojs/runtime/api/Timers.java | 50 ++- .../org/autojs/autojs/ui/main/MainActivity.kt | 13 - gradle/wrapper/gradle-wrapper.properties | 4 +- version.properties | 8 +- 23 files changed, 1036 insertions(+), 1056 deletions(-) delete mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Loopers.java create mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt delete mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Timer.java create mode 100644 app/src/main/java/org/autojs/autojs/core/looper/Timer.kt delete mode 100644 app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java create mode 100644 app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt delete mode 100644 app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java create mode 100644 app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt create mode 100644 app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt delete mode 100644 app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java diff --git a/README.md b/README.md index 6c1ab9d0..88e94c96 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,18 @@ [comment]: <> "Version history only shows last 3 versions" +# 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)_ +* `优化` 适配 VSCode 插件 1.0.6 + # v6.3.2 ###### 2023/07/06 @@ -140,6 +152,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 支持按目录级别加载或同时加载多个文件 * `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v632)_ @@ -175,55 +188,6 @@ * `优化` 客户端模式非正常关闭时将于 AutoJs6 启动时按最近一次的历史地址自动连接 * `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v631)_ -# v6.3.0 - -###### 2023/04/29 - -* `新增` ocr 模块 (参阅 项目文档 > [光学字符识别](https://docs.autojs6.com/#/ocr)) _[`issue #8`](http://issues.autojs6.com/8)_ -* `新增` notice 模块 (参阅 项目文档 > [消息通知](https://docs.autojs6.com/#/notice)) -* `新增` s13n 模块 (参阅 项目文档 > [标准化](https://docs.autojs6.com/#/s13n)) -* `新增` Color 模块 (参阅 项目文档 > [颜色类](https://docs.autojs6.com/#/colorType)) -* `新增` 前台时保持屏幕常亮功能及设置选项 -* `新增` 额外的文档启动器 (launcher) 便于独立阅读应用文档 (支持在设置中隐藏或显示) -* `修复` colors.toString 方法功能异常 -* `修复` app.openUrl 方法自动添加协议前缀功能异常 -* `修复` app.viewFile/editFile 在参数对应文件不存在时的行为异常 -* `修复` pickup 方法的回调函数无法被调用的问题 -* `修复` 布局分析显示的控件信息 bounds 属性值负数符号被替换为逗号的问题 -* `修复` bounds/boundsInside/boundsContains 选择器无法正常筛选狭义空矩形 (如边界倒置矩形) _[`issue #49`](http://issues.autojs6.com/49)_ -* `修复` 更换主题或修改语言后点击或长按主页文档标签将导致应用崩溃的问题 -* `修复` 文本编辑器双指缩放调节字体大小时可能出现抖动的问题 -* `修复` 构建脚本中部分依赖源无法下载的问题 (已全部整合) _[`issue #40`](http://issues.autojs6.com/40)_ -* `修复` Tasker 无法添加 AutoJs6 操作插件 (Action Plugin) 的问题 (试修) _[`issue #41`](http://issues.autojs6.com/41)_ -* `修复` 高版本 JDK 编译项目时 ButterKnife 注解无法解析资源 ID 的问题 _[`issue #48`](http://issues.autojs6.com/48)_ -* `修复` 无障碍服务较大概率出现服务异常的问题 (试修) -* `修复` images.medianBlur 的 size 参数使用方式与文档不符的问题 -* `修复` engines 模块显示脚本全称时文件名与扩展名之间句点符号丢失的问题 -* `修复` 加权 RGB 距离检测算法内部实现可能存在的计算失误 (试修) -* `修复` console 模块的浮动窗口相关方法无法在 show 方法之前使用的问题 -* `修复` console.setSize 等方法可能无法生效的问题 _[`issue #50`](http://issues.autojs6.com/50)_ -* `修复` colors.material 颜色空间的颜色常量赋值错误 -* `修复` UI 模式的日期选择控件 minDate 及 maxDate 属性无法正确解析日期格式的问题 -* `修复` 运行脚本后快速切换到主页 "任务" 标签页面将出现两个相同运行中任务的问题 -* `修复` 文件管理页面从其他页面返回时页面状态可能被重置的问题 _[`issue #52`](http://issues.autojs6.com/52)_ -* `修复` 文件管理页面排序状态与图标显示状态不符的问题 -* `优化` 文件管理页面增加文件及文件夹修改时间显示 -* `优化` 文件管理页面排序类型支持状态记忆 -* `优化` README.md 添加项目编译构建小节与脚本开发辅助小节 _[`issue #33`](http://issues.autojs6.com/33)_ -* `优化` images 模块相关方法的区域 (region) 选项参数支持更多传入方式 (参阅 项目文档 > [全能类型](https://docs.autojs6.com/#/omniTypes?id=omniregion)) -* `优化` app.startActivity 页面简写参数增加 pref/homepage/docs/about 等形式的支持 -* `优化` web 模块的全局方法挂载到模块本身以增强可用性 (参阅 项目文档 > [万维网](https://docs.autojs6.com/#/web)) -* `优化` web.newInjectableWebView 方法内部默认实现部分常用的 WebView 设置选项 -* `优化` colors 模块添加多种转换方法及工具方法并添加更多静态常量以及可直接作为参数的颜色名称 -* `优化` console 模块添加多种控制台浮动窗口的样式配置方法并支持 build 构建器统一配置窗口样式 -* `优化` 控制台浮动窗口支持拖动标题区域移动窗口位置 -* `优化` 控制台浮动窗口支持脚本结束后自动延迟关闭 -* `优化` 控制台浮动窗口及其 Activity 活动窗口支持双指缩放调整字体大小 -* `优化` http 模块相关方法支持超时参数 (timeout) -* `优化` Gradle 构建脚本支持 JDK 版本主动降级 (fallback) -* `优化` Gradle 构建脚本支持根据平台类型及版本自动选择合适的构建工具版本 (程度有限) -* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v630)_ - ##### 更多版本历史可参阅 * [CHANGELOG.md](http://changelog.autojs6.com) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4fa01209..86b9419a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -361,6 +361,7 @@ android { } } buildConfigField("String", "VERSION_DATE", "\"${Utils.getDateString("MMM d, yyyy", "GMT+08:00")}\"") + buildConfigField("String", "VSCODE_EXT_REQUIRED_VERSION", "\"${versions.vscodeExtRequiredVersion}\"") } applicationVariants.all { @@ -460,6 +461,7 @@ class Versions(filePath: String) { val sdkVersionCompile = properties["COMPILE_SDK_VERSION"].let { it as String }.toInt() val appVersionName = properties["VERSION_NAME"] as String val appVersionCode = properties["VERSION_BUILD"].let { it as String }.toInt() + val vscodeExtRequiredVersion = properties["VSCODE_EXT_REQUIRED_VERSION"] as String private val javaVersionRaw = properties["JAVA_VERSION"] as String diff --git a/app/src/main/assets/doc/CHANGELOG.md b/app/src/main/assets/doc/CHANGELOG.md index 1577d275..440810b2 100644 --- a/app/src/main/assets/doc/CHANGELOG.md +++ b/app/src/main/assets/doc/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.java b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.java deleted file mode 100644 index 62324a57..00000000 --- a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.autojs.autojs.core.looper; - -import android.os.Handler; -import android.os.Looper; -import android.os.MessageQueue; -import android.util.Log; - -import androidx.annotation.Nullable; - -import org.autojs.autojs.lang.ThreadCompat; -import org.autojs.autojs.rhino.AutoJsContext; -import org.autojs.autojs.runtime.ScriptRuntime; -import org.autojs.autojs.runtime.api.Threads; -import org.autojs.autojs.runtime.api.Timers; -import org.autojs.autojs.runtime.exception.ScriptInterruptedException; -import org.mozilla.javascript.Context; - -import java.util.HashSet; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Created by Stardust on 2017/7/29. - */ -@SuppressWarnings("ConstantConditions") -public class Loopers implements MessageQueue.IdleHandler { - - private static final String LOG_TAG = "Loopers"; - - public interface LooperQuitHandler { - boolean shouldQuit(); - } - - private static final Runnable EMPTY_RUNNABLE = () -> { - }; - - private final ThreadLocal waitWhenIdle = new ThreadLocal<>() { - @Nullable - @Override - protected Boolean initialValue() { - return Looper.myLooper() == Looper.getMainLooper(); - } - }; - private final ThreadLocal> waitIds = new ThreadLocal<>() { - @Nullable - @Override - protected HashSet initialValue() { - return new HashSet<>(); - } - }; - private final ThreadLocal maxWaitId = new ThreadLocal<>() { - @Nullable - @Override - protected Integer initialValue() { - return 0; - } - }; - private final ThreadLocal> looperQuitHandlers = new ThreadLocal<>(); - private volatile Looper mServantLooper; - private final Timers mTimers; - private LooperQuitHandler mMainLooperQuitHandler; - private final Handler mMainHandler; - private final Looper mMainLooper; - private final Threads mThreads; - private final MessageQueue mMainMessageQueue; - - public Loopers(ScriptRuntime runtime) { - mTimers = runtime.timers; - mThreads = runtime.threads; - prepare(); - mMainLooper = Looper.myLooper(); - mMainHandler = new Handler(); - mMainMessageQueue = Looper.myQueue(); - } - - - public Looper getMainLooper() { - return mMainLooper; - } - - public void addLooperQuitHandler(LooperQuitHandler handler) { - CopyOnWriteArrayList handlers = looperQuitHandlers.get(); - if (handlers == null) { - handlers = new CopyOnWriteArrayList<>(); - looperQuitHandlers.set(handlers); - } - handlers.add(handler); - } - - public boolean removeLooperQuitHandler(LooperQuitHandler handler) { - CopyOnWriteArrayList handlers = looperQuitHandlers.get(); - return handlers != null && handlers.remove(handler); - } - - private boolean shouldQuitLooper() { - if (Thread.currentThread().isInterrupted()) { - return true; - } - if (mTimers.hasPendingCallbacks()) { - return false; - } - if (waitWhenIdle.get() || !waitIds.get().isEmpty()) { - return false; - } - if (((AutoJsContext) Context.getCurrentContext()).hasPendingContinuation()) { - return false; - } - CopyOnWriteArrayList handlers = looperQuitHandlers.get(); - if (handlers == null) { - return true; - } - for (LooperQuitHandler handler : handlers) { - if (!handler.shouldQuit()) { - return false; - } - } - return true; - } - - - private void initServantThread() { - final Object lock = Loopers.this; - new ThreadCompat(() -> { - Looper.prepare(); - mServantLooper = Looper.myLooper(); - synchronized (lock) { - lock.notifyAll(); - } - Looper.loop(); - }).start(); - } - - public Looper getServantLooper() { - if (mServantLooper == null) { - initServantThread(); - synchronized (this) { - try { - this.wait(); - } catch (InterruptedException e) { - throw new ScriptInterruptedException(); - } - } - } - return mServantLooper; - } - - private void quitServantLooper() { - if (mServantLooper == null) - return; - mServantLooper.quit(); - } - - public int waitWhenIdle() { - int id = maxWaitId.get(); - Log.d(LOG_TAG, "waitWhenIdle: " + id); - maxWaitId.set(id + 1); - waitIds.get().add(id); - return id; - } - - public void doNotWaitWhenIdle(int waitId) { - Log.d(LOG_TAG, "doNotWaitWhenIdle: " + waitId); - waitIds.get().remove(waitId); - } - - public void waitWhenIdle(boolean b) { - waitWhenIdle.set(b); - } - - public void recycle() { - quitServantLooper(); - mMainMessageQueue.removeIdleHandler(this); - } - - public void setMainLooperQuitHandler(LooperQuitHandler mainLooperQuitHandler) { - mMainLooperQuitHandler = mainLooperQuitHandler; - } - - @Override - public boolean queueIdle() { - Looper l = Looper.myLooper(); - if (l == null) - return true; - if (l == mMainLooper) { - Log.d(LOG_TAG, "main looper queueIdle"); - if (shouldQuitLooper() && !mThreads.hasRunningThreads() && - mMainLooperQuitHandler != null && mMainLooperQuitHandler.shouldQuit()) { - Log.d(LOG_TAG, "main looper quit"); - l.quit(); - } - } else { - Log.d(LOG_TAG, "looper queueIdle: " + l); - if (shouldQuitLooper()) { - l.quit(); - } - } - return true; - } - - public void prepare() { - if (Looper.myLooper() == null) - LooperHelper.prepare(); - Looper.myQueue().addIdleHandler(this); - } - - public void notifyThreadExit(TimerThread thread) { - Log.d(LOG_TAG, "notifyThreadExit: " + thread); - //当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程), - //此时通过向主线程发送一个空的Runnable,主线程执行完这个Runnable后会触发IdleHandler,从而检查自身是否退出 - mMainHandler.post(EMPTY_RUNNABLE); - } -} diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt new file mode 100644 index 00000000..bc409ddd --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt @@ -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() + var isEnd: Boolean = false + private set + + //线程即将退出时调用,返回true阻止线程退出,只要有一个task返回true线程就不会退出 + open fun onFinish(loopers: Loopers): Boolean { + return true + } + + fun end() { + isEnd = true + } + + //线程正在退出,这里应该结束任务的执行,回收资源 + open fun onStop(loopers: Loopers) {} + override fun toString(): String { + return "AsyncTask: $describe" + } + } + + private var waitWhenIdle: Boolean + + @Volatile + private var mServantLooper: Looper? = null + private var mMainLooperQuitHandler: LooperQuitHandler? = null + private val allTasks = ConcurrentLinkedQueue() + val mTimer: Timer + 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 {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Timer.java b/app/src/main/java/org/autojs/autojs/core/looper/Timer.java deleted file mode 100644 index dae315b6..00000000 --- a/app/src/main/java/org/autojs/autojs/core/looper/Timer.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.autojs.autojs.core.looper; - -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.SparseArray; - -import org.autojs.autojs.runtime.ScriptRuntime; -import org.autojs.autojs.concurrent.VolatileBox; - -/** - * Created by Stardust on 2017/12/27. - */ -public class Timer { - - private final SparseArray mHandlerCallbacks = new SparseArray<>(); - private int mCallbackMaxId = 0; - private final ScriptRuntime mRuntime; - private final Handler mHandler; - private long mMaxCallbackUptimeMillis = 0; - private final VolatileBox mMaxCallbackMillisForAllThread; - - public Timer(ScriptRuntime runtime, VolatileBox maxCallbackMillisForAllThread) { - mRuntime = runtime; - mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread; - mHandler = new Handler(); - } - - public Timer(ScriptRuntime runtime, VolatileBox maxCallbackMillisForAllThread, Looper looper) { - mRuntime = runtime; - mMaxCallbackMillisForAllThread = maxCallbackMillisForAllThread; - mHandler = new Handler(looper); - } - - public int setTimeout(final Object callback, final long delay, final Object... args) { - mCallbackMaxId++; - final int id = mCallbackMaxId; - Runnable r = () -> { - callFunction(callback, args); - mHandlerCallbacks.remove(id); - }; - mHandlerCallbacks.put(id, r); - postDelayed(r, delay); - return id; - } - - private void callFunction(Object callback, Object[] args) { - if(Looper.myLooper() == Looper.getMainLooper()){ - try { - mRuntime.bridges.callFunction(callback, null, args); - }catch (Exception e){ - mRuntime.exit(e); - } - }else { - mRuntime.bridges.callFunction(callback, null, args); - } - } - - public boolean clearTimeout(int id) { - return clearCallback(id); - } - - public int setInterval(final Object listener, final long interval, final Object... args) { - mCallbackMaxId++; - final int id = mCallbackMaxId; - final Runnable r = new Runnable() { - @Override - public void run() { - if (mHandlerCallbacks.get(id) == null) - return; - callFunction(listener, args); - postDelayed(this, interval); - } - }; - mHandlerCallbacks.put(id, r); - postDelayed(r, interval); - return id; - } - - public void postDelayed(Runnable r, long interval) { - long uptime = SystemClock.uptimeMillis() + interval; - mHandler.postAtTime(r, uptime); - mMaxCallbackUptimeMillis = Math.max(mMaxCallbackUptimeMillis, uptime); - synchronized (mMaxCallbackMillisForAllThread) { - mMaxCallbackMillisForAllThread.set(Math.max(mMaxCallbackMillisForAllThread.get(), uptime)); - } - } - - public void post(Runnable r) { - - } - - public boolean clearInterval(int id) { - return clearCallback(id); - } - - public int setImmediate(final Object listener, final Object... args) { - mCallbackMaxId++; - final int id = mCallbackMaxId; - Runnable r = () -> { - callFunction(listener, args); - mHandlerCallbacks.remove(id); - }; - mHandlerCallbacks.put(id, r); - postDelayed(r, 0); - return id; - } - - public boolean clearImmediate(int id) { - return clearCallback(id); - } - - private boolean clearCallback(int id) { - Runnable callback = mHandlerCallbacks.get(id); - if (callback != null) { - mHandler.removeCallbacks(callback); - mHandlerCallbacks.remove(id); - return true; - } - return false; - } - - public boolean hasPendingCallbacks() { - return mMaxCallbackUptimeMillis > SystemClock.uptimeMillis(); - } - - public void removeAllCallbacks() { - mHandler.removeCallbacksAndMessages(null); - } - -} diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt b/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt new file mode 100644 index 00000000..b3e02adc --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt @@ -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() + 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 = + (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 {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java deleted file mode 100644 index f9434692..00000000 --- a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.autojs.autojs.core.looper; - -import static org.autojs.autojs.util.StringUtils.str; - -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; - -import org.autojs.autojs.concurrent.VolatileBox; -import org.autojs.autojs.engine.RhinoJavaScriptEngine; -import org.autojs.autojs.lang.ThreadCompat; -import org.autojs.autojs.runtime.ScriptRuntime; -import org.autojs.autojs.runtime.exception.ScriptInterruptedException; -import org.autojs.autojs6.R; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * Created by Stardust on 2017/12/27. - */ -public class TimerThread extends ThreadCompat { - - private static final ConcurrentHashMap sTimerMap = new ConcurrentHashMap<>(); - - private Timer mTimer; - private final VolatileBox mMaxCallbackUptimeMillisForAllThreads; - private final ScriptRuntime mRuntime; - private final Runnable mTarget; - private boolean mRunning = false; - private final Object mRunningLock = new Object(); - - public TimerThread(ScriptRuntime runtime, VolatileBox maxCallbackUptimeMillisForAllThreads, Runnable target) { - super(target); - mRuntime = runtime; - mTarget = target; - mMaxCallbackUptimeMillisForAllThreads = maxCallbackUptimeMillisForAllThreads; - } - - @Override - public void run() { - mRuntime.loopers.prepare(); - mTimer = new Timer(mRuntime, mMaxCallbackUptimeMillisForAllThreads); - sTimerMap.put(Thread.currentThread(), mTimer); - ((RhinoJavaScriptEngine) mRuntime.engines.myEngine()).enterContext(); - notifyRunning(); - new Handler().post(mTarget); - try { - Looper.loop(); - } catch (Throwable e) { - if (!ScriptInterruptedException.causedByInterrupted(e)) { - mRuntime.console.error(Thread.currentThread() + ": ", e); - } - } finally { - onExit(); - mTimer = null; - org.mozilla.javascript.Context.exit(); - sTimerMap.remove(Thread.currentThread(), mTimer); - } - } - - @Override - public void interrupt() { - LooperHelper.quitForThread(this); - super.interrupt(); - } - - private void notifyRunning() { - synchronized (mRunningLock) { - mRunning = true; - mRunningLock.notifyAll(); - } - } - - @CallSuper - protected void onExit() { - mRuntime.loopers.notifyThreadExit(this); - } - - public static Timer getTimerForThread(Thread thread) { - return sTimerMap.get(thread); - } - - public static Timer getTimerForCurrentThread() { - return getTimerForThread(Thread.currentThread()); - } - - public int setTimeout(Object callback, long delay, Object... args) { - return getTimer().setTimeout(callback, delay, args); - } - - public Timer getTimer() { - if (mTimer == null) { - throw new IllegalStateException(str(R.string.error_thread_is_not_alive)); - } - return mTimer; - } - - public boolean clearTimeout(int id) { - return getTimer().clearTimeout(id); - } - - public int setInterval(Object listener, long interval, Object... args) { - return getTimer().setInterval(listener, interval, args); - } - - public boolean clearInterval(int id) { - return getTimer().clearInterval(id); - } - - public int setImmediate(Object listener, Object... args) { - return getTimer().setImmediate(listener, args); - } - - public boolean clearImmediate(int id) { - return getTimer().clearImmediate(id); - } - - public void waitFor() throws InterruptedException { - synchronized (mRunningLock) { - if (!mRunning) { - mRunningLock.wait(); - } - } - } - - @NonNull - @Override - public String toString() { - return "Thread[" + getName() + "," + getPriority() + "]"; - } - -} diff --git a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt new file mode 100644 index 00000000..d30c089d --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt @@ -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) + } + + fun setTimeout(callback: Any): Int { + return setTimeout(callback, 1) + } + + val timer: Timer + get() { + checkNotNull(mTimer) { "thread is not alive" } + return mTimer as Timer + } + + fun clearTimeout(id: Int): Boolean { + return timer.clearTimeout(id) + } + + fun setInterval(listener: Any?, interval: Long, vararg args: Any?): Int { + return timer.setInterval(listener!!, interval, *args as Array) + } + + fun setInterval(listener: Any?): Int { + return setInterval(listener, 1) + } + + fun clearInterval(id: Int): Boolean { + return timer.clearInterval(id) + } + + fun setImmediate(listener: Any, vararg args: Any?): Int { + return timer.setImmediate(listener, *args as Array) + } + + fun clearImmediate(id: Int): Boolean { + return timer.clearImmediate(id) + } + + @Throws(InterruptedException::class) + fun waitFor() { + synchronized(mRunningLock) { + if (mRunning) return + mRunningLock.wait() + } + } + + override fun toString(): String { + return "Thread[$name,$priority]" + } + + companion object { + private val sTimerMap = ConcurrentHashMap() + + @JvmStatic + fun getTimerForThread(thread: Thread): Timer? { + return sTimerMap[thread] + } + + val timerForCurrentThread: Timer? + get() = getTimerForThread(currentThread()) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java b/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java index 3cade410..440cd3bf 100644 --- a/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java +++ b/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java @@ -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() { diff --git a/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt b/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt index 52243e86..92067d89 100644 --- a/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt +++ b/app/src/main/java/org/autojs/autojs/engine/RhinoJavaScriptEngine.kt @@ -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(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 { diff --git a/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java b/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java deleted file mode 100644 index fd9738fd..00000000 --- a/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.java +++ /dev/null @@ -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 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)); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt b/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt new file mode 100644 index 00000000..329c2e0c --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/engine/module/AssetAndUrlModuleSourceProvider.kt @@ -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? = null +) : ModuleSourceProviderBase() { + val mContext = context + private val okHttpClient = OkHttpClient.Builder().followRedirects(true).build() + private val contentResolver: ContentResolver = context.contentResolver + private val moduleSources: ArrayList = 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>( + 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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt b/app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt new file mode 100644 index 00000000..bb38955e --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/engine/module/ScopeRequire.kt @@ -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 = 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 { + 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): 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? = + loadingModuleInterfaces.get() as? MutableMap + 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>() + 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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java b/app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java deleted file mode 100644 index 82b1a7e2..00000000 --- a/app/src/main/java/org/autojs/autojs/engine/module/UrlModuleSourceProvider.java +++ /dev/null @@ -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 privilegedUris; - private final Iterable 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 privilegedUris, - Iterable 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 privilegedUris, - Iterable 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 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 etags = urlConnection.getHeaderFields().get("ETag"); - if (etags == null || etags.isEmpty()) { - return null; - } - final StringBuilder b = new StringBuilder(); - final Iterator 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; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java index 19e3eaac..297b6d33 100644 --- a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java +++ b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java @@ -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; diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt index 2f3592aa..3b09d24c 100644 --- a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt +++ b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt @@ -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 diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java b/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java index 90c0e28d..5e823b79 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java @@ -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); } } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt b/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt index 6a850839..d3be8e14 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt @@ -1,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() + 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() + fun disposable(): VolatileDispose<*> { + return VolatileDispose() + } - fun atomic(value: Long) = AtomicLong(value) + fun atomic(value: Long): AtomicLong { + return AtomicLong(value) + } fun atomic() = AtomicLong() fun lock() = ReentrantLock() fun shutDownAll() { + threadPool.shutdownNow() synchronized(mThreads) { - mThreads.apply { - forEach { it.interrupt() } - clear() + for (thread in mThreads) { + thread.interrupt() } + mThreads.clear() } } @@ -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() } + } } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java b/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java index c218767c..56e5628a 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java @@ -1,9 +1,6 @@ package org.autojs.autojs.runtime.api; import android.os.Looper; -import android.os.SystemClock; - -import org.autojs.autojs.concurrent.VolatileBox; import org.autojs.autojs.core.looper.Timer; import org.autojs.autojs.core.looper.TimerThread; import org.autojs.autojs.runtime.ScriptRuntime; @@ -11,28 +8,24 @@ import org.autojs.autojs.runtime.ScriptRuntime; /** * Created by Stardust on 2017/7/21. */ + public class Timers { private static final String LOG_TAG = "Timers"; - private final VolatileBox mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); - private final Threads mThreads; - private final Timer mMainTimer; - private final Timer mUiTimer; - + //private VolatileBox mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); + private Threads mThreads; + private Timer mUiTimer; + private ScriptRuntime mRuntime; public Timers(ScriptRuntime runtime) { - mMainTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads); - mUiTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads, Looper.getMainLooper()); + mUiTimer = new Timer(runtime, Looper.getMainLooper()); mThreads = runtime.threads; + mRuntime = runtime; } public Timer getMainTimer() { - return mMainTimer; - } - - VolatileBox getMaxCallbackUptimeMillisForAllThreads() { - return mMaxCallbackUptimeMillisForAllThreads; + return mRuntime.loopers.getMTimer(); } public Timer getTimerForCurrentThread() { @@ -41,19 +34,27 @@ public class Timers { public Timer getTimerForThread(Thread thread) { if (thread == mThreads.getMainThread()) { - return mMainTimer; + return mRuntime.loopers.getMTimer(); } Timer timer = TimerThread.getTimerForThread(thread); if (timer == null && Looper.myLooper() == Looper.getMainLooper()) { return mUiTimer; } - return timer; + if (timer == null) { + return mRuntime.loopers.getMTimer(); + } else { + return timer; + } } public int setTimeout(Object callback, long delay, Object... args) { return getTimerForCurrentThread().setTimeout(callback, delay, args); } + public int setTimeout(Object callback) { + return setTimeout(callback, 1); + } + public boolean clearTimeout(int id) { return getTimerForCurrentThread().clearTimeout(id); } @@ -62,6 +63,10 @@ public class Timers { return getTimerForCurrentThread().setInterval(listener, interval, args); } + public int setInterval(Object listener) { + return setInterval(listener, 1); + } + public boolean clearInterval(int id) { return getTimerForCurrentThread().clearInterval(id); } @@ -74,17 +79,8 @@ public class Timers { return getTimerForCurrentThread().clearImmediate(id); } - public boolean hasPendingCallbacks() { - // 如果是脚本主线程,则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。 - if (mThreads.getMainThread() == Thread.currentThread()) { - return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis(); - } - // 否则检查当前线程的定时回调 - return getTimerForCurrentThread().hasPendingCallbacks(); - } - public void recycle() { - mMainTimer.removeAllCallbacks(); + mRuntime.loopers.getMTimer().removeAllCallbacks(); } } diff --git a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt index eab2df3c..b96757c8 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt @@ -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") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 67c8ae7d..e4743973 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jul 03 16:17:34 CST 2023 +#Wed Jul 12 09:21:49 CST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/version.properties b/version.properties index a3ee7381..617c4e6a 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,10 @@ -#Fri Jul 07 01:39:02 CST 2023 -BUILD_TIME=1688665142214 +#Wed Jul 12 14:08:52 CST 2023 +BUILD_TIME=1689142132084 COMPILE_SDK_VERSION=33 JAVA_VERSION=20 MIN_SDK_VERSION=24 TARGET_SDK_VERSION=33 +#2006 VERSION_BUILD=1999 -VERSION_NAME=6.3.2 +VERSION_NAME=6.3.3 +VSCODE_EXT_REQUIRED_VERSION=1.0.8