You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS端PWA缓存50 MiB限制的应对方法探讨

嘿,这个问题我之前做iOS端PWA的时候实打实踩过坑,Safari对Cache API的50MB限制确实挺棘手的,但只要结合几个策略,不仅能保证缓存满了之后应用依然离线可用,还能避免崩溃。下面是我亲测有效的方案:

1. 先给资源做优先级分层,核心资源锁死缓存

首先得把你的资源拆成两类,精准分配缓存策略:

  • 核心资源:就是离线必须用到的基础内容——比如启动页HTML、核心业务JS、基础样式CSS、底部导航图标这些,一定要把总大小控制在50MB以内(能压缩就压缩,比如图片转WebP、JS/CSS混淆压缩)。
  • 非核心资源:比如非首屏图片、视频、次要页面的静态资源,这些可以缓存在线优先,或者按需加载。

在Service Worker里,用Cache First策略专门守护核心资源,确保它们最先被缓存且不会被轻易清理:

// 安装阶段缓存核心资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('core-cache-v1')
      .then((cache) => cache.addAll([
        '/',
        '/css/core.css',
        '/js/app-core.js',
        '/icons/main-logo.png'
        // 其他核心资源...
      ]))
      .then(() => self.skipWaiting())
  );
});

// 请求阶段优先命中核心缓存
self.addEventListener('fetch', (event) => {
  const requestUrl = new URL(event.request.url);
  // 判断是否为核心资源
  if (['/', '/css/core.css', '/js/app-core.js'].includes(requestUrl.pathname)) {
    event.respondWith(
      caches.match(event.request)
        .then((response) => response || fetch(event.request))
    );
  } else {
    // 非核心资源用Network First,离线时 fallback 到已有缓存
    event.respondWith(
      fetch(event.request)
        .then((networkResponse) => {
          // 有剩余空间再缓存非核心资源
          caches.open('non-core-cache-v1')
            .then((cache) => cache.put(event.request, networkResponse.clone()));
          return networkResponse;
        })
        .catch(() => caches.match(event.request))
    );
  }
});

2. 用IndexedDB存大资源,绕开Cache API限制

Safari的Cache API卡50MB,但IndexedDB的存储限制宽松得多(一般是设备剩余存储空间的50%左右),像视频、大体积图片这类资源,直接存在IndexedDB里,Service Worker拦截请求时从这里读取即可。

举个简单的大图片存储+读取示例:

// 前端页面将大图片存入IndexedDB
async function saveLargeImageToIDB(url) {
  const response = await fetch(url);
  const blob = await response.blob();
  const db = await openDB('large-resources', 1, {
    upgrade(db) {
      db.createObjectStore('images');
    }
  });
  await db.put('images', blob, url);
}

// Service Worker从IndexedDB读取资源
self.addEventListener('fetch', (event) => {
  const requestUrl = new URL(event.request.url);
  if (requestUrl.pathname.includes('/large-media/')) {
    event.respondWith(
      openDB('large-resources', 1)
        .then((db) => db.get('images', requestUrl.href))
        .then((blob) => {
          if (blob) {
            return new Response(blob, { headers: { 'Content-Type': 'image/jpeg' } });
          }
          return fetch(event.request);
        })
    );
  }
});

注意IndexedDB是异步操作,一定要做好异常捕获,避免读取失败导致页面崩溃。

3. 监控缓存空间,主动清理低优先级资源

一定要监听quotaexceeded事件,当缓存满时,主动清理非核心缓存里的旧资源或低优先级资源,保证核心缓存的空间不被占用。

比如按缓存时间排序,删除最早的非核心资源:

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then((response) => {
        return caches.open('non-core-cache-v1')
          .then((cache) => {
            // 尝试缓存,捕获配额不足错误
            return cache.put(event.request, response.clone())
              .catch(async (err) => {
                if (err.name === 'QuotaExceededError') {
                  // 获取所有缓存条目,按时间排序
                  const keys = await cache.keys();
                  keys.sort(async (a, b) => {
                    const aResp = await cache.match(a);
                    const bResp = await cache.match(b);
                    return new Date(aResp.headers.get('date')) - new Date(bResp.headers.get('date'));
                  });
                  // 删除最早的10个条目(可调整数量)
                  for (let i = 0; i < 10; i++) {
                    await cache.delete(keys[i]);
                  }
                  // 再次尝试缓存
                  return cache.put(event.request, response.clone());
                }
                throw err;
              });
          })
          .then(() => response);
      })
  );
});

另外,在Service Worker的activate事件里定期清理旧缓存版本,避免无效缓存占用空间。

4. 兜底降级策略,绝对避免崩溃

如果碰到极端情况,缓存空间完全不够,核心资源都存不下了,一定要在代码里加try-catch捕获所有缓存相关错误,保证Service Worker不会崩溃,同时给用户友好提示:

self.addEventListener('install', (event) => {
  event.waitUntil(
    (async () => {
      try {
        const cache = await caches.open('core-cache-v1');
        await cache.addAll([/* 核心资源列表 */]);
        self.skipWaiting();
      } catch (err) {
        // 缓存失败时记录日志,确保Service Worker依然激活
        console.error('核心资源缓存失败:', err);
        self.skipWaiting();
      }
    })()
  );
});

前端页面也可以检测离线状态和缓存可用性,如果发现离线时核心资源加载失败,显示友好提示:“当前离线功能受限,请尝试连接网络后重新加载”,而不是让页面直接崩溃。


内容的提问来源于stack exchange,提问作者user444002

火山引擎 最新活动