6.3.3 - pre - 修复 VSCode 插件问题

This commit is contained in:
SuperMonster003
2023-07-12 15:20:05 +08:00
parent 7b1c8bddc8
commit 26eb5a6db5
23 changed files with 1036 additions and 1056 deletions

View File

@@ -110,6 +110,18 @@
[comment]: <> "Version history only shows last 3 versions" [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 # v6.3.2
###### 2023/07/06 ###### 2023/07/06
@@ -140,6 +152,7 @@
* `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_ * `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_
* `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_ * `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_
* `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭) * `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭)
* `优化` findMultiColors 方法内部实现 (by [LYS](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_
* `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件 * `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件
* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v632)_ * `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v632)_
@@ -175,55 +188,6 @@
* `优化` 客户端模式非正常关闭时将于 AutoJs6 启动时按最近一次的历史地址自动连接 * `优化` 客户端模式非正常关闭时将于 AutoJs6 启动时按最近一次的历史地址自动连接
* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v631)_ * `优化` 部分依赖或本地库版本调整 _[`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) * [CHANGELOG.md](http://changelog.autojs6.com)

View File

@@ -361,6 +361,7 @@ android {
} }
} }
buildConfigField("String", "VERSION_DATE", "\"${Utils.getDateString("MMM d, yyyy", "GMT+08:00")}\"") buildConfigField("String", "VERSION_DATE", "\"${Utils.getDateString("MMM d, yyyy", "GMT+08:00")}\"")
buildConfigField("String", "VSCODE_EXT_REQUIRED_VERSION", "\"${versions.vscodeExtRequiredVersion}\"")
} }
applicationVariants.all { applicationVariants.all {
@@ -460,6 +461,7 @@ class Versions(filePath: String) {
val sdkVersionCompile = properties["COMPILE_SDK_VERSION"].let { it as String }.toInt() val sdkVersionCompile = properties["COMPILE_SDK_VERSION"].let { it as String }.toInt()
val appVersionName = properties["VERSION_NAME"] as String val appVersionName = properties["VERSION_NAME"] as String
val appVersionCode = properties["VERSION_BUILD"].let { it as String }.toInt() 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 private val javaVersionRaw = properties["JAVA_VERSION"] as String

View File

@@ -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 # v6.3.2
###### 2023/07/06 ###### 2023/07/06
@@ -34,6 +46,7 @@
* `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_ * `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_
* `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_ * `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_
* `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭) * `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭)
* `优化` findMultiColors 方法内部实现 (by [LYS](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_
* `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件 * `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件
* `优化` 升级 Leakcanary 版本 2.11 -> 2.12 * `优化` 升级 Leakcanary 版本 2.11 -> 2.12
* `优化` 升级 Android Analytics 版本 14.2.0 -> 14.3.0 * `优化` 升级 Android Analytics 版本 14.2.0 -> 14.3.0
@@ -382,7 +395,7 @@
* `修复` toast.dismiss() 可能无效的问题 * `修复` 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 异常 * `优化` 忽略 sleep() 的 ScriptInterruptedException 异常
* `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2 * `优化` 附加 Androidx AppCompat (Legacy) 版本 1.0.2
* `优化` 升级 Androidx AppCompat 版本 1.4.0 -> 1.4.1 * `优化` 升级 Androidx AppCompat 版本 1.4.0 -> 1.4.1

View File

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

View 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 {}
}
}

View File

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

View 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 {}
}
}

View File

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

View 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())
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.widget.EditText; import android.widget.EditText;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import org.autojs.autojs.core.eventloop.EventEmitter; import org.autojs.autojs.core.eventloop.EventEmitter;
import org.autojs.autojs.core.looper.Loopers; import org.autojs.autojs.core.looper.Loopers;
import org.autojs.autojs.core.looper.Timer; import org.autojs.autojs.core.looper.Timer;
@@ -20,7 +21,7 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
private final Timer mTimer; private final Timer mTimer;
private final Loopers mLoopers; private final Loopers mLoopers;
private JsDialog mDialog; private JsDialog mDialog;
private volatile int mWaitId = -1; private volatile Loopers.AsyncTask task;
public JsDialogBuilder(Context context, ScriptRuntime runtime) { public JsDialogBuilder(Context context, ScriptRuntime runtime) {
@@ -55,14 +56,14 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
} }
}); });
dismissListener(dialog -> { dismissListener(dialog -> {
mTimer.postDelayed(() -> mLoopers.doNotWaitWhenIdle(mWaitId), 0); mTimer.postDelayed(() -> mLoopers.removeAsyncTask(task), 0);
emit("dismiss", dialog); emit("dismiss", dialog);
}); });
cancelListener(dialog -> emit("cancel", dialog)); cancelListener(dialog -> emit("cancel", dialog));
} }
public void onShowCalled() { public void onShowCalled() {
mTimer.postDelayed(() -> mWaitId = mLoopers.waitWhenIdle(), 0); mTimer.postDelayed(() -> task = mLoopers.createAndAddAsyncTask("js-dialog"), 0);
} }
public JsDialog getDialog() { public JsDialog getDialog() {

View File

@@ -5,6 +5,7 @@ import android.view.View
import org.autojs.autojs.core.automator.UiObjectCollection import org.autojs.autojs.core.automator.UiObjectCollection
import org.autojs.autojs.core.ui.ViewExtras import org.autojs.autojs.core.ui.ViewExtras
import org.autojs.autojs.engine.module.AssetAndUrlModuleSourceProvider import org.autojs.autojs.engine.module.AssetAndUrlModuleSourceProvider
import org.autojs.autojs.engine.module.ScopeRequire
import org.autojs.autojs.execution.ExecutionConfig import org.autojs.autojs.execution.ExecutionConfig
import org.autojs.autojs.pio.UncheckedIOException import org.autojs.autojs.pio.UncheckedIOException
import org.autojs.autojs.project.ScriptConfig import org.autojs.autojs.project.ScriptConfig
@@ -17,7 +18,6 @@ import org.mozilla.javascript.Context
import org.mozilla.javascript.Script import org.mozilla.javascript.Script
import org.mozilla.javascript.Scriptable import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.ScriptableObject import org.mozilla.javascript.ScriptableObject
import org.mozilla.javascript.commonjs.module.RequireBuilder
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -121,11 +121,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
mAndroidContext, MODULES_PATH, mAndroidContext, MODULES_PATH,
listOf<URI>(File("/").toURI()) listOf<URI>(File("/").toURI())
) )
RequireBuilder() ScopeRequire(context, scope, SoftCachingModuleScriptProvider(provider)).install(scope)
.setModuleScriptProvider(SoftCachingModuleScriptProvider(provider))
.setSandboxed(true)
.createRequire(context, scope)
.install(scope)
} }
protected fun createScope(context: Context): TopLevelScope { protected fun createScope(context: Context): TopLevelScope {

View File

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

View File

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

View File

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

View File

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

View File

@@ -74,7 +74,7 @@ abstract public class JsonSocket extends Socket {
private final ExecutorService executorService = Executors.newSingleThreadExecutor(); 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 int HANDSHAKE_TIMEOUT = 5 * 1000;
public static final String TYPE_HELLO = DevPluginService.TYPE_HELLO; public static final String TYPE_HELLO = DevPluginService.TYPE_HELLO;
@@ -196,10 +196,10 @@ abstract public class JsonSocket extends Socket {
} else { } else {
String header = new String(Arrays.copyOfRange(bytes, 0, HEADER_SIZE)); 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); 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); Log.d(TAG, "Data type from header: " + dataType);
mFragment = new Fragment(dataSize, 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 { 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return 0; return 0;

View File

@@ -16,12 +16,14 @@ import org.autojs.autojs.pref.Pref.getBoolean
import org.autojs.autojs.pref.Pref.putBoolean import org.autojs.autojs.pref.Pref.putBoolean
import org.autojs.autojs.util.StringUtils.key import org.autojs.autojs.util.StringUtils.key
import org.autojs.autojs.util.ViewUtils import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.BuildConfig
import org.autojs.autojs6.R import org.autojs.autojs6.R
import java.io.IOException import java.io.IOException
import java.net.Socket import java.net.Socket
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.concurrent.Executors import java.util.concurrent.Executors
class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : JsonSocket(service) { class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : JsonSocket(service) {
private val jsonSocketExecutor = Executors.newSingleThreadExecutor() private val jsonSocketExecutor = Executors.newSingleThreadExecutor()
@@ -83,7 +85,7 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
private fun onHello(message: JsonObject) { private fun onHello(message: JsonObject) {
var currentVersion: String? = null var currentVersion: String? = null
val requiredVersion = REQUIRED_SERVER_VERSION val requiredVersion = BuildConfig.VSCODE_EXT_REQUIRED_VERSION
Log.i(TAG, "onHello: $message") Log.i(TAG, "onHello: $message")
val data = message["data"] val data = message["data"]
if (data != null && data.isJsonObject) { if (data != null && data.isJsonObject) {
@@ -121,18 +123,20 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
""".trimIndent() """.trimIndent()
if (activity is Activity) { if (activity is Activity) {
MaterialDialog.Builder(activity) activity.runOnUiThread {
.title(activity.getString(R.string.text_connection_cannot_be_established)) MaterialDialog.Builder(activity)
.content(msg) .title(activity.getString(R.string.text_connection_cannot_be_established))
.positiveText(R.string.dialog_button_back) .content(msg)
.build() .positiveText(R.string.dialog_button_back)
.also { .build()
it.contentView?.apply { .also {
autoLinkMask = Linkify.WEB_URLS it.contentView?.apply {
text = text autoLinkMask = Linkify.WEB_URLS
text = text
}
mHandler.post { it.show() }
} }
mHandler.post { it.show() } }
}
} else { } else {
val toastMsg = """ val toastMsg = """
${activity.getString(R.string.text_min_version_of_vscode_vsc_ext)}: ${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 sRequiredBytesCommands[md5] = obj
} }
} }
else -> service.responseHandler.handle(obj) else -> service.responseHandler.handle(obj)
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -215,7 +220,6 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
companion object { companion object {
private const val REQUIRED_SERVER_VERSION = "1.0.5"
private val TAG = JsonSocketClient::class.java.simpleName private val TAG = JsonSocketClient::class.java.simpleName
var serverAddressHistories: LinkedHashSet<String> var serverAddressHistories: LinkedHashSet<String>

View File

@@ -10,7 +10,6 @@ import androidx.annotation.NonNull;
import org.autojs.autojs.core.eventloop.EventEmitter; import org.autojs.autojs.core.eventloop.EventEmitter;
import org.autojs.autojs.core.looper.Loopers; import org.autojs.autojs.core.looper.Loopers;
import org.autojs.autojs.pref.Language;
import org.autojs.autojs.runtime.ScriptBridges; import org.autojs.autojs.runtime.ScriptBridges;
import org.autojs.autojs.runtime.ScriptRuntime; import org.autojs.autojs.runtime.ScriptRuntime;
import org.autojs.autojs.tool.MapBuilder; import org.autojs.autojs.tool.MapBuilder;
@@ -23,7 +22,7 @@ import java.util.Set;
/** /**
* Created by Stardust on 2018/2/5. * 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 { 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 ScriptBridges mScriptBridges;
private final SensorEventEmitter mNoOpSensorEventEmitter; private final SensorEventEmitter mNoOpSensorEventEmitter;
private final ScriptRuntime mScriptRuntime; 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) { public Sensors(Context context, ScriptRuntime runtime) {
super(runtime.bridges); super(runtime.bridges);
@@ -90,7 +96,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
mScriptBridges = runtime.bridges; mScriptBridges = runtime.bridges;
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges); mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
mScriptRuntime = runtime; mScriptRuntime = runtime;
runtime.loopers.addLooperQuitHandler(this); runtime.loopers.addAsyncTask(mAsyncTask);
} }
public SensorEventEmitter register(String sensorName) { public SensorEventEmitter register(String sensorName) {
@@ -122,13 +128,8 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
return emitter; return emitter;
} }
@Override
public boolean shouldQuit() {
return mSensorEventEmitters.isEmpty();
}
public Sensor getSensor(String sensorName) { public Sensor getSensor(String sensorName) {
sensorName = sensorName.toUpperCase(Language.getPrefLanguage().getLocale()); sensorName = sensorName.toUpperCase();
Integer type = SENSORS.get(sensorName); Integer type = SENSORS.get(sensorName);
type = type == null ? getSensorTypeByReflect(sensorName) : type; type = type == null ? getSensorTypeByReflect(sensorName) : type;
return type == null ? null : mSensorManager.getDefaultSensor(type); return type == null ? null : mSensorManager.getDefaultSensor(type);
@@ -159,6 +160,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
} }
mSensorEventEmitters.clear(); mSensorEventEmitters.clear();
} }
mScriptRuntime.loopers.removeLooperQuitHandler(this); mScriptRuntime.loopers.removeAsyncTask(mAsyncTask);
} }
} }

View File

@@ -1,11 +1,15 @@
package org.autojs.autojs.runtime.api package org.autojs.autojs.runtime.api
import org.autojs.autojs.concurrent.VolatileDispose 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.MainThreadProxy
import org.autojs.autojs.core.looper.TimerThread import org.autojs.autojs.core.looper.TimerThread
import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.util.StringUtils.str import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.autojs.autojs6.R 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.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
@@ -13,58 +17,91 @@ import java.util.concurrent.locks.ReentrantLock
* Created by Stardust on 2017/12/3. * Created by Stardust on 2017/12/3.
*/ */
class Threads(private val mRuntime: ScriptRuntime) { class Threads(private val mRuntime: ScriptRuntime) {
private val mThreads = HashSet<Thread>() private val mThreads = HashSet<Thread>()
val mainThread: Thread = Thread.currentThread()
private val mMainThreadProxy = MainThreadProxy(Thread.currentThread(), mRuntime) private val mMainThreadProxy = MainThreadProxy(Thread.currentThread(), mRuntime)
private var mSpawnCount = 0 private var mSpawnCount = 0
private var mTaskCount = AtomicLong(0)
private var mExit = false 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 -> fun runTaskForThreadPool(runnable: BaseFunction) {
if (thread === mainThread) mMainThreadProxy else thread 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 { fun start(runnable: Runnable): TimerThread {
val thread = createThread(runnable) val thread = createThread(runnable)
synchronized(mThreads) { synchronized(mThreads) {
check(!mExit) { str(R.string.error_script_is_on_exiting) } check(!mExit) { "script exiting" }
thread.let { mThreads.add(thread)
mThreads.add(it) thread.name = mainThread.name + " (Spawn-" + mSpawnCount + ")"
it.name = "${mainThread.name} (Spawn-$mSpawnCount)" mSpawnCount++
mSpawnCount++ thread.start()
it.start()
}
} }
return thread return thread
} }
private fun createThread(runnable: Runnable): TimerThread { private fun createThread(runnable: Runnable): TimerThread {
val millis = mRuntime.timers.maxCallbackUptimeMillisForAllThreads return object : TimerThread(mRuntime, runnable) {
return object : TimerThread(mRuntime, millis, runnable) {
override fun onExit() { override fun onExit() {
synchronized(mThreads) { mThreads.remove(currentThread()) } synchronized(mThreads) { mThreads.remove(currentThread()) }
super.onExit() 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 atomic() = AtomicLong()
fun lock() = ReentrantLock() fun lock() = ReentrantLock()
fun shutDownAll() { fun shutDownAll() {
threadPool.shutdownNow()
synchronized(mThreads) { synchronized(mThreads) {
mThreads.apply { for (thread in mThreads) {
forEach { it.interrupt() } thread.interrupt()
clear()
} }
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() }
}
} }

View File

@@ -1,9 +1,6 @@
package org.autojs.autojs.runtime.api; package org.autojs.autojs.runtime.api;
import android.os.Looper; 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.Timer;
import org.autojs.autojs.core.looper.TimerThread; import org.autojs.autojs.core.looper.TimerThread;
import org.autojs.autojs.runtime.ScriptRuntime; import org.autojs.autojs.runtime.ScriptRuntime;
@@ -11,28 +8,24 @@ import org.autojs.autojs.runtime.ScriptRuntime;
/** /**
* Created by Stardust on 2017/7/21. * Created by Stardust on 2017/7/21.
*/ */
public class Timers { public class Timers {
private static final String LOG_TAG = "Timers"; private static final String LOG_TAG = "Timers";
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); //private VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
private final Threads mThreads; private Threads mThreads;
private final Timer mMainTimer; private Timer mUiTimer;
private final Timer mUiTimer; private ScriptRuntime mRuntime;
public Timers(ScriptRuntime runtime) { public Timers(ScriptRuntime runtime) {
mMainTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads); mUiTimer = new Timer(runtime, Looper.getMainLooper());
mUiTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads, Looper.getMainLooper());
mThreads = runtime.threads; mThreads = runtime.threads;
mRuntime = runtime;
} }
public Timer getMainTimer() { public Timer getMainTimer() {
return mMainTimer; return mRuntime.loopers.getMTimer();
}
VolatileBox<Long> getMaxCallbackUptimeMillisForAllThreads() {
return mMaxCallbackUptimeMillisForAllThreads;
} }
public Timer getTimerForCurrentThread() { public Timer getTimerForCurrentThread() {
@@ -41,19 +34,27 @@ public class Timers {
public Timer getTimerForThread(Thread thread) { public Timer getTimerForThread(Thread thread) {
if (thread == mThreads.getMainThread()) { if (thread == mThreads.getMainThread()) {
return mMainTimer; return mRuntime.loopers.getMTimer();
} }
Timer timer = TimerThread.getTimerForThread(thread); Timer timer = TimerThread.getTimerForThread(thread);
if (timer == null && Looper.myLooper() == Looper.getMainLooper()) { if (timer == null && Looper.myLooper() == Looper.getMainLooper()) {
return mUiTimer; return mUiTimer;
} }
return timer; if (timer == null) {
return mRuntime.loopers.getMTimer();
} else {
return timer;
}
} }
public int setTimeout(Object callback, long delay, Object... args) { public int setTimeout(Object callback, long delay, Object... args) {
return getTimerForCurrentThread().setTimeout(callback, delay, args); return getTimerForCurrentThread().setTimeout(callback, delay, args);
} }
public int setTimeout(Object callback) {
return setTimeout(callback, 1);
}
public boolean clearTimeout(int id) { public boolean clearTimeout(int id) {
return getTimerForCurrentThread().clearTimeout(id); return getTimerForCurrentThread().clearTimeout(id);
} }
@@ -62,6 +63,10 @@ public class Timers {
return getTimerForCurrentThread().setInterval(listener, interval, args); return getTimerForCurrentThread().setInterval(listener, interval, args);
} }
public int setInterval(Object listener) {
return setInterval(listener, 1);
}
public boolean clearInterval(int id) { public boolean clearInterval(int id) {
return getTimerForCurrentThread().clearInterval(id); return getTimerForCurrentThread().clearInterval(id);
} }
@@ -74,17 +79,8 @@ public class Timers {
return getTimerForCurrentThread().clearImmediate(id); return getTimerForCurrentThread().clearImmediate(id);
} }
public boolean hasPendingCallbacks() {
// 如果是脚本主线程则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。
if (mThreads.getMainThread() == Thread.currentThread()) {
return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis();
}
// 否则检查当前线程的定时回调
return getTimerForCurrentThread().hasPendingCallbacks();
}
public void recycle() { public void recycle() {
mMainTimer.removeAllCallbacks(); mRuntime.loopers.getMTimer().removeAllCallbacks();
} }
} }

View File

@@ -54,13 +54,11 @@ import org.autojs.autojs.ui.pager.ViewPager
import org.autojs.autojs.ui.settings.PreferencesActivity import org.autojs.autojs.ui.settings.PreferencesActivity
import org.autojs.autojs.ui.widget.DrawerAutoClose import org.autojs.autojs.ui.widget.DrawerAutoClose
import org.autojs.autojs.ui.widget.SearchViewItem import org.autojs.autojs.ui.widget.SearchViewItem
import org.autojs.autojs.util.DeveloperUtils
import org.autojs.autojs.util.ForegroundServiceUtils import org.autojs.autojs.util.ForegroundServiceUtils
import org.autojs.autojs.util.StringUtils import org.autojs.autojs.util.StringUtils
import org.autojs.autojs.util.UpdateUtils.autoCheckForUpdatesIfNeededWithSnackbar import org.autojs.autojs.util.UpdateUtils.autoCheckForUpdatesIfNeededWithSnackbar
import org.autojs.autojs.util.ViewUtils import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs.util.WorkingDirectoryUtils import org.autojs.autojs.util.WorkingDirectoryUtils
import org.autojs.autojs6.BuildConfig
import org.autojs.autojs6.R import org.autojs.autojs6.R
import org.autojs.autojs6.databinding.ActivityMainBinding import org.autojs.autojs6.databinding.ActivityMainBinding
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@@ -246,17 +244,6 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
return if (i < 0) 2 else grantResults[i] 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 override fun getOnActivityResultDelegateMediator() = mActivityResultMediator
@Suppress("OVERRIDE_DEPRECATION") @Suppress("OVERRIDE_DEPRECATION")

View File

@@ -1,6 +1,6 @@
#Mon Jul 03 16:17:34 CST 2023 #Wed Jul 12 09:21:49 CST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,8 +1,10 @@
#Fri Jul 07 01:39:02 CST 2023 #Wed Jul 12 14:08:52 CST 2023
BUILD_TIME=1688665142214 BUILD_TIME=1689142132084
COMPILE_SDK_VERSION=33 COMPILE_SDK_VERSION=33
JAVA_VERSION=20 JAVA_VERSION=20
MIN_SDK_VERSION=24 MIN_SDK_VERSION=24
TARGET_SDK_VERSION=33 TARGET_SDK_VERSION=33
#2006
VERSION_BUILD=1999 VERSION_BUILD=1999
VERSION_NAME=6.3.2 VERSION_NAME=6.3.3
VSCODE_EXT_REQUIRED_VERSION=1.0.8