fix: improve ToolHub startup and cleanup stability
This commit is contained in:
@@ -2311,78 +2311,7 @@ var scInlineState = {
|
||||
lastQuery: ""
|
||||
};
|
||||
|
||||
// # 图标缓存与队列(避免每次重渲染都重复取 icon,减少卡顿)
|
||||
// 这段代码的主要内容/用途:为内联列表提供轻量级 icon 缓存与串行加载队列,避免一次性开太多线程。
|
||||
var scIconCache = {};
|
||||
var scIconQueue = [];
|
||||
var scIconWorkerRunning = false;
|
||||
function __scIconKey(it) {
|
||||
try { return __scStr(it.pkg) + '|' + __scStr(it.shortcutId) + '|' + __scStr(it.userId); } catch(e) { return ''; }
|
||||
}
|
||||
function __scLoadIconForItem(it) {
|
||||
// 这段代码的主要内容/用途:优先取快捷方式图标,失败则回退到应用图标。
|
||||
try {
|
||||
if (!it) return null;
|
||||
if (it.shortcutInfo) {
|
||||
try {
|
||||
var la = context.getSystemService(android.content.Context.LAUNCHER_APPS_SERVICE);
|
||||
if (la) {
|
||||
var dr = la.getShortcutIconDrawable(it.shortcutInfo, 0);
|
||||
if (dr) return dr;
|
||||
}
|
||||
} catch(eS0) { safeLog(null, 'e', "catch " + String(eS0)); }
|
||||
}
|
||||
try {
|
||||
var pm = context.getPackageManager();
|
||||
return pm.getApplicationIcon(__scStr(it.pkg));
|
||||
} catch(eA0) { safeLog(null, 'e', "catch " + String(eA0)); }
|
||||
} catch(eAll0) { safeLog(null, 'e', "catch " + String(eAll0)); }
|
||||
return null;
|
||||
}
|
||||
function __scEnqueueIconLoad(it, iv) {
|
||||
try {
|
||||
var key = __scIconKey(it);
|
||||
if (!key) return;
|
||||
if (scIconCache[key]) {
|
||||
try { iv.setImageDrawable(scIconCache[key]); } catch(eSet0) { safeLog(null, 'e', "catch " + String(eSet0)); }
|
||||
return;
|
||||
}
|
||||
// # 记录 tag:防止滚动/重绘后错位
|
||||
try { iv.setTag(key); } catch(eTag0) { safeLog(null, 'e', "catch " + String(eTag0)); }
|
||||
scIconQueue.push({ key: key, it: it, iv: iv });
|
||||
if (!scIconWorkerRunning) {
|
||||
scIconWorkerRunning = true;
|
||||
new java.lang.Thread(new java.lang.Runnable({
|
||||
run: function() {
|
||||
while (true) {
|
||||
var job = null;
|
||||
try { if (scIconQueue.length > 0) job = scIconQueue.shift(); } catch(eQ0) { job = null; }
|
||||
if (!job) break;
|
||||
|
||||
var dr = null;
|
||||
try { dr = __scLoadIconForItem(job.it); } catch(eLd0) { dr = null; }
|
||||
if (dr) scIconCache[job.key] = dr;
|
||||
|
||||
try {
|
||||
self.runOnUiThreadSafe(function() {
|
||||
try {
|
||||
if (!job || !job.iv) return;
|
||||
var cur = null;
|
||||
try { cur = job.iv.getTag(); } catch(eTg0) { cur = null; }
|
||||
if (cur && String(cur) === String(job.key) && dr) {
|
||||
job.iv.setImageDrawable(dr);
|
||||
}
|
||||
} catch(eUi0) { safeLog(null, 'e', "catch " + String(eUi0)); }
|
||||
});
|
||||
} catch(ePost0) { safeLog(null, 'e', "catch " + String(ePost0)); }
|
||||
}
|
||||
scIconWorkerRunning = false;
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
} catch(eEnq0) { safeLog(null, 'e', "catch " + String(eEnq0)); }
|
||||
}
|
||||
|
||||
// # 图标缓存与加载已统一使用下方 __scIconLoader / __scRequestIcon,避免维护两套后台线程队列。
|
||||
// # 折叠头部(点击展开/收起)
|
||||
var scHeader = new android.widget.LinearLayout(context);
|
||||
scHeader.setOrientation(android.widget.LinearLayout.HORIZONTAL);
|
||||
@@ -2410,10 +2339,10 @@ scRefreshTv.setOnClickListener(new android.view.View.OnClickListener({
|
||||
try {
|
||||
scInlineState.forceReload = true;
|
||||
scInlineState.loaded = false;
|
||||
// 清空 icon 缓存,避免旧图标占用内存且影响新列表显示
|
||||
try { scIconCache = {}; } catch(eC0) { safeLog(null, 'e', "catch " + String(eC0)); }
|
||||
try { scIconQueue = []; } catch(eC1) { safeLog(null, 'e', "catch " + String(eC1)); }
|
||||
try { scIconWorkerRunning = false; } catch(eC2) { safeLog(null, 'e', "catch " + String(eC2)); }
|
||||
// 清空当前内联列表的图标缓存,避免旧图标占用内存且影响新列表显示
|
||||
try { __scIconCache = {}; } catch(eC0) { safeLog(null, 'e', "catch " + String(eC0)); }
|
||||
try { __scIconKeys = []; } catch(eC1) { safeLog(null, 'e', "catch " + String(eC1)); }
|
||||
try { __scIconInFlight = {}; } catch(eC2) { safeLog(null, 'e', "catch " + String(eC2)); }
|
||||
// 若当前已展开,立即触发重新加载与渲染
|
||||
if (scInlineState.expanded) __scEnsureLoadedAndRender();
|
||||
} catch(eR) { safeLog(null, 'e', "catch " + String(eR)); }
|
||||
|
||||
@@ -119,12 +119,12 @@ FloatBallAppWM.prototype.close = function() {
|
||||
}
|
||||
} 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();
|
||||
if (this.__scIconLoaderSingleton && this.__scIconLoaderSingleton.ht) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 18) this.__scIconLoaderSingleton.ht.quitSafely();
|
||||
else this.__scIconLoaderSingleton.ht.quit();
|
||||
}
|
||||
} catch (eScIcon) {}
|
||||
try { self.__scIconLoaderSingleton = null; } catch (eScIcon2) {}
|
||||
try { this.__scIconLoaderSingleton = null; } catch (eScIcon2) {}
|
||||
|
||||
safeLog(this.L, 'i', "close done");
|
||||
|
||||
@@ -158,8 +158,8 @@ FloatBallAppWM.prototype.dispose = function() {
|
||||
|
||||
// # 清理单例引用
|
||||
try {
|
||||
if (self.__shortcutPickerSingleton === this.__shortcutPickerSingleton) {
|
||||
self.__shortcutPickerSingleton = null;
|
||||
if (this.__shortcutPickerSingleton) {
|
||||
this.__shortcutPickerSingleton = null;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
@@ -245,50 +245,83 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
|
||||
);
|
||||
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.screen = self.getScreenSizePx();
|
||||
self.state.lastRotation = self.getRotation();
|
||||
self.state.loadedPos = self.loadSavedPos();
|
||||
|
||||
self.createBallViews();
|
||||
self.state.ballLp = self.createBallLayoutParams();
|
||||
|
||||
var startBox = { ok: false, err: "启动确认超时", added: false };
|
||||
var startLatch = new java.util.concurrent.CountDownLatch(1);
|
||||
var posted = false;
|
||||
try {
|
||||
posted = h.post(new JavaAdapter(java.lang.Runnable, {
|
||||
run: function() {
|
||||
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.state.wm = context.getSystemService(android.content.Context.WINDOW_SERVICE);
|
||||
self.state.density = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
self.setupDisplayMonitor();
|
||||
self.touchActivity();
|
||||
if (self.L) self.L.updateConfig(self.config);
|
||||
|
||||
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));
|
||||
self.state.screen = self.getScreenSizePx();
|
||||
self.state.lastRotation = self.getRotation();
|
||||
self.state.loadedPos = self.loadSavedPos();
|
||||
|
||||
self.createBallViews();
|
||||
self.state.ballLp = self.createBallLayoutParams();
|
||||
|
||||
try {
|
||||
self.state.wm.addView(self.state.ballRoot, self.state.ballLp);
|
||||
self.state.addedBall = true;
|
||||
startBox.added = true;
|
||||
} catch (eAdd) {
|
||||
startBox.ok = false;
|
||||
startBox.err = "悬浮球 addView 失败: " + String(eAdd);
|
||||
try { self.toast(startBox.err); } 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();
|
||||
|
||||
startBox.ok = true;
|
||||
startBox.err = "";
|
||||
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) {
|
||||
startBox.ok = false;
|
||||
startBox.err = "启动异常: " + String(eAll);
|
||||
try { self.toast(startBox.err); } catch (eTT2) {}
|
||||
if (self.L) self.L.fatal("start runnable err=" + String(eAll));
|
||||
try { self.close(); } catch (eC2) {}
|
||||
} finally {
|
||||
try { startLatch.countDown(); } catch (eLatch) {}
|
||||
}
|
||||
} 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) {}
|
||||
}
|
||||
}));
|
||||
} catch (ePost) {
|
||||
posted = false;
|
||||
startBox.ok = false;
|
||||
startBox.err = "启动任务投递失败: " + String(ePost);
|
||||
try { startLatch.countDown(); } catch (eLatch2) {}
|
||||
}
|
||||
|
||||
if (!posted) {
|
||||
startBox.ok = false;
|
||||
if (!startBox.err) startBox.err = "启动任务投递失败";
|
||||
} else {
|
||||
try {
|
||||
var done = startLatch.await(2500, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||
if (!done && self.L) self.L.e("start confirm timeout; addView result unknown");
|
||||
} catch (eWait) {
|
||||
startBox.ok = false;
|
||||
startBox.err = "启动确认等待异常: " + String(eWait);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
msg: "已按 WM 专属 HandlerThread 模型启动(Shell 默认 Action,失败广播桥兜底;Content URI 已启用)",
|
||||
ok: !!startBox.ok,
|
||||
err: String(startBox.err || ""),
|
||||
msg: startBox.ok ? "悬浮球 addView 已确认成功" : String(startBox.err || "启动失败"),
|
||||
preCloseBroadcastSent: preCloseSent,
|
||||
closeAction: String(this.config.ACTION_CLOSE_ALL),
|
||||
receiverRegisteredOnMain: {
|
||||
|
||||
@@ -54,19 +54,19 @@
|
||||
"size": 20386
|
||||
},
|
||||
"th_14_panels.js": {
|
||||
"sha256": "65528739877540cdd457017ebd083e1ad0f1f911706129d75a88647c48a59c51",
|
||||
"size": 220509
|
||||
"sha256": "a237b164e29f4a581893b86bd7d3f9776dc0ecc7a56ecc53f9465525fa117253",
|
||||
"size": 217295
|
||||
},
|
||||
"th_15_extra.js": {
|
||||
"sha256": "c8e10fe54a965fe7b27f0996b2f2636655077e39df774d6cbb1e73c5b36553fc",
|
||||
"size": 62625
|
||||
},
|
||||
"th_16_entry.js": {
|
||||
"sha256": "8c7d2d8dfa1dc51b47a01be8639a9da78cb40670c66cc8f78d339afecfd83be5",
|
||||
"size": 11125
|
||||
"sha256": "6bb08673d720665e2d6a04d4b2021cc7f836818f4781daff59cb7b382322d1c9",
|
||||
"size": 12285
|
||||
}
|
||||
},
|
||||
"keyId": "toolhub-targets-2026-rsa3072",
|
||||
"schema": 2,
|
||||
"version": 20260507155220
|
||||
"version": 20260512020008
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
TwIGuNRj9b0pIqiICJpA3vNxwNtQfsSj4LQKyOcUfuVxbbDsAJ1SZWra9ioDdd1jdjo8jEiSHMZ5JgDdKSkrUQZwUpQYsBXdtFFS2Zb5aoGk7QLgrgHRoJvvJOinU3/DLS93by3msNMoEJmZLQEoa4ltXK7hehc5dclp82ZOXg3p5A1OCgdzavGzSqPfg4eH1o3GHzS2I/zDhZgzrbKNcoKUsy//fr5E34ePQ4aWNXAmcyQFNXLT8aCDL+yXn3wjbTgASuHS+GncwbYJ5y4/toMyW+EJNX0xiOsIa7OFA7jRfX0U6qqgZH0fW/jGlZNeJhnqZYfOpjKIPy7t7B5iThJT0iiJ6L4X/mjUe9ggO/YAhJ7g4Vp8m/g5LpmWpVPLRwYJje6Q5Ypulm95CaBek2FEjXwqGa1hsCQ1MfMj1rCwfn7biLH1Vkfff+JgoRvE/2JWbPMLU+j1jCNOLWVUPIpTsazu8/kR4NkjJPUm7uZX5ogtfRhGbkLl08+lB8Lm
|
||||
axGRqLze6TvsjEgpjNjRGL0u2CHxBCSwWZ8AKQBZ2Q9Gr8rMajyTkNt6vRW0aHPWaJhM9Silyh/3VIoy4iz0Id2RltAwADV7XygLGL2jMGlHsjC2U1g8bavgL1JKK/UY0WvnQ1fUgI2bDqWA2eN/SQvP0/DbRwOeheCvcNrf5aHxMuEYyorIbbhFxsqGXb8Uut6ieKe2aUV5jSL1LOatt2QKgnOM5ylpRmf3M9w3g8f4SDjHKhCup6K7H52ooazoFmMP030suXVhn1vmujrZtQtcgmzMU6hI1ZSZOcPIekgWxzIlJ2ktXxR/b9TlQ4JcEtxG2D/jIx02M0mQXE9kfZZJ7I3g6h2FNc4YdJS51pXr7YvtGPevR6yxP+/J5e6fVcm+W5EW8eFHzrtyhBI8zvG7vJSCYBV+jwl/XNznwnx0whduQ7RqZMM4++xdzyew5wtIWnlVxI4wqX+B852cnSU/EdTPu2HoBt+TfqEO5p7oDok1pO01i5BWghkvujKt
|
||||
|
||||
Reference in New Issue
Block a user