refactor: split th_2_core.js into 12 modules, rename all files to 2-digit numbering

- Split th_2_core.js (4715 lines, 177KB) into:
  th_02_core.js, th_03_icon.js, th_04_theme.js, th_05_persistence.js,
  th_06_icon_parser.js, th_07_shortcut.js, th_08_content.js,
  th_09_animation.js, th_10_shell.js, th_11_action.js,
  th_12_rebuild.js, th_13_panel_ui.js
- Rename existing: th_1_base→th_01_base, th_3_panels→th_14_panels,
  th_4_extra→th_15_extra, th_5_entry→th_16_entry
- Update ToolHub.js MODULE_MANIFEST, modules array, and critical module check
This commit is contained in:
root
2026-04-20 11:53:13 +08:00
parent c64d4c336b
commit c7e9b92322
18 changed files with 4747 additions and 4722 deletions

170
code/th_03_icon.js Normal file
View File

@@ -0,0 +1,170 @@
// @version 1.0.0
FloatBallAppWM.prototype._iconCache = {
map: {},
keys: [],
max: 80, // 减少缓存数量,降低内存压力
get: function(key) {
var item = this.map[key];
if (!item) return null;
// 移动到末尾(最近使用)
var idx = this.keys.indexOf(key);
if (idx > -1) {
this.keys.splice(idx, 1);
this.keys.push(key);
}
return item.dr;
},
put: function(key, drawable) {
// 清理旧的
if (this.map[key]) {
this._remove(key);
}
// 空间检查:超过 80% 时批量清理 20%
if (this.keys.length >= this.max * 0.8) {
var removeCount = Math.floor(this.max * 0.2);
for (var i = 0; i < removeCount && this.keys.length > 0; i++) {
var oldKey = this.keys.shift();
this._remove(oldKey);
}
}
this.keys.push(key);
this.map[key] = {dr: drawable, ts: Date.now()};
},
_remove: function(key) {
var item = this.map[key];
if (item && item.dr) {
// 关键:回收 Bitmap防止内存泄漏
try {
if (item.dr instanceof android.graphics.drawable.BitmapDrawable) {
var bmp = item.dr.getBitmap();
if (bmp && !bmp.isRecycled()) bmp.recycle();
}
} catch(e) {}
delete this.map[key];
}
},
clear: function() {
for (var i = 0; i < this.keys.length; i++) {
this._remove(this.keys[i]);
}
this.keys = [];
}
};
// 兼容性封装(保持原有调用方式不变)
FloatBallAppWM.prototype._iconLruEnsure = function() {};
FloatBallAppWM.prototype._iconLruGet = function(key) {
return this._iconCache.get(key);
};
FloatBallAppWM.prototype._iconLruPut = function(key, val) {
try {
this._iconLruEnsure(120);
var k = String(key || "");
if (!k) return;
if (val == null) return;
// # 若已存在,先移除旧顺序位置
try {
var ord = this._iconLru.order;
for (var i = ord.length - 1; i >= 0; i--) {
if (ord[i] === k) { ord.splice(i, 1); break; }
}
ord.push(k);
} catch (eLru3) {}
this._iconLru.map[k] = val;
// # 超限清理:按最久未使用淘汰
try {
var maxN = Math.max(20, Math.floor(Number(this._iconLru.max || 120)));
var ord2 = this._iconLru.order;
while (ord2.length > maxN) {
var oldK = ord2.shift();
if (oldK != null) {
try { delete this._iconLru.map[oldK]; } catch (eDel) {}
}
}
} catch (eLru4) {}
} catch (eLru5) {}
};
// =======================【工具悬浮球图标PNG 文件)】======================
// # 这段代码的主要内容/用途:从指定路径加载透明 PNG 作为悬浮球图标;带"文件大小/像素上限"保护;按目标尺寸采样解码,避免 system_server OOM。
FloatBallAppWM.prototype.loadBallIconDrawableFromFile = function(path, targetPx, maxBytes, maxPx) {
try {
var p = String(path || "");
if (!p) return null;
// # 统一 LRU 缓存:文件图标(按 path + targetPx + mtime + size 复用 Drawable避免反复解码
var f = new java.io.File(p);
if (!f.exists() || !f.isFile()) return null;
var ckLru = null;
try {
ckLru = "file|" + p + "@" + String(targetPx == null ? "" : targetPx) + "@" + String(f.lastModified()) + "@" + String(f.length());
var hitLru = this._iconLruGet(ckLru);
if (hitLru) return hitLru;
} catch (eLruF0) {}
// # 文件大小限制(字节)
var limitBytes = Math.max(0, Math.floor(Number(maxBytes || 0)));
if (limitBytes > 0) {
try {
var sz = Number(f.length());
if (sz > limitBytes) return null;
} catch (eSz) { return null; }
}
// # 先只读尺寸(不解码)
var opt = new android.graphics.BitmapFactory.Options();
opt.inJustDecodeBounds = true;
try { android.graphics.BitmapFactory.decodeFile(p, opt); } catch (eB0) { return null; }
var w = Number(opt.outWidth || 0);
var h = Number(opt.outHeight || 0);
if (w <= 0 || h <= 0) return null;
// # 像素边长上限(宽/高任意一边超限则拒绝)
var limitPx = Math.max(0, Math.floor(Number(maxPx || 0)));
if (limitPx > 0) {
if (w > limitPx || h > limitPx) return null;
}
// # 计算采样倍率:按目标尺寸(一般为 iconSizePx采样
var tp = Math.max(1, Math.floor(Number(targetPx || 1)));
// # 允许解码到目标的 2 倍以内,减少锯齿又不浪费内存
var desired = Math.max(tp * 2, tp);
var sample = 1;
while ((w / sample) > desired || (h / sample) > desired) sample = sample * 2;
if (sample < 1) sample = 1;
var opt2 = new android.graphics.BitmapFactory.Options();
opt2.inJustDecodeBounds = false;
opt2.inSampleSize = sample;
opt2.inPreferredConfig = android.graphics.Bitmap.Config.ARGB_8888;
var bmp = null;
try { bmp = android.graphics.BitmapFactory.decodeFile(p, opt2); } catch (eB1) { bmp = null; }
if (bmp == null) return null;
var d = new android.graphics.drawable.BitmapDrawable(context.getResources(), bmp);
// # 写入统一 LRU 缓存
try {
if (ckLru) this._iconLruPut(ckLru, d);
} catch (eLruF1) {}
return d;
} catch (e0) {
return null;
}
};