三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

react navite图片加载优化、大图卡顿、缓存策略

react navite图片加载优化、大图卡顿、缓存策略

如果你是在做React Native 图片加载优化,尤其是「大图卡顿 + 列表图片 + 缓存」,核心不是单纯换一个 Image 组件,而是要同时处理:

图片尺寸 → 解码 → 内存 → 磁盘缓存 → 列表复用 → 网络加载

1. 大图卡顿的核心原因

比如服务器返回一张4000 × 3000的 JPEG:

  • 文件可能只有 2 MB

  • 但解码到内存后大约是:

4000 × 3000 × 4 ≈ 48 MB

如果一个 FlatList 同时出现 5 张这样的图片,就可能瞬间产生200+ MB的 Bitmap/RGBA 内存压力。

所以:

不要把“压缩文件大小”和“降低图片内存占用”混为一谈。

最有效的优化通常是让服务端根据显示尺寸返回合适分辨率。


2. 首选:服务端图片缩放

假设 UI 中图片实际只显示:

屏幕:375px 图片:150 × 150

不要请求:

4000 × 4000

而应该请求类似:

300 × 300

或者根据 DPR:

150 × 150 × 2 = 300 × 300

如果你的图片 CDN 支持参数,可以设计成:

/image/xxx.jpg?w=300&h=300&q=80

甚至:

/image/xxx.jpg?w=600&h=600&dpr=2

推荐尺寸策略

UI 图片尺寸推荐下载尺寸
50×50100×100
100×100200×200
150×150300×300
300×200600×400
全屏图片根据设备实际分辨率

这通常是 RN 图片性能优化里收益最大的一步


3. React Native 中不要只用<Image>

如果是大量图片、Feed、聊天列表、瀑布流,我更推荐考虑:

expo-image

如果你使用 Expo:

import { Image } from 'expo-image'; <Image source={{ uri: imageUrl }} style={styles.image} contentFit="cover" cachePolicy="memory-disk" />

缓存策略:

cachePolicy="memory"

适合:

图片很容易重复出现,但不希望磁盘缓存太多。

cachePolicy="disk"

适合:

希望图片能够跨 App 重启继续使用缓存。

cachePolicy="memory-disk"

通常比较适合 Feed / 商品列表 / 社交 App。

还可以使用:

placeholder={blurhash} transition={200}

改善用户感知上的加载体验。


4. 如果是纯 React Native

可以使用:

<Image source={{ uri: url }} style={styles.image} />

但需要注意:

RN Image 自身的缓存行为并不等于一个完整的图片缓存系统。

如果你对缓存有比较严格的需求,可以考虑:

  • expo-image

  • react-native-fast-image

  • 自己实现 HTTP/CDN 缓存策略

不过现在如果是新项目,我通常会优先考虑expo-image,尤其是 Expo 项目。


5. FlatList 才是第二个大坑

很多时候用户说:

“图片加载卡顿”

实际上真正的问题是:

FlatList ↓ 同时创建大量 Image ↓ 大量图片 decode ↓ JS / UI / Native 内存压力 ↓ 掉帧

例如:

<FlatList data={data} renderItem={renderItem} keyExtractor={(item) => item.id} />

可以进一步优化:

<FlatList data={data} renderItem={renderItem} keyExtractor={(item) => item.id} initialNumToRender={8} maxToRenderPerBatch={6} windowSize={5} removeClippedSubviews />

但这些参数不要盲目调得很小

例如:

windowSize={2}

虽然内存下降,但快速滑动时可能频繁创建/销毁内容,反而出现闪烁和重新加载。


6. 图片组件一定要固定尺寸

不推荐:

<Image source={{ uri: url }} style={{ width: '100%', aspectRatio: 1, }} />

然后让布局不断计算。

更推荐让 FlatList item 的尺寸尽可能明确:

const IMAGE_SIZE = 120; <Image source={{ uri: item.image }} style={{ width: IMAGE_SIZE, height: IMAGE_SIZE, }} />

尤其是:

  • Grid

  • Avatar

  • 商品列表

  • 聊天列表

  • 瀑布流

明确尺寸可以减少 layout 和渲染的不确定性。


7. 缓存策略不要简单理解成“永久缓存”

比较合理的是:

Memory Cache ↓ Disk Cache ↓ Network

例如:

第一次打开 Network ↓ Disk Cache ↓ Memory Cache 第二次进入页面 Memory Cache ↓ 直接显示 App 重启 Disk Cache ↓ 读取 缓存不存在 / 过期 Network

对于头像、商品图片、Feed 图片,可以采用不同策略。

Avatar

Memory + Disk

因为重复率很高。

Feed 图片

Memory + Disk

但要限制磁盘缓存规模。

一次性大图

Disk

甚至:

不长期缓存

8. URL 一定要稳定

缓存最怕这种情况:

https://cdn.xxx.com/a.jpg?t=123 https://cdn.xxx.com/a.jpg?t=456 https://cdn.xxx.com/a.jpg?t=789

虽然实际是同一张图片,但缓存系统可能认为是不同资源。

更好的方式:

https://cdn.xxx.com/image/a.jpg?v=abc123

内容发生变化的时候才修改版本:

a.jpg?v=abc124

这样:

URL ↓ Cache Key ↓ Image

能够稳定复用。


9. 大图不要在 RN 里面 resize

这是一个很重要的架构问题。

❌ 不推荐:

服务器 5000×5000 ↓ RN 下载 ↓ RN decode ↓ RN resize ↓ 显示 300×300

因为最昂贵的事情之一已经发生了:

5000×5000 decode

应该:

原图 ↓ CDN Resize ↓ 300×300 ↓ RN download ↓ decode ↓ 显示

这样能同时减少:

  • 网络流量

  • decode 时间

  • Bitmap 内存

  • GC 压力

  • UI 卡顿


10. 不要 preload 太多大图

例如:

images.forEach(image => { Image.prefetch(image.url); });

如果images有 100 张:

100 × 大图

可能导致:

  • 网络请求暴增

  • 磁盘缓存暴增

  • 内存压力

  • decode 峰值

  • 页面真正显示时反而卡

更合理的是:

当前屏幕 + 前后 1~2 屏

进行有限预加载。


11. 一个比较成熟的架构

如果是 Feed / 电商 / 社交 App,我会建议:

┌──────────────┐ │ Image CDN │ └──────┬───────┘ │ width/height/q │ ▼ ┌──────────────┐ │ expo-image │ └──────┬───────┘ │ ┌─────────┴─────────┐ ▼ ▼ Memory Cache Disk Cache │ │ └─────────┬─────────┘ ▼ FlatList │ Virtualization │ ▼ Visible Items

核心原则就是:

让“需要显示多少像素”决定“下载多少像素”,而不是让原图尺寸决定一切。


12. 如果你的问题是“滚动时卡顿”

我会按这个优先级排查:

① 图片是不是远大于显示尺寸 ↓ ② 是否使用了 CDN resize ↓ ③ 图片 decode 是否占用大量内存 ↓ ④ FlatList 是否一次渲染太多 item ↓ ⑤ Image 是否频繁 mount/unmount ↓ ⑥ cache 是否命中 ↓ ⑦ 是否存在 JS thread 阻塞 ↓ ⑧ 是否存在 UI thread / GPU 压力

不要第一时间就调windowSize/initialNumToRender

很多项目把 FlatList 参数调了一圈,最后发现真正的问题是:

每个 100×100 的头像 实际上下载的是 3000×3000

← 返回列表