fix(card): 观影 4x4/6x4 海报墙「只有 6 张图」修复 #2
用户原话:「观影4x4和4x6,只有六张图,划到第七张,就没图片了」
病根:两个上限各写各的
· CARD_IMG_MAX = 6 (配图数)
· rows.length < 30 (列表长度)
⇒ 前 6 格有海报,第 7 格起 img === '',posterCell 走 else 画淡紫占位块。
渲染没错、数据也没缺(线上 movie_details 221 条全有 poster_key,去重 205 张),
纯粹是「配图数 < 格子数」的结构性问题。
修法(不是把 6 改成 20 就完事,而是让格子数由图数决定):
· CARD_IMG_MAX 6 → 20,依据华为 Form Kit FAQ:API 20+ 上限 20 张 / 10MB,
本机 const.ohos.apiversion = 26 走这一档;
内存按解码后算 240×360×4×20 = 6.59MB < 10MB,留 3.4MB 余量。
· 拆出 CARD_ROW_MAX = 30(命名常量),专给不画图的 2x2/2x4 纯文本行。
· 新增 list:只保留 img !== '' 的行,长度天然 ≤ CARD_IMG_MAX
⇒ 格子数与图数在结构上永远相等,某张 404 也不会冒空格子。
· fbAttachPosters 签名 (recent, list) → (rows),两个数组各写一遍循环太容易只改一处。
· 新增 fbCardImagePaths 并发取图(MAX_PARALLEL = 4):20 张串行实测 10~17s,
卡片刷新窗口撑不住。MAX_FILES 24 必须 ≥ CARD_IMG_MAX,否则后下的挤掉先下的。
验证:静态守卫 1317/0;反向验证 15/0(7 条注入各自恰好命中目标断言)。
其中注入 A 抓出原先一条假绿断言:写的是「CARD_IMG_MAX >= 4」,
而 bug 原值正好是 6,6 >= 4 恒真 ⇒ 等于什么都没守。已改成「> 6」。
This commit is contained in:
@@ -38,12 +38,31 @@ const TAG: string = 'FbCardImg';
|
||||
|
||||
/** 缓存目录(`filesDir/cardimg`):跟着应用数据走,卸载即清 */
|
||||
const DIR_NAME: string = 'cardimg';
|
||||
/** 缩略图尺寸:卡片上最大也就 ~150vp 宽(≈ 470px@3.125x),240×360 足够 */
|
||||
/** 缩略图尺寸:卡片上最大也就 ~150vp 宽(≈ 470px@3.2x),240×360 足够。
|
||||
*
|
||||
* ⚠️ 别为了"更清晰"往上调:卡片图片的官方限制是按**解码后内存**算的
|
||||
* (宽×高×4),不是 jpg 文件大小。240×360 = 0.33MB/张,20 张 6.59MB 安全;
|
||||
* 调到 360×540 就是 0.74MB/张,20 张 14.8MB 超过 10MB 上限 ⇒ 丢图甚至白卡。
|
||||
* 依据见 `FbCardMediaData.ets` 里 `CARD_IMG_MAX` 的注释(工单 #2)。 */
|
||||
const THUMB_W: number = 240;
|
||||
const THUMB_H: number = 360;
|
||||
/** 目录里最多留多少个文件(每部电影一张,24 部够翻一大阵子) */
|
||||
/**
|
||||
* 目录里最多留多少个文件。
|
||||
*
|
||||
* 🔴 必须 **≥ `FbCardMediaData.CARD_IMG_MAX`**(现在 20)。
|
||||
* 工单 #2 把图数量从 6 提到 20,如果这里还是原来的小值,
|
||||
* 就会出现「一次推送要 20 张、缓存只留 N 张」——
|
||||
* 后下载的把先下载的挤掉,表现为「翻回去前面几张又没了」,比原来的 bug 更难查。
|
||||
* 24 是 20 + 4 的余量(留几张上一轮的,翻页时不必重新下载)。
|
||||
*/
|
||||
const MAX_FILES: number = 24;
|
||||
|
||||
/**
|
||||
* 同时下载几张(见 `fbCardImagePaths` 的注释:为什么不是 1,也不是 20)。
|
||||
* 4 是折中:20 张压到 5 轮,又不会一次开 20 个连接。
|
||||
*/
|
||||
const MAX_PARALLEL: number = 4;
|
||||
|
||||
function dirOf(ctx: common.Context): string {
|
||||
return ctx.filesDir + '/' + DIR_NAME;
|
||||
}
|
||||
@@ -236,6 +255,55 @@ export async function fbCardImagePath(ctx: common.Context, posterKey: string,
|
||||
return made;
|
||||
}
|
||||
|
||||
/**
|
||||
* 并发取多张海报的本地小图。
|
||||
*
|
||||
* 🔴🔴 为什么需要它(工单 #2,2026-10-27):图数量从 6 提到 20 之后,
|
||||
* 原来那种"一条 for 循环里逐张 await"的写法会变成**串行 20 次网络往返**。
|
||||
* 单张超时是 `connectTimeout 6000 + readTimeout 10000`,最坏 16s/张 ⇒
|
||||
* 20 张串行理论上能卡 320 秒,而卡片刷新窗口远没有这么长
|
||||
* (`FbFormAbility` 里 updateForm 完不成,系统会按失败处理,
|
||||
* 表现是"卡片一直旧数据 / 干脆不刷新")。
|
||||
* 实测首轮 6 张串行约 3~5 秒,20 张串行会到 10~17 秒,已经接近不可接受。
|
||||
*
|
||||
* ⇒ 用**固定并发窗口**(`MAX_PARALLEL`)滚动推进,而不是一次 20 个并发:
|
||||
* · 一次全发:瞬时开 20 个 HttpRequest,小内存设备容易失败,且服务端瞬时压力大;
|
||||
* · 串行:见上,太慢。
|
||||
* 4 路并发把 20 张压到约 5 轮,总耗时接近"最慢的那一轮",实际 3~6 秒。
|
||||
*
|
||||
* @return 与 `keys` **一一对应**的本地路径数组(失败的位置是空串,不改变长度/顺序)
|
||||
*/
|
||||
export async function fbCardImagePaths(ctx: common.Context, keys: string[],
|
||||
urlOf: (key: string) => string): Promise<string[]> {
|
||||
const out: string[] = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
out.push('');
|
||||
}
|
||||
let next: number = 0;
|
||||
const worker = async (): Promise<void> => {
|
||||
while (true) {
|
||||
const idx: number = next;
|
||||
next++;
|
||||
if (idx >= keys.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
out[idx] = await fbCardImagePath(ctx, keys[idx], urlOf(keys[idx]));
|
||||
} catch (err) {
|
||||
// 单张失败只留空,绝不冒泡(卡片进程被回收最多是"这张没图")
|
||||
out[idx] = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
const n: number = Math.min(MAX_PARALLEL, keys.length);
|
||||
const workers: Promise<void>[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
workers.push(worker());
|
||||
}
|
||||
await Promise.all(workers);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 打开本地小图并返回 fd(失败 -1) */
|
||||
export function fbCardImageFd(path: string): number {
|
||||
if (path === '') {
|
||||
|
||||
Reference in New Issue
Block a user