// @version 1.0.0 function runOnMainSync(fn, timeoutMs) { if (!fn) return { ok: false, error: "empty-fn" }; try { var mainLooper = android.os.Looper.getMainLooper(); var myLooper = android.os.Looper.myLooper(); if (mainLooper !== null && myLooper !== null && myLooper === mainLooper) { return { ok: true, value: fn() }; } } catch (eLoop) {} try { var box = { ok: false, value: null, error: null }; var latch = new java.util.concurrent.CountDownLatch(1); var h = new android.os.Handler(android.os.Looper.getMainLooper()); h.post(new java.lang.Runnable({ run: function() { try { box.value = fn(); box.ok = true; } catch (eRun) { box.error = eRun; } finally { latch.countDown(); } } })); var waitMs = timeoutMs || 1500; var done = latch.await(waitMs, java.util.concurrent.TimeUnit.MILLISECONDS); if (!done) return { ok: false, error: "timeout" }; if (!box.ok) return { ok: false, error: box.error }; return box; } catch (e) { return { ok: false, error: e }; } } function registerReceiverOnMain(actions, callback) { try { var rcv = new JavaAdapter(android.content.BroadcastReceiver, { onReceive: function(ctx, intent) { try { callback(ctx, intent); } catch (e) { safeLog(null, 'e', "receiver callback fail: " + String(e)); } } }); var f = new android.content.IntentFilter(); var isArray = false; try { isArray = (Object.prototype.toString.call(actions) === "[object Array]"); } catch (eArr0) { isArray = false; } var actList = isArray ? actions : [String(actions)]; var i; for (i = 0; i < actList.length; i++) { f.addAction(String(actList[i])); } var reg = runOnMainSync(function() { context.getApplicationContext().registerReceiver(rcv, f); return true; }, 2000); if (!reg.ok) { safeLog(null, 'e', "registerReceiver fail: " + String(reg.error)); return null; } return rcv; } catch (e) { safeLog(null, 'e', "registerReceiverOnMain fatal: " + String(e)); return null; } } FloatBallAppWM.prototype.close = function() { if (this.state.closing) return; this.state.closing = true; safeLog(this.L, 'i', "close begin"); this.cancelDockTimer(); this.stopDisplayMonitor(); try { if (this.state.addedBall && this.state.ballLp) this.savePos(this.state.ballLp.x, this.state.ballLp.y); } catch (eS) {} try { FileIO.flushDebouncedWrites(); } catch (eFlushCfg) { safeLog(this.L, 'e', "flushDebouncedWrites fail: " + String(eFlushCfg)); } this.hideAllPanels(); if (this.state.addedBall && this.state.ballRoot) this.safeRemoveView(this.state.ballRoot, "ballRoot"); this.state.ballRoot = null; this.state.ballContent = null; this.state.ballLp = null; this.state.addedBall = false; // # 注销广播接收器 (修复内存泄漏) if (this.state.receivers && this.state.receivers.length > 0) { var list = this.state.receivers.slice ? this.state.receivers.slice(0) : this.state.receivers; var unreg = runOnMainSync(function() { for (var i = 0; i < list.length; i++) { try { context.getApplicationContext().unregisterReceiver(list[i]); } catch(e) { safeLog(null, 'e', "unregisterReceiver fail: " + String(e)); } } return true; }, 2000); if (!unreg.ok) safeLog(this.L, 'e', "receiver cleanup incomplete: " + String(unreg.error)); this.state.receivers = []; } // # 清理 HandlerThread try { if (this.state.ht) { if (android.os.Build.VERSION.SDK_INT >= 18) this.state.ht.quitSafely(); else this.state.ht.quit(); } } catch (eQ) {} // # 清理图标加载线程 try { if (this._iconLoader && this._iconLoader.ht) { if (android.os.Build.VERSION.SDK_INT >= 18) this._iconLoader.ht.quitSafely(); else this._iconLoader.ht.quit(); } } catch (eIcon) {} try { if (self.__scIconLoaderSingleton && self.__scIconLoaderSingleton.ht) { if (android.os.Build.VERSION.SDK_INT >= 18) self.__scIconLoaderSingleton.ht.quitSafely(); else self.__scIconLoaderSingleton.ht.quit(); } } catch (eScIcon) {} try { self.__scIconLoaderSingleton = null; } catch (eScIcon2) {} safeLog(this.L, 'i', "close done"); // # 清理日志定时器 try { if (this.L) { try { this.L._flushBuffer(); } catch (eFlushLog0) { safeLog(this.L, 'e', "logger flush fail: " + String(eFlushLog0)); } if (this.L._flushTimer) { this.L._flushTimer.cancel(); this.L._flushTimer = null; } } } catch (eLog) {} // # 清空缓存 try { this._iconLru = null; this._shortcutIconFailTs = {}; if (typeof __scIconCache !== "undefined") __scIconCache = {}; if (typeof __scAppLabelCache !== "undefined") __scAppLabelCache = {}; } catch (eCache) {} }; /** * 完全销毁实例,释放所有资源 * 用于长期运行后彻底清理,避免内存泄漏 */ FloatBallAppWM.prototype.dispose = function() { // # 先执行标准关闭流程 this.close(); // # 清理单例引用 try { if (self.__shortcutPickerSingleton === this.__shortcutPickerSingleton) { self.__shortcutPickerSingleton = null; } } catch (e) {} // # 清理配置缓存 this._settingsCache = null; this._buttonsCache = null; // # 清理日志引用 this.L = null; // # 标记已销毁 this.state = { disposed: true }; }; FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) { var self = this; var kv = { pkg: String((entryProcInfo && entryProcInfo.packageName) ? entryProcInfo.packageName : ""), proc: String((entryProcInfo && entryProcInfo.processName) ? entryProcInfo.processName : ""), uid: String((entryProcInfo && entryProcInfo.uid) ? entryProcInfo.uid : "") }; var action = applyRule(closeRule, kv); if (!action) action = "shortx.wm.floatball.CLOSE"; this.config.ACTION_CLOSE_ALL = String(action); var preCloseSent = false; try { context.sendBroadcast(new android.content.Intent(String(this.config.ACTION_CLOSE_ALL))); preCloseSent = true; } catch (e0) { try { if (typeof shell === "function") { shell("am broadcast -a " + String(this.config.ACTION_CLOSE_ALL)); preCloseSent = true; } } catch (e1) {} } var ht = new android.os.HandlerThread(String(this.config.WM_THREAD_NAME)); ht.start(); var h = new android.os.Handler(ht.getLooper()); this.state.ht = ht; this.state.h = h; // # 注册广播接收器(统一管理) var closeRcv = registerReceiverOnMain(this.config.ACTION_CLOSE_ALL, function(ctx, it) { try { h.post(new JavaAdapter(java.lang.Runnable, { run: function() { try { self.close(); } catch (e1) {} } })); } catch (e2) {} }); if (closeRcv) this.state.receivers.push(closeRcv); var cfgRcv = registerReceiverOnMain( [android.content.Intent.ACTION_CONFIGURATION_CHANGED, android.content.Intent.ACTION_WALLPAPER_CHANGED], function(ctx, intent) { try { var act = String(intent.getAction()); h.post(new JavaAdapter(java.lang.Runnable, { run: function() { try { if (self.state.closing) return; if (act === android.content.Intent.ACTION_CONFIGURATION_CHANGED) { self.cancelDockTimer(); self.onScreenChangedReflow(); self.touchActivity(); } if (self.state.ballContent) self.updateBallContentBackground(self.state.ballContent); if (self.state.panel) self.updatePanelBackground(self.state.panel); if (self.state.settingsPanel) self.updatePanelBackground(self.state.settingsPanel); if (self.state.viewerPanel) self.updatePanelBackground(self.state.viewerPanel); } catch (e1) {} } })); } catch (e0) {} } ); if (cfgRcv) this.state.receivers.push(cfgRcv); h.post(new JavaAdapter(java.lang.Runnable, { run: function() { try { self.state.wm = context.getSystemService(android.content.Context.WINDOW_SERVICE); self.state.density = context.getResources().getDisplayMetrics().density; if (self.L) self.L.updateConfig(self.config); self.state.loadedPos = self.loadSavedPos(); self.state.screen = self.getScreenSizePx(); self.state.lastRotation = self.getRotation(); self.createBallViews(); self.state.ballLp = self.createBallLayoutParams(); try { self.state.wm.addView(self.state.ballRoot, self.state.ballLp); self.state.addedBall = true; } catch (eAdd) { try { self.toast("悬浮球 addView 失败: " + String(eAdd)); } catch (eT) {} if (self.L) self.L.fatal("addView ball fail err=" + String(eAdd)); self.state.addedBall = false; try { self.close(); } catch (eC) {} return; } self.setupDisplayMonitor(); self.touchActivity(); if (self.L) { self.L.i("start ok actionClose=" + String(self.config.ACTION_CLOSE_ALL)); self.L.i("ball x=" + String(self.state.ballLp.x) + " y=" + String(self.state.ballLp.y) + " sizeDp=" + String(self.config.BALL_SIZE_DP)); } } catch (eAll) { try { self.toast("启动异常: " + String(eAll)); } catch (eTT2) {} if (self.L) self.L.fatal("start runnable err=" + String(eAll)); try { self.close(); } catch (eC2) {} } } })); return { ok: true, msg: "已按 WM 专属 HandlerThread 模型启动(Shell 默认 Action,失败广播桥兜底;Content URI 已启用)", preCloseBroadcastSent: preCloseSent, closeAction: String(this.config.ACTION_CLOSE_ALL), receiverRegisteredOnMain: { // 修复:旧版本遗留变量 closeRegistered/cfgRegistered 可能未定义,避免触发 ReferenceError 导致重启 close: (typeof closeRegistered !== "undefined") ? !!closeRegistered : false, config: (typeof cfgRegistered !== "undefined") ? !!cfgRegistered : false }, cfgPanelKey: this.currentPanelKey, buttons: (this.panels && this.panels[this.currentPanelKey]) ? this.panels[this.currentPanelKey].length : 0, layout: { cols: this.config.PANEL_COLS, rows: this.config.PANEL_ROWS }, threadModel: { entryThreadMustNotTouchWM: true, perOverlaySingleHandlerThread: true, wmThreadName: String(this.config.WM_THREAD_NAME) }, shell: { useActionFirst: false, // 已移除 ShellCommand Action hasShellCommand: false, // 已移除 ShellCommand Action bridge: { action: String(this.config.SHELL_BRIDGE_ACTION), extraCmd: String(this.config.SHELL_BRIDGE_EXTRA_CMD), extraRoot: String(this.config.SHELL_BRIDGE_EXTRA_ROOT), defaultRoot: !!this.config.SHELL_BRIDGE_DEFAULT_ROOT } }, content: { maxRows: Number(this.config.CONTENT_MAX_ROWS || 20), viewerTextSp: Number(this.config.CONTENT_VIEWER_TEXT_SP || 12) } }; };