diff --git a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AlgorithmChanger.java b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AlgorithmChanger.java index c7c5894d..4ab9ac17 100644 --- a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AlgorithmChanger.java +++ b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AlgorithmChanger.java @@ -7,22 +7,38 @@ import com.stardust.automator.search.SearchAlgorithm; import java.lang.reflect.Field; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class AlgorithmChanger { - protected final static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, 60, TimeUnit.SECONDS, new LinkedBlockingDeque() - , new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = Executors.defaultThreadFactory().newThread(r); - t.setName("parallel-pre-build-t-" + t.getName()); - return t; + protected static volatile AutoCloseThreadPool threadPoolExecutor = null; + + protected static AutoCloseThreadPool getExecutor() { + if (threadPoolExecutor == null) { + synchronized (AlgorithmChanger.class) { + if (threadPoolExecutor == null) { + threadPoolExecutor = new AutoCloseThreadPool( + 4, 4, 60, TimeUnit.SECONDS, + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setName("parallel-pre-build-t-" + t.getName()); + return t; + } + }, new Runnable() { + @Override + public void run() { + Log.d(TAG, "线程池关闭"); + threadPoolExecutor = null; + } + }); + } + } } - }); + return threadPoolExecutor; + } private static final String TAG = "AlgorithmChanger"; public static boolean enableLogging = false; diff --git a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AutoCloseThreadPool.java b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AutoCloseThreadPool.java new file mode 100644 index 00000000..a998d655 --- /dev/null +++ b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/AutoCloseThreadPool.java @@ -0,0 +1,81 @@ +package com.tony.autojs.search; + +import java.util.concurrent.*; + +/** + * 自动关闭的线程池,当无任务提交的idleTimeout(timeUnit)之后,检查当前线程池是否空闲,如果是则关闭线程池 + * @author TonyJiangWJ + * @since 2025/1/24 + */ +public class AutoCloseThreadPool { + private final ThreadPoolExecutor threadPool; + private final ScheduledExecutorService monitor; + private final long idleTimeout; + private final TimeUnit timeUnit; + private ScheduledFuture shutdownTask; + private final Runnable callback; + + public AutoCloseThreadPool(int corePoolSize, int maxPoolSize, long idleTimeout, TimeUnit timeUnit, + ThreadFactory threadFactory, + Runnable callback) { + this.threadPool = new ThreadPoolExecutor( + corePoolSize, + maxPoolSize, + 60L, TimeUnit.SECONDS, // 非核心线程空闲存活时间 + new LinkedBlockingQueue(), + threadFactory + ); + this.monitor = Executors.newSingleThreadScheduledExecutor(); + this.idleTimeout = idleTimeout; + this.timeUnit = timeUnit; + this.callback = callback; + } + + public void execute(Runnable task) { + synchronized (this) { + // 提交任务前检查线程池是否已关闭 + if (threadPool.isShutdown()) { + throw new RejectedExecutionException("ThreadPool已关闭,无法接受新任务"); + } + // 取消之前的关闭任务 + if (shutdownTask != null && !shutdownTask.isDone()) { + shutdownTask.cancel(false); + } + // 提交任务 + threadPool.execute(task); + // 调度新的关闭检查任务 + scheduleShutdownCheck(); + } + } + + private void scheduleShutdownCheck() { + shutdownTask = monitor.schedule(new Runnable() { + @Override + public void run() { + synchronized (AutoCloseThreadPool.this) { + // 检查活动线程数和任务队列 + if (threadPool.getActiveCount() == 0 && threadPool.getQueue().isEmpty()) { + // 关闭线程池和监控 + threadPool.shutdown(); + monitor.shutdown(); + // 执行线程池关闭回调 + if (callback != null) { + callback.run(); + } + } + } + } + }, idleTimeout, timeUnit); + } + + public void shutdownNow() { + synchronized (this) { + threadPool.shutdownNow(); + monitor.shutdownNow(); + } + } + + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return threadPool.awaitTermination(timeout, unit); + } +} diff --git a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/ParallelPreBuildTreeSearch.java b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/ParallelPreBuildTreeSearch.java index f430e5f8..be1e1402 100644 --- a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/ParallelPreBuildTreeSearch.java +++ b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/ParallelPreBuildTreeSearch.java @@ -54,7 +54,7 @@ public abstract class ParallelPreBuildTreeSearch implements SearchAlgorithm { final CountDownLatch countDownLatch = new CountDownLatch(parent.getRoot().childCount()); for (int i = 0; i < parent.getRoot().childCount(); i++) { final int finalI = i; - AlgorithmChanger.threadPoolExecutor.execute(new Runnable() { + AlgorithmChanger.getExecutor().execute(new Runnable() { @Override public void run() { UiObject child = parent.getRoot().child(finalI); @@ -90,7 +90,7 @@ public abstract class ParallelPreBuildTreeSearch implements SearchAlgorithm { final CountDownLatch countDownLatch = new CountDownLatch(parent.getRoot().childCount()); for (int i = 0; i < parent.getRoot().childCount(); i++) { final int finalI = i; - AlgorithmChanger.threadPoolExecutor.execute(new Runnable() { + AlgorithmChanger.getExecutor().execute(new Runnable() { @Override public void run() { UiObject child = parent.getRoot().child(finalI); diff --git a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/UiObjectTreeBuilder.java b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/UiObjectTreeBuilder.java index a499a1b6..b6f282a9 100644 --- a/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/UiObjectTreeBuilder.java +++ b/js-supports/autojs-tool-common/src/main/java/com/tony/autojs/search/UiObjectTreeBuilder.java @@ -8,7 +8,6 @@ import com.stardust.autojs.core.accessibility.AccessibilityBridge; import com.stardust.automator.UiObject; import com.stardust.automator.filter.Filter; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,12 +19,15 @@ public class UiObjectTreeBuilder extends ParallelPreBuildTreeSearch { private AccessibilityBridge mAccessibilityBridge; + public UiObjectTreeBuilder() { + + } + public UiObjectTreeBuilder(AccessibilityBridge accessibilityBridge) { this.mAccessibilityBridge = accessibilityBridge; } - public List buildTreeNode() { - List roots = mAccessibilityBridge.windowRoots(); + public List buildTreeNode(List roots) { if (BuildConfig.DEBUG) Log.d(TAG, "find: roots = " + roots); if (roots.isEmpty()) { @@ -45,8 +47,15 @@ public class UiObjectTreeBuilder extends ParallelPreBuildTreeSearch { return result; } - public List buildVisibleTreeNode() { + public List buildTreeNode() { + if (mAccessibilityBridge == null) { + return Collections.emptyList(); + } List roots = mAccessibilityBridge.windowRoots(); + return buildTreeNode(roots); + } + + public List buildVisibleTreeNode(List roots) { if (BuildConfig.DEBUG) Log.d(TAG, "find: roots = " + roots); if (roots.isEmpty()) { @@ -66,6 +75,14 @@ public class UiObjectTreeBuilder extends ParallelPreBuildTreeSearch { return result; } + public List buildVisibleTreeNode() { + if (mAccessibilityBridge == null) { + return Collections.emptyList(); + } + List roots = mAccessibilityBridge.windowRoots(); + return buildVisibleTreeNode(roots); + } + @NonNull @Override public ArrayList search(@NonNull UiObject root, @NonNull Filter filter, int limit) {