From ac2b242be09951b75ecf0fc493880ff472ef18a9 Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Sun, 28 Sep 2025 22:59:10 +0800 Subject: [PATCH] =?UTF-8?q?6.7.0=20-=20Alpha6=20-=20=E4=BD=BF=E7=94=A8=20L?= =?UTF-8?q?iveData=20=E5=8F=8A=20SharedFlow=20=E6=9B=BF=E4=BB=A3=E5=B7=B2?= =?UTF-8?q?=E5=BC=83=E7=94=A8=E7=9A=84=20LocalBroadcastManager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changelog/lang_zh-Hans.json | 4 +- app/build.gradle.kts | 9 +- app/src/main/java/org/autojs/autojs/App.kt | 52 ++-- app/src/main/java/org/autojs/autojs/AutoJs.kt | 78 ++++-- .../receiver/BaseBroadcastReceiver.java | 2 +- .../receiver/DynamicBroadcastReceivers.kt | 245 ++++++++++-------- .../org/autojs/autojs/ipc/InAppEventBus.kt | 36 +++ .../autojs/ipc/LayoutInspectEventBus.kt | 25 ++ .../autojs/runtime/api/augment/app/App.kt | 64 ++++- .../org/autojs/autojs/ui/main/MainActivity.kt | 4 + .../ui/timing/TimedTaskSettingActivity.kt | 4 +- gradle/libs.versions.toml | 8 +- version.properties | 6 +- 13 files changed, 357 insertions(+), 180 deletions(-) create mode 100644 app/src/main/java/org/autojs/autojs/ipc/InAppEventBus.kt create mode 100644 app/src/main/java/org/autojs/autojs/ipc/LayoutInspectEventBus.kt diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index cf7956af..57bb5a53 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -37,6 +37,7 @@ "文件管理器浮动按钮展开后点击菜单项时优化菜单收起时机", "崩溃报告页面支持双指缩放调整字体大小并添加常用功能按钮", "应用启动器图标支持自适应图标特性 _[`issue #405`](http://issues.autojs6.com/405)_", + "使用 LiveData 及 SharedFlow 替代已弃用的 LocalBroadcastManager", "Gradle 构建脚本提升 7z 格式文件的解压效率", "使用版本目录 (Version Catalogs) 集中管理 Gradle 依赖和插件版本", "模块化 Gradle 脚本, 将共享构建逻辑迁移至 buildSrc 并抽象为约定插件", @@ -62,7 +63,8 @@ "升级 Androidx AppCompat 版本 1.7.0 -> 1.7.1", "升级 Material Progressbar 版本 1.4.2 -> 1.6.1", "升级 Androidx Room 版本 2.7.1 -> 2.7.2", - "升级 OpenCC 版本 1.2.0 -> 1.2.2" + "升级 OpenCC 版本 1.2.0 -> 1.2.2", + "升级 Desugar JDK Libs 版本 2.0.4 -> 2.1.5" ] }, "v6.6.4": { diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e30951db..eb7823d4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,9 +49,6 @@ dependencies /* Unclassified */ { // Androidx Core implementation(libs.core.ktx) - // Local Broadcast Manager - implementation(libs.localbroadcastmanager) - // LeakCanary debugImplementation(libs.leakcanary) @@ -427,6 +424,12 @@ dependencies /* Archived */ { // ! It was ever imported and used for MediaType constants. // ! zh-CN: 曾用于 MediaType 常量的导入及使用. // implementation("javax.ws.rs:javax.ws.rs-api:2.1.1") + + // Local Broadcast Manager + // @Comment by SuperMonster003 on Sep 27, 2025. + // ! Deprecated. Chould by replaced with LiveData or SharedFlow. + // ! zh-CN: 已弃用. 可用 LiveData 或 SharedFlow 替代. + // implementation(libs.localbroadcastmanager) } dependencies /* Reserved for auto append by IDE */ { diff --git a/app/src/main/java/org/autojs/autojs/App.kt b/app/src/main/java/org/autojs/autojs/App.kt index a2a58858..ae4881d1 100644 --- a/app/src/main/java/org/autojs/autojs/App.kt +++ b/app/src/main/java/org/autojs/autojs/App.kt @@ -2,7 +2,6 @@ package org.autojs.autojs import android.annotation.SuppressLint import android.content.Context -import android.content.Intent import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.drawable.Drawable @@ -11,7 +10,6 @@ import android.os.Build import android.util.Log import android.view.View import android.widget.ImageView -import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.multidex.MultiDexApplication import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomViewTarget @@ -21,6 +19,7 @@ import com.google.mlkit.common.sdkinternal.MlKitContext import com.hjq.toast.Toaster import com.tencent.bugly.Bugly import com.tencent.bugly.crashreport.CrashReport +import io.reactivex.android.schedulers.AndroidSchedulers import org.autojs.autojs.apkbuilder.ApkBuilder import org.autojs.autojs.app.GlobalAppContext import org.autojs.autojs.core.pref.Pref @@ -28,6 +27,7 @@ import org.autojs.autojs.core.ui.inflater.ImageLoader import org.autojs.autojs.core.ui.inflater.util.Drawables import org.autojs.autojs.event.GlobalKeyObserver import org.autojs.autojs.external.receiver.DynamicBroadcastReceivers +import org.autojs.autojs.ipc.InAppEventBus import org.autojs.autojs.leakcanary.LeakCanarySetup import org.autojs.autojs.pluginclient.DevPluginService import org.autojs.autojs.runtime.api.augment.ocr.Ocr @@ -86,7 +86,7 @@ class App : MultiDexApplication() { if (!BuildConfig.DEBUG) { @Suppress("SpellCheckingInspection") FlurryAgent.Builder() - .withLogEnabled(BuildConfig.DEBUG) + .withLogEnabled(false) .build(this, "D42MH48ZN4PJC5TKNYZD") } } @@ -94,9 +94,9 @@ class App : MultiDexApplication() { private fun setUpDebugEnvironment() { Bugly.isDev = false val crashHandler = CrashHandler(CrashReportActivity::class.java) - - val strategy = CrashReport.UserStrategy(applicationContext) - strategy.setCrashHandleCallback(crashHandler) + val strategy = CrashReport.UserStrategy(applicationContext).apply { + crashHandleCallback = crashHandler + } CrashReport.initCrashReport(applicationContext, BUGLY_APP_ID, false, strategy) @@ -128,27 +128,44 @@ class App : MultiDexApplication() { private fun initDynamicBroadcastReceivers() { dynamicBroadcastReceivers = DynamicBroadcastReceivers(this) val localActions = ArrayList() - val actions = ArrayList() + val systemActions = ArrayList() TimedTaskManager.allIntentTasks .filter { task -> task.action != null } .doOnComplete { if (localActions.isNotEmpty()) { dynamicBroadcastReceivers.register(localActions, true) } - if (actions.isNotEmpty()) { - dynamicBroadcastReceivers.register(actions, false) + if (systemActions.isNotEmpty()) { + dynamicBroadcastReceivers.register(systemActions, false) + } + + // @Archived by SuperMonster003 on Sep 27, 2025. + // ! LocalBroadcastManager is deprecated. + // ! zh-CN: LocalBroadcastManager 已被弃用. + // # Intent(DynamicBroadcastReceivers.ACTION_STARTUP).let { + // # LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(it) + // # } + + // @Hint by JetBrains AI Assistant on Sep 27, 2025. + // ! Why schedule on next main-loop tick? + // ! Post the emit to the next frame on the main thread to avoid a race where + // ! the SharedFlow collectors (registered via launchIn on Main) are not yet active + // ! at the exact moment of emission. Emitting in the next loop ensures all + // ! subscriptions are set up, preventing event loss when replay=0. + // ! zh-CN: + // ! 为什么要在主线程的下一帧再触发? + // ! 将触发放到主线程消息队列的下一轮执行, 避免 "先发后订" 的竞态: + // ! 订阅 (launchIn(Main)) 尚未真正激活时如果立即发送, replay=0 会丢事件. + // ! 下一帧再发可以确保收集器已就绪, 从而可靠接收启动事件. + AndroidSchedulers.mainThread().scheduleDirect { + InAppEventBus.tryEmit(DynamicBroadcastReceivers.ACTION_STARTUP) } - LocalBroadcastManager.getInstance(applicationContext).sendBroadcast( - Intent( - DynamicBroadcastReceivers.ACTION_STARTUP - ) - ) } .subscribe({ if (it.isLocal) { localActions.add(it.action) } else { - actions.add(it.action) + systemActions.add(it.action) } }, { it.printStackTrace() }) @@ -235,6 +252,11 @@ class App : MultiDexApplication() { super.onConfigurationChanged(newConfig) } + fun clear() { + dynamicBroadcastReceivers.unregisterAll() + InAppEventBus.clear() + } + companion object { private const val BUGLY_APP_ID = "19b3607b53" diff --git a/app/src/main/java/org/autojs/autojs/AutoJs.kt b/app/src/main/java/org/autojs/autojs/AutoJs.kt index be83f005..8c9844cc 100644 --- a/app/src/main/java/org/autojs/autojs/AutoJs.kt +++ b/app/src/main/java/org/autojs/autojs/AutoJs.kt @@ -2,18 +2,21 @@ package org.autojs.autojs import android.annotation.SuppressLint import android.app.Application -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.util.Log -import androidx.localbroadcastmanager.content.LocalBroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.autojs.autojs.core.accessibility.AccessibilityTool import org.autojs.autojs.core.accessibility.Capture import org.autojs.autojs.core.accessibility.LayoutInspector.CaptureAvailableListener import org.autojs.autojs.core.console.GlobalConsole import org.autojs.autojs.execution.ScriptExecutionGlobalListener import org.autojs.autojs.external.fileprovider.AppFileProvider +import org.autojs.autojs.ipc.LayoutInspectEvent +import org.autojs.autojs.ipc.LayoutInspectEventBus import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.api.AppUtils import org.autojs.autojs.runtime.api.AppUtils.Companion.ActivityShortForm @@ -39,37 +42,52 @@ open class AutoJs(appContext: Application) : AbstractAutoJs(appContext) { private val mA11yTool = AccessibilityTool(appContext) + private val mJob = Job() + private val mScope = CoroutineScope(Dispatchers.Main.immediate + mJob) + init { scriptEngineService.registerGlobalScriptExecutionListener(ScriptExecutionGlobalListener()) - LocalBroadcastManager.getInstance(appContext).registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - try { - val action = intent.action ?: return - when { - action.equals(LayoutBoundsFloatyWindow::class.java.name, true) -> { - mA11yTool.ensureService() - capture(object : LayoutInspectFloatyWindow { - override fun create(capture: Capture) = LayoutBoundsFloatyWindow(capture, context, true) - }) - } - action.equals(LayoutHierarchyFloatyWindow::class.java.name, true) -> { - mA11yTool.ensureService() - capture(object : LayoutInspectFloatyWindow { - override fun create(capture: Capture) = LayoutHierarchyFloatyWindow(capture, context, true) - }) - } + // @Archived by SuperMonster003 on Sep 27, 2025. + // ! LocalBroadcastManager is deprecated. + // ! zh-CN: LocalBroadcastManager 已被弃用. + // # LocalBroadcastManager.getInstance(appContext).registerReceiver(object : BroadcastReceiver() { + // # override fun onReceive(context: Context, intent: Intent) { + // # ... ... + // # val action = intent.action ?: return + // # when { + // # action.equals(LayoutBoundsFloatyWindow::class.java.name, true) -> { ... ... } + // # action.equals(LayoutHierarchyFloatyWindow::class.java.name, true) -> { ... ... } + // # } + // # ... ... + // # } + // # }, IntentFilter().apply { + // # addAction(LayoutBoundsFloatyWindow::class.java.name) + // # addAction(LayoutHierarchyFloatyWindow::class.java.name) + // # }) + + LayoutInspectEventBus.events.onEach { event -> + try { + when (event) { + LayoutInspectEvent.ShowLayoutBounds -> { + mA11yTool.ensureService() + capture(object : LayoutInspectFloatyWindow { + override fun create(capture: Capture) = LayoutBoundsFloatyWindow(capture, appContext, true) + }) } - } catch (e: Exception) { - if (isBackgroundThread()) { - throw e + LayoutInspectEvent.ShowLayoutHierarchy -> { + mA11yTool.ensureService() + capture(object : LayoutInspectFloatyWindow { + override fun create(capture: Capture) = LayoutHierarchyFloatyWindow(capture, appContext, true) + }) } } + } catch (e: Exception) { + if (isBackgroundThread()) { + throw e + } } - }, IntentFilter().apply { - addAction(LayoutBoundsFloatyWindow::class.java.name) - addAction(LayoutHierarchyFloatyWindow::class.java.name) - }) + }.launchIn(mScope) } private interface LayoutInspectFloatyWindow { @@ -131,6 +149,10 @@ open class AutoJs(appContext: Application) : AbstractAutoJs(appContext) { ).forEach { putProperty(it.fullName, it.className) } } + fun clear() { + mJob.cancel() + } + companion object { private var isInitialized = false private val TAG = AutoJs::class.java.simpleName diff --git a/app/src/main/java/org/autojs/autojs/external/receiver/BaseBroadcastReceiver.java b/app/src/main/java/org/autojs/autojs/external/receiver/BaseBroadcastReceiver.java index 3c1f1356..9ad89cde 100644 --- a/app/src/main/java/org/autojs/autojs/external/receiver/BaseBroadcastReceiver.java +++ b/app/src/main/java/org/autojs/autojs/external/receiver/BaseBroadcastReceiver.java @@ -36,7 +36,7 @@ public class BaseBroadcastReceiver extends BroadcastReceiver { } } - static void runTask(Context context, Intent intent, IntentTask task) { + public static void runTask(Context context, Intent intent, IntentTask task) { Log.d(LOG_TAG, "runTask: action = " + intent.getAction() + ", script = " + task.getScriptPath()); ScriptFile file = new ScriptFile(task.getScriptPath()); ExecutionConfig config = new ExecutionConfig(); diff --git a/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.kt b/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.kt index 2c4c9172..2d1949b4 100644 --- a/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.kt +++ b/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.kt @@ -1,151 +1,182 @@ -package org.autojs.autojs.external.receiver; +package org.autojs.autojs.external.receiver -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.IntentFilter; -import android.os.Build; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.util.Log; +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.util.Log +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.autojs.autojs.ipc.InAppEventBus +import org.autojs.autojs.timing.IntentTask +import org.autojs.autojs.timing.TimedTaskManager -import org.autojs.autojs.timing.IntentTask; +class DynamicBroadcastReceivers(private val context: Context) { -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; + private val mActions: MutableSet = LinkedHashSet() + private val mReceiverRegistries: MutableList = ArrayList() + private val mDefaultActionReceiver = BaseBroadcastReceiver() + private val mPackageActionReceiver = BaseBroadcastReceiver() -public class DynamicBroadcastReceivers { - - public static final String ACTION_STARTUP = "org.autojs.autojs.action.startup"; - - private static final String LOG_TAG = "DynBroadcastReceivers"; - - private final Set mActions = new LinkedHashSet<>(); - private final List mReceiverRegistries = new ArrayList<>(); - private final BaseBroadcastReceiver mDefaultActionReceiver = new BaseBroadcastReceiver(); - private final BaseBroadcastReceiver mPackageActionReceiver = new BaseBroadcastReceiver(); - private final Context mContext; - - @SuppressLint("UnspecifiedRegisterReceiverFlag") - public DynamicBroadcastReceivers(Context context) { - mContext = context; + init { + @SuppressLint("UnspecifiedRegisterReceiverFlag") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS), Context.RECEIVER_EXPORTED); + context.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS), Context.RECEIVER_EXPORTED) } else { - mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS)); + context.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS)) } - IntentFilter filter = createIntentFilter(StaticBroadcastReceiver.PACKAGE_ACTIONS); - filter.addDataScheme("package"); + val filter: IntentFilter = createIntentFilter(StaticBroadcastReceiver.PACKAGE_ACTIONS) + filter.addDataScheme("package") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - mContext.registerReceiver(mPackageActionReceiver, filter, Context.RECEIVER_EXPORTED); + context.registerReceiver(mPackageActionReceiver, filter, Context.RECEIVER_EXPORTED) } else { - mContext.registerReceiver(mPackageActionReceiver, filter); + context.registerReceiver(mPackageActionReceiver, filter) } } } - public void registerIntent(IntentTask task) { - register(Collections.singletonList(task.getAction()), task.isLocal()); + fun registerIntent(task: IntentTask) { + register(mutableListOf(task.action), task.isLocal) } - public synchronized void register(List actions, boolean local) { - LinkedHashSet newActions = new LinkedHashSet<>(); - for (String action : actions) { - if (!StaticBroadcastReceiver.ACTIONS.contains(action) - && !StaticBroadcastReceiver.PACKAGE_ACTIONS.contains(action) - && !mActions.contains(action)) { - newActions.add(action); + @Synchronized + fun register(actions: MutableList, local: Boolean) { + val newActions = LinkedHashSet() + for (action in actions) { + if (!StaticBroadcastReceiver.ACTIONS.contains(action) && !StaticBroadcastReceiver.PACKAGE_ACTIONS.contains(action) && !mActions.contains(action) + ) { + newActions.add(action) } } if (newActions.isEmpty()) { - return; + return } - ReceiverRegistry receiverRegistry = new ReceiverRegistry(newActions, local); - receiverRegistry.register(); - mReceiverRegistries.add(receiverRegistry); + val receiverRegistry = ReceiverRegistry(newActions, local) + receiverRegistry.register() + mReceiverRegistries.add(receiverRegistry) + mActions.addAll(newActions) } - public synchronized void unregister(String action) { + @Synchronized + fun unregister(action: String?) { if (!mActions.contains(action)) { - return; + return } - mActions.remove(action); - Iterator iterator = mReceiverRegistries.iterator(); + mActions.remove(action) + val iterator = mReceiverRegistries.iterator() while (iterator.hasNext()) { - ReceiverRegistry receiverRegistry = iterator.next(); + val receiverRegistry = iterator.next() if (!receiverRegistry.actions.contains(action)) { - continue; + continue } - receiverRegistry.actions.remove(action); - receiverRegistry.unregister(); + receiverRegistry.actions.remove(action) + receiverRegistry.unregister() if (!receiverRegistry.register()) { - iterator.remove(); + iterator.remove() } - break; + break } } - public synchronized void unregisterAll() { - for (ReceiverRegistry registry : mReceiverRegistries) { - registry.unregister(); + @Synchronized + fun unregisterAll() { + for (registry in mReceiverRegistries) { + registry.unregister() } - mReceiverRegistries.clear(); + mReceiverRegistries.clear() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mContext.unregisterReceiver(mDefaultActionReceiver); - mContext.unregisterReceiver(mPackageActionReceiver); + context.unregisterReceiver(mDefaultActionReceiver) + context.unregisterReceiver(mPackageActionReceiver) } } - static IntentFilter createIntentFilter(Collection actions) { - IntentFilter filter = new IntentFilter(); - for (String action : actions) { - filter.addAction(action); - } - return filter; - } + private inner class ReceiverRegistry(var actions: LinkedHashSet, var local: Boolean) { - private class ReceiverRegistry { - BroadcastReceiver receiver; - LinkedHashSet actions; - boolean local; - - ReceiverRegistry(LinkedHashSet actions, boolean local) { - this.actions = actions; - this.local = local; - receiver = new BaseBroadcastReceiver(); - } - - void unregister() { - if (local) { - LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(mContext); - broadcastManager.unregisterReceiver(receiver); - } else { - mContext.unregisterReceiver(receiver); - } - } + var receiver: BroadcastReceiver = BaseBroadcastReceiver() + var jobs: List = emptyList() @SuppressLint("UnspecifiedRegisterReceiverFlag") - boolean register() { - if (actions.isEmpty()) - return false; - IntentFilter intentFilter = createIntentFilter(actions); - if (local) { - LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(mContext); - broadcastManager.registerReceiver(receiver, intentFilter); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); - } else { - mContext.registerReceiver(receiver, intentFilter); - } + fun register(): Boolean { + if (actions.isEmpty()) { + return false + } + if (local) { + + // @Archived by SuperMonster003 on Sep 27, 2025. + // ! LocalBroadcastManager is deprecated. + // ! zh-CN: LocalBroadcastManager 已被弃用. + // # LocalBroadcastManager.getInstance(mContext).registerReceiver(receiver, intentFilter); + + if (jobs.isNotEmpty()) { + jobs.forEach { it.cancel() } + } + jobs = actions.map { action -> + InAppEventBus.streamOf(action).onEach { + try { + TimedTaskManager.getIntentTaskOfAction(action) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ intentTask: IntentTask -> + BaseBroadcastReceiver.runTask(context, Intent(action), intentTask) + }, { obj: Throwable? -> + obj?.printStackTrace() + }) + } catch (e: Exception) { + e.printStackTrace() + } + }.launchIn(appScope) + } + Log.d(LOG_TAG, "register (in-app): $actions") + } else { + val intentFilter: IntentFilter = createIntentFilter(actions) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED) + } else { + context.registerReceiver(receiver, intentFilter) + } + Log.d(LOG_TAG, "register (system): $actions") + } + return true + } + + fun unregister() { + if (local) { + + // @Archived by SuperMonster003 on Sep 27, 2025. + // ! LocalBroadcastManager is deprecated. + // ! zh-CN: LocalBroadcastManager 已被弃用. + // # LocalBroadcastManager.getInstance(mContext).unregisterReceiver(receiver); + + jobs.forEach { it.cancel() } + jobs = emptyList() + } else { + context.unregisterReceiver(receiver) } - Log.d(LOG_TAG, "register: " + actions); - return true; } } + + companion object { + + private const val LOG_TAG = "DynBroadcastReceivers" + private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + + @JvmStatic + val ACTION_STARTUP = "org.autojs.autojs.action.startup" + + private fun createIntentFilter(actions: MutableCollection): IntentFilter { + return IntentFilter().apply { + actions.forEach { addAction(it) } + } + } + + } } diff --git a/app/src/main/java/org/autojs/autojs/ipc/InAppEventBus.kt b/app/src/main/java/org/autojs/autojs/ipc/InAppEventBus.kt new file mode 100644 index 00000000..54210262 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ipc/InAppEventBus.kt @@ -0,0 +1,36 @@ +package org.autojs.autojs.ipc + +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow + +object InAppEventBus { + + // Map + private val streams = mutableMapOf>() + + @JvmStatic + @Synchronized + fun streamOf(action: String): SharedFlow { + return streams.getOrPut(action) { + MutableSharedFlow( + replay = 1, + extraBufferCapacity = 64, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + } + } + + @JvmStatic + @Synchronized + fun tryEmit(action: String): Boolean { + return streams[action]?.tryEmit(Unit) == true + } + + @JvmStatic + @Synchronized + fun clear() { + streams.clear() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/ipc/LayoutInspectEventBus.kt b/app/src/main/java/org/autojs/autojs/ipc/LayoutInspectEventBus.kt new file mode 100644 index 00000000..f0ca31c2 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ipc/LayoutInspectEventBus.kt @@ -0,0 +1,25 @@ +package org.autojs.autojs.ipc + +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow + +sealed interface LayoutInspectEvent { + object ShowLayoutBounds : LayoutInspectEvent + object ShowLayoutHierarchy : LayoutInspectEvent +} + +object LayoutInspectEventBus { + // One-time event: replay=0, provide buffering + // and discard oldest on overflow to avoid emit suspension. + // zh-CN: 单次事件: replay=0, 提供一定缓冲并在溢出时丢弃最旧, 避免 emit 挂起. + private val _events = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 64, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + val events: SharedFlow get() = _events + + suspend fun showLayoutBounds() = _events.emit(LayoutInspectEvent.ShowLayoutBounds) + suspend fun showLayoutHierarchy() = _events.emit(LayoutInspectEvent.ShowLayoutHierarchy) +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt index 951f7f26..76b77905 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/app/App.kt @@ -2,16 +2,26 @@ package org.autojs.autojs.runtime.api.augment.app import android.content.Intent import android.net.Uri +import android.os.Handler +import android.os.Looper import android.provider.Settings -import androidx.localbroadcastmanager.content.LocalBroadcastManager +import androidx.core.net.toUri +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.autojs.autojs.annotation.RhinoFunctionBody import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface import org.autojs.autojs.extension.AnyExtensions.isJsNullish import org.autojs.autojs.extension.AnyExtensions.jsBrief import org.autojs.autojs.extension.FlexibleArray import org.autojs.autojs.extension.ScriptableExtensions.defineProp +import org.autojs.autojs.extension.ScriptableExtensions.deleteProp import org.autojs.autojs.extension.ScriptableExtensions.prop import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire +import org.autojs.autojs.external.receiver.BaseBroadcastReceiver +import org.autojs.autojs.ipc.LayoutInspectEventBus import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.api.AppUtils.Companion.ActivityShortForm import org.autojs.autojs.runtime.api.AppUtils.Companion.BroadcastShortForm @@ -20,6 +30,8 @@ import org.autojs.autojs.runtime.api.augment.Augmentable import org.autojs.autojs.runtime.api.augment.shell.Shell import org.autojs.autojs.runtime.exception.ShouldNeverHappenException import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException +import org.autojs.autojs.timing.IntentTask +import org.autojs.autojs.timing.TimedTaskManager import org.autojs.autojs.util.RhinoUtils import org.autojs.autojs.util.RhinoUtils.UNDEFINED import org.autojs.autojs.util.RhinoUtils.coerceBoolean @@ -32,13 +44,10 @@ import org.autojs.autojs.util.RootUtils import org.autojs.autojs6.BuildConfig import org.mozilla.javascript.Context import org.mozilla.javascript.NativeObject -import org.mozilla.javascript.ScriptableObject.PERMANENT import org.mozilla.javascript.Undefined import java.io.File import java.net.URI import org.autojs.autojs.util.App as PresetApp -import androidx.core.net.toUri -import org.autojs.autojs.extension.ScriptableExtensions.deleteProp @Suppress("unused", "UNUSED_PARAMETER") class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { @@ -423,15 +432,14 @@ class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { fun sendBroadcast(scriptRuntime: ScriptRuntime, args: Array): Undefined = ensureArgumentsOnlyOne(args) { o -> when (o) { is String -> { - runCatching { - sendLocalBroadcastSyncInternal(intentRhinoWithRuntime(scriptRuntime, newNativeObject().also { - val value = BroadcastShortForm.valueOf(o.uppercase()).className - it.defineProp("action", value, PERMANENT) - })) - }.getOrElse { - when (it) { - is WrappedIllegalArgumentException -> throw WrappedIllegalArgumentException("Broadcast short form $o cannot be found") - else -> throw it + CoroutineScope(Dispatchers.Main).launch { + when (BroadcastShortForm.valueOf(o.uppercase())) { + BroadcastShortForm.INSPECT_LAYOUT_BOUNDS -> LayoutInspectEventBus.showLayoutBounds() + BroadcastShortForm.LAYOUT_BOUNDS -> LayoutInspectEventBus.showLayoutBounds() + BroadcastShortForm.BOUNDS -> LayoutInspectEventBus.showLayoutBounds() + BroadcastShortForm.INSPECT_LAYOUT_HIERARCHY -> LayoutInspectEventBus.showLayoutHierarchy() + BroadcastShortForm.LAYOUT_HIERARCHY -> LayoutInspectEventBus.showLayoutHierarchy() + BroadcastShortForm.HIERARCHY -> LayoutInspectEventBus.showLayoutHierarchy() } } } @@ -459,7 +467,35 @@ class App(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { @JvmStatic fun sendLocalBroadcastSyncInternal(intent: Intent?) { - intent?.let { LocalBroadcastManager.getInstance(globalContext).sendBroadcastSync(it) } + + // @Archived by SuperMonster003 on Sep 27, 2025. + // ! LocalBroadcastManager is deprecated. + // ! zh-CN: LocalBroadcastManager 已被弃用. + // # intent?.let { LocalBroadcastManager.getInstance(globalContext).sendBroadcastSync(it) } + + intent?.action?.let { action -> + try { + // Get tasks synchronously in background thread, then execute in main thread. + // zh-CN: 在后台线程同步获取任务, 再回到主线程执行. + val mainHandler = Handler(Looper.getMainLooper()) + Schedulers.io().scheduleDirect { + try { + val task = TimedTaskManager.getIntentTaskOfAction(action) + .firstOrError() + .blockingGet() + task?.let { + mainHandler.post { + BaseBroadcastReceiver.runTask(globalContext, intent, it) + } + } + } catch (e: Throwable) { + e.printStackTrace() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } } @JvmStatic 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 b4deb26c..10804d2c 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 @@ -24,6 +24,7 @@ import com.google.android.material.tabs.TabLayout import com.huaban.analysis.jieba.CharsDictionaryDatabase import com.huaban.analysis.jieba.PhrasesDictionaryDatabase import com.huaban.analysis.jieba.WordDictionaryDatabase +import org.autojs.autojs.App import org.autojs.autojs.AutoJs import org.autojs.autojs.app.FragmentPagerAdapterBuilder import org.autojs.autojs.app.FragmentPagerAdapterBuilder.StoredFragmentPagerAdapter @@ -318,6 +319,9 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { ScreenCapturerForegroundService.stopService() AutoJs.instance.scriptEngineService.stopAll() + AutoJs.instance.clear() + + App.app.clear() WordDictionaryDatabase.getInstance(applicationContext).close() CharsDictionaryDatabase.getInstance(applicationContext).close() diff --git a/app/src/main/java/org/autojs/autojs/ui/timing/TimedTaskSettingActivity.kt b/app/src/main/java/org/autojs/autojs/ui/timing/TimedTaskSettingActivity.kt index d2da34cf..d678832c 100644 --- a/app/src/main/java/org/autojs/autojs/ui/timing/TimedTaskSettingActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/timing/TimedTaskSettingActivity.kt @@ -420,7 +420,9 @@ class TimedTaskSettingActivity : BaseActivity() { val task = IntentTask().apply { action = actionString scriptPath = mScriptFile.path - isLocal = actionString == DynamicBroadcastReceivers.ACTION_STARTUP + if (actionString == DynamicBroadcastReceivers.ACTION_STARTUP) { + isLocal = true + } } when (val intentTask = mIntentTask) { null -> { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a43c22ac..bb2a41c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,5 @@ [versions] -#deprecated : Will be removed in the future -#substitution : LiveData or SharedFlow -#since : Sep 25, 2025 (by SuperMonster003) -localbroadcastmanager = "1.1.0" - #strictly : 4.12.0 #purpose : Compatibility for kotlin 2.0.x (e.g. 2.0.21) #because : Version 5.0.0+ requires kotlin 2.2.0+ @@ -37,7 +32,7 @@ jackson-databind = "2.13.4.2" log4j = "1.2.17" #purpose : Compatibility of java.time.* for Android API Level < 26 (Android 8.0) [O] -desugar = "2.0.4" +desugar = "2.1.5" analytics = "14.4.0" android-device-names = "2.1.1" @@ -151,7 +146,6 @@ junit-quickcheck-core = { module = "com.pholser:junit-quickcheck-core", version. junit-quickcheck-generators = { module = "com.pholser:junit-quickcheck-generators", version.ref = "junit-quickcheck-generators" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } licensesdialog = { module = "de.psdev.licensesdialog:licensesdialog", version.ref = "licensesdialog" } -localbroadcastmanager = { module = "androidx.localbroadcastmanager:localbroadcastmanager", version.ref = "localbroadcastmanager" } log4j = { module = "log4j:log4j", version.ref = "log4j" } material = { module = "com.google.android.material:material", version.ref = "material" } materialprogressbar = { module = "me.zhanghai.android.materialprogressbar:library", version.ref = "materialprogressbar" } diff --git a/version.properties b/version.properties index cb982c35..a1e97c2b 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ -#Thu Sep 25 19:23:59 CST 2025 -BUILD_TIME=1758799439530 +#Sun Sep 28 22:30:49 CST 2025 +BUILD_TIME=1759069849955 COMPILE_SDK_VERSION=36 IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_NDK_VERSION=26.1.10909125 @@ -23,6 +23,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 TARGET_SDK_VERSION=36 TARGET_SDK_VERSION_INRT=29 -VERSION_BUILD=3379 +VERSION_BUILD=3386 VERSION_NAME=6.7.0 Alpha6 VSCODE_EXT_REQUIRED_VERSION=1.0.8