- 689处空catch块补全日志 - eval远程代码增加SHA256校验 - 删除ToolHubLogger重复定义 - getParentFile()增加null保护 - 提取buildButtonEditorPanelView内通用工具函数到文件级 - 修复HandlerThread/ValueAnimator资源泄漏
171 lines
5.5 KiB
JavaScript
171 lines
5.5 KiB
JavaScript
// @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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(eDel)); }
|
||
}
|
||
}
|
||
} catch(eLru4) { safeLog(null, 'e', "catch " + String(eLru4)); }
|
||
} catch(eLru5) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(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) { safeLog(null, 'e', "catch " + String(eLruF1)); }
|
||
return d;
|
||
} catch (e0) {
|
||
return null;
|
||
}
|
||
};
|
||
|