首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

那些被遗忘的码农

  • 24-03-05 03:21
  • 2561
  • 10483
blog.csdn.net

640?wx_fmt=gif

“请@那谁,别找我。”

640?wx_fmt=jpeg

在去年的那些被遗忘的码农……中,小C谈到了多媒体工程师这个非常小众的群体,总结起来就是:人少、事难干、学习投入时间大。当然,还经常背锅,这是程序员的必备技能之一吧。

不过话说回来,凡事都有两面。小众意味着竞争对手少,僧少粥多;“事难干”意味着更大的挑战和机遇,难者不会,会者不难;“学习投入时间长”带来的成果更大,高投入才有高回报。同时,根据思科最新的视觉网络指数显示:

到2022年,视频流量将翻两番。那时,视频将占所有IP流量的82%,高于目前的75%。思科表示,到2022年,将近一半的设备和连接都具备视频功能。

届时,调用视频API、写FFmpeg脚本就像使用公有云服务一样平常。

但不是所有人都适合深入研究多媒体技术,通过研究多媒体技术人的背景不难发现,他们大多数是科班出身,有坚实的基础知识体系,并曾经接受过专业“导师”的指导。当然,不排除你就是那个“万中无一的练武奇才”,那恭喜你,你不在本文的讨论范围内喽。

对于大多数普通人而言,完全没有必要成为多媒体领域的顶尖技术专家,你可以通过适当的学习和实践,掌握一些常用的技巧和工具,让你的工作事半功倍,不要错失多媒体爆发带来的机会。据我所知,LiveVideoStack当仁不让的正在策划一系列课程,关注“livevideostack”微信公众号了解最新的动态。

前文说到,学习多媒体技术需要“导师”领路,为什么呢?因为这里面有太多前人的经验,也许你的创新已经被前辈证实过走不通了。这也就是为什么,多媒体技术人之间的交流格外重要,但也格外不易。LiveVideoStackCon也许是为数不多的开放的、中立的技术交流窗口,而且覆盖了从工业界到学术界的技术专家及行业案例。就拿今年4月的LiveVideoStackCon 2019上海站来说,先来看看专家阵容:

640?wx_fmt=jpeg

640?wx_fmt=png

LiveVideoStackCon上海2019部分讲师

详见http://sh2019.livevideostack.com/speaker

首先,我会推荐一些学术的专家。正如前文所述,学习多媒体技术找到合适的导师是非常关键的,以下这些专家不仅拥有专业的学术成果,还具备工业界的视野。不仅适合初学多媒体的技术人,也适合拥有多年经验的玩家补全知识体系,了解学术界最新动态与成果。

刘杉,腾讯集团副总经理/杰出科学家

虞露,浙江大学教授

王苫社,北京大学信息技术学院研究员

徐异凌,上海交通大学研究员

接下来,会重点推荐适合初学者聆听的演讲,包括“开源技术栈”专场,你可以快速了解最新、最具潜力的多媒体开源技术栈:

《基于Tacotron模型的端到端语音合成开源实践》 马力 喜马拉雅FM 音视频高级工程师

《Firmware(SOF)—— 开源音频DSP 固件解决方案》 揭扬 英特尔 高级音频软件工程师

烟台小樱桃网络科技有限公司 CTO,Freeswitch专家 杜金房

此外,《服务端与架构设计》和《前端与客户端》都是比较综合的话题,如果你理解分布式计算与存储、网络基本原理,或者Android和iOS开发,都是可以get到价值的。

《美摄多平台可扩展的视频技术解决方案》 北京美摄网络科技有限公司 研发中心副总监 李磊

《B站Up主上传质量调优实践》 哔哩哔哩 视频云高级研发经理 唐君行

《基于WebRTC打造商业级的在线课堂应用》 TutorABC 架构师 李炼

《如何构建一套Internet尺度上的弹性SFU/MCU媒体服务器》 英特尔 实时通信解决方案架构师 段先德

接下来推荐的talk至少需要你有5年的多媒体开发经验,少说也需要3年,那就是“视频编解码”和“多媒体传输网络”专场,相信是物超所值的:

《B站的QUIC实践之路》 哔哩哔哩 高级工程师 王盛

Cascade Range Networks, Inc CTO/联合创始人 范醒哲

小鱼易连 视频及AI技术负责人 白刚

《BBR在实时视频传输中的拥塞控制实践》 学霸君 高级技术总监 袁荣喜

《图像与屏幕内容编码技术的研究与实现》 腾讯 音视频实验室视频编解码技术负责人 王诗涛

“画质评价与增强”、“QoE”和“音频技术”直接关系到用户体验以及平台的成本,是做多媒体技术的关键指标,适合3年以上的多媒体从业经验的同学关注:

华为 媒体技术院视频编码优化团队负责人 王豪

《无参考质量评估在视频增强的进展与应用》 腾讯 音视频实验室专家 高孟平

《QoE驱动的音画体验优化及在芒果TV的实践》 芒果TV 音视频研发经理 谭嵩

《AVS2音频国家标准及 WANOS中国全景声技术》 全景声科技 创始人 潘兴德

《车载音频算法调节与主观评价》 前哈曼国际 声学工程师 戴雨潇

《企业协作服务中的音频需求》 思科 资深音频算法工程师 高华

当然,如果你对VR、教育行业和AI感兴趣,以下的talk不容错过:

《虚拟现实与增强现实产品发展与展望》 歌尔研究院 青岛北航研究院技术总监 迟小羽

《智能物联成就智慧城市》 宇视科技研究院 院长 周迪

《教育当中的自然语言处理》 校宝在线 CTO 孙琳

叽里呱啦 联合创始人/CTO 汪钦

好未来教育集团 音视频技术负责人 尹亮

最后,如果你对行业趋势感兴趣,期待抓住下一个技术浪潮,可以关注“技术与商业策略”的三个圆桌论坛:5G、AI及新技术;成本控制;网络新技术。

点击阅读原文,了解LiveVideoStackCon更多信息。特别提示,别忘了使用CSDN渠道邀请码【CSDN】购票享受特别折扣。

640?wx_fmt=gif

CSDN
微信公众号
成就一亿技术人

前端在 WebView 和 H5 环境下的缓存问题,目标是确保用户能够加载到最新版本的资源。这是一个常见且有时棘手的问题,通常需要结合多种策略来解决。

核心问题:为什么会缓存?

缓存本身是提升性能的关键机制。浏览器和 WebView 会缓存静态资源(JS, CSS, 图片等)甚至 HTML 页面,以避免每次都从服务器重新下载,加快页面加载速度,减少网络流量。但问题在于,当你更新了资源后,如果缓存策略不当,用户设备上的缓存副本没有失效,用户就会继续使用旧版本。

缓存可能发生在多个层面:

  1. 浏览器/WebView 自身缓存: 基于 HTTP 缓存头(如 Cache-Control, Expires, ETag, Last-Modified)。
  2. HTTP 代理缓存: (较少见于移动端)中间网络节点可能缓存资源。
  3. CDN 缓存: 如果你使用了内容分发网络 (CDN),CDN 节点会缓存资源。
  4. Service Worker 缓存: 如果你使用了 Service Worker,它会接管缓存控制逻辑。
  5. 原生代码层面的缓存(WebView 特定): Android WebView 和 iOS WKWebView 提供了额外的缓存控制 API。

解决方案策略概览

解决缓存问题的核心思想是:让客户端(浏览器/WebView)知道资源已经更新,需要重新获取。 主要方法包括:

  1. 资源 URL 版本化(最佳实践):
    • 内容哈希 (Content Hashing): 为文件名添加基于文件内容的哈希值(如 app.a1b2c3d4.js)。内容改变则哈希改变,URL 改变,缓存自然失效。这是最推荐的方式。
    • 版本号参数: 在 URL 后附加版本号查询参数(如 app.js?v=1.2.0)。
    • 时间戳参数: 在 URL 后附加时间戳查询参数(如 app.js?t=1678886400)。
  2. 配置 HTTP 缓存头: 通过服务器配置,精确控制资源的缓存行为。
  3. 原生 WebView 配置: 在 App 的原生代码中调整 WebView 的缓存策略或手动清除缓存。
  4. Service Worker: 使用 Service Worker 拦截请求并实现自定义的缓存更新逻辑。
  5. HTML Meta 标签(效果有限): 使用 标签尝试影响缓存,但通常不如 HTTP 头可靠。

详细方案与代码示例


方案一:资源 URL 版本化 (基于构建工具) - 推荐

这是最有效和推荐的方法,因为它利用了缓存的工作原理:不同 URL 的资源被视为不同资源。

原理: 现代前端构建工具(如 Webpack, Vite, Parcel)可以在构建时根据文件内容生成唯一的哈希值,并将其添加到输出的文件名中。例如,main.js 可能会变成 main.a1b2c3d4.js。当文件内容更改时,哈希值也会更改,生成一个新的文件名(如 main.e5f6g7h8.js)。HTML 文件中引用的资源路径也会相应更新。

浏览器或 WebView 请求 main.e5f6g7h8.js 时,由于这是一个全新的 URL,它们会乖乖地从服务器下载新文件。对于未更改的文件(如库文件 vendor.xyz.js),其哈希和 URL 保持不变,可以继续使用缓存。

实现 (以 Webpack 为例):

javascript
代码解读
复制代码
// webpack.config.js (简化示例) const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = (env, argv) => { const isProduction = argv.mode === 'production'; return { mode: isProduction ? 'production' : 'development', entry: './src/index.js', // 你的入口文件 output: { path: path.resolve(__dirname, 'dist'), // [contenthash] 会根据文件内容生成哈希 // 在生产模式下使用哈希,开发模式下不需要,以便热更新 filename: isProduction ? 'js/[name].[contenthash:8].js' : 'js/[name].js', chunkFilename: isProduction ? 'js/[name].[contenthash:8].chunk.js' : 'js/[name].chunk.js', // 公共路径,确保资源能正确加载,特别是当你的应用不是部署在根目录时 // 如果使用 CDN,这里可以配置 CDN 地址 publicPath: '/', // 清理旧的构建产物 clean: true, }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', // 假设你使用了 Babel }, }, { test: /\.css$/, use: [ // 在生产环境提取 CSS 到单独文件,开发环境使用 style-loader isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'postcss-loader', // 假设你使用了 PostCSS ], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', // Webpack 5 内置的资源处理 generator: { // 同样为图片等资源添加哈希 filename: 'images/[name].[contenthash:8][ext]', }, }, // 其他 loader 配置 (e.g., for fonts, etc.) ], }, plugins: [ // 自动生成 HTML 文件,并注入带哈希的 JS 和 CSS 文件链接 new HtmlWebpackPlugin({ template: './public/index.html', // 你的 HTML 模板 filename: 'index.html', inject: 'body', // 'head' 或 'body' 或 true minify: isProduction ? { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, } : false, }), // 仅在生产环境提取 CSS isProduction && new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css', chunkFilename: 'css/[name].[contenthash:8].chunk.css', }), // 其他插件 (e.g., DefinePlugin for environment variables) ].filter(Boolean), // 过滤掉非生产环境下的 MiniCssExtractPlugin optimization: { // 代码分割,将第三方库和公共模块提取出来 splitChunks: { chunks: 'all', name(module, chunks, cacheGroupKey) { // 生成更可预测的 chunk 名称 const moduleFileName = module .identifier() .split('/') .reduceRight((item) => item); const allChunksNames = chunks.map((item) => item.name).join('~'); return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`; }, }, // 为运行时代码(manifest)也创建一个单独的 chunk,防止它频繁变动导致其他 chunk 缓存失效 runtimeChunk: 'single', }, devServer: { static: './dist', hot: true, // 开启热模块替换 historyApiFallback: true, // 对于单页应用需要 }, performance: { hints: isProduction ? 'warning' : false, // 在生产中提示资源过大 }, // 可选:配置 source map devtool: isProduction ? 'source-map' : 'eval-source-map', }; };

实现 (以 Vite 为例):

Vite 默认在生产构建 (vite build) 时就会对 JS, CSS 和静态资源进行哈希命名,通常不需要额外配置。

javascript
代码解读
复制代码
// vite.config.js (基本配置通常足够) import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // 或 vue(), etc. import { visualizer } from 'rollup-plugin-visualizer'; // 可选:分析包大小 export default defineConfig({ plugins: [ react(), visualizer({ open: true }) // 可选 ], build: { // Vite 默认就会添加哈希 // rollupOptions: { // output: { // entryFileNames: `assets/[name].[hash].js`, // chunkFileNames: `assets/[name].[hash].js`, // assetFileNames: `assets/[name].[hash].[ext]` // } // } sourcemap: true, // 生产环境生成 source map }, // 配置开发服务器等 server: { port: 3000, open: true, } });

优点:

  • 非常可靠,利用了基础的缓存机制。
  • 可以对静态资源设置非常长的缓存时间(如一年),因为内容不变 URL 就不变。
  • 构建工具自动处理,开发体验好。

缺点:

  • 每次构建都会生成新的文件名,需要确保服务器能正确提供这些文件。
  • 主 HTML 文件本身不能使用这种方法进行强缓存(否则无法获取到引用新资源路径的 HTML),通常需要结合 HTTP 头来控制 HTML 的缓存。

方案二:配置 HTTP 缓存头

控制 HTTP 缓存头是服务器端的任务,它告诉浏览器/WebView 如何缓存资源。

关键 HTTP 头:

  • Cache-Control (HTTP/1.1, 首选):

    • public: 表明响应可以被任何中间缓存(如 CDN, 代理)缓存。
    • private: 响应只能被最终用户的浏览器缓存,不能被中间缓存。
    • no-cache: 强制客户端在每次使用缓存副本前,必须向服务器发送请求验证资源是否过期(使用 ETag 或 Last-Modified)。如果资源未改变,服务器返回 304 Not Modified,客户端使用缓存;如果已改变,服务器返回 200 OK 和新资源。注意:不是完全禁止缓存,而是需要验证。
    • no-store: 完全禁止浏览器和任何中间缓存存储任何版本的响应。每次请求都必须从服务器完整下载。非常消耗性能,谨慎使用。
    • max-age=: 指定资源被视为新鲜的最长时间(秒)。例如 max-age=3600 表示缓存 1 小时。
    • s-maxage=: 类似于 max-age,但仅适用于共享缓存(如 CDN)。优先级高于 max-age。
    • must-revalidate: 一旦资源过期(max-age 或 Expires),缓存必须到源服务器验证,不能直接使用陈旧副本(即使在网络断开等特殊情况下)。
    • proxy-revalidate: 类似于 must-revalidate,但仅适用于共享缓存。
    • immutable: (较新) 表明响应正文在新鲜期内不会改变。这可以告诉浏览器,只要资源未过期,就不需要因为用户刷新页面而去发起条件请求(If-None-Match, If-Modified-Since)进行验证。非常适合与内容哈希文件名结合使用。
  • Pragma (HTTP/1.0):

    • Pragma: no-cache: 效果类似于 Cache-Control: no-cache,主要用于兼容旧的 HTTP/1.0 客户端。如果 Cache-Control 存在,Pragma 通常会被忽略。
  • Expires (HTTP/1.0):

    • Expires: : 指定资源过期的绝对日期/时间。优先级低于 Cache-Control: max-age。由于依赖客户端和服务器时钟同步,不如 max-age 精确。
  • ETag (Entity Tag):

    • 服务器为资源生成的唯一标识符(通常是文件内容的哈希或版本号)。客户端缓存资源时会存储 ETag。下次请求时,通过 If-None-Match 请求头将 ETag 发回服务器。如果服务器上的资源 ETag 没变,返回 304 Not Modified;否则返回 200 OK 和新资源及新的 ETag。
  • Last-Modified:

    • 服务器提供的资源最后修改时间。客户端下次请求时通过 If-Modified-Since 请求头发送此时间。服务器比较时间,如果未修改,返回 304;否则返回 200 和新资源及新的 Last-Modified 时间。ETag 通常更精确。

推荐策略:

  1. HTML 文件 (例如 index.html):

    • 不要强缓存! 因为 HTML 文件是入口,它包含了对其他带哈希资源的引用。如果 HTML 被强缓存,用户将永远无法获取到引用新版 JS/CSS 的 HTML。
    • 使用 Cache-Control: no-cache。这确保浏览器每次都会向服务器验证 HTML 文件是否有更新。如果服务器检测到文件未变(基于 ETag 或 Last-Modified),会返回 304 Not Modified,浏览器使用本地缓存,这仍然比完全下载快。
    • 或者,使用 Cache-Control: max-age=0, must-revalidate 达到类似效果。
  2. 带内容哈希的静态资源 (JS, CSS, Images, Fonts 等):

    • 强缓存! 因为它们的文件名已经包含了版本信息,内容不变 URL 就不变。内容改变了,URL 就会改变,自然不会命中旧缓存。
    • 设置 Cache-Control: public, max-age=31536000, immutable。
      • public: 允许 CDN 等中间缓存。
      • max-age=31536000: 缓存一年(365 * 24 * 60 * 60 秒)。
      • immutable: 告诉浏览器这个资源在有效期内绝不会改变,避免不必要的验证请求(即使是用户手动刷新页面)。

实现 (以 Nginx 为例):

nginx
代码解读
复制代码
# /etc/nginx/nginx.conf or sites-available/your-site.conf server { listen 80; server_name yourdomain.com; root /path/to/your/frontend/dist; # 指向构建产物目录 # 默认入口文件 index index.html index.htm; location / { try_files $uri $uri/ /index.html; # 支持 SPA 路由 } # 对 HTML 文件设置 no-cache location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; # 更激进的禁止缓存HTML # 或者使用 ETag/Last-Modified 验证 # add_header Cache-Control "no-cache"; # expires -1; # 另一种设置不缓存的方式 (等同于 Cache-Control: no-cache) # etag on; # 确保 Nginx 生成 ETag } # 对带哈希的静态资源设置长期缓存 # 匹配 /js/, /css/, /images/, /fonts/ 等目录下的文件 # 正则表达式匹配文件名中包含点+至少8位字母数字哈希值+点+扩展名 location ~* \.(?:[a-z0-9]{8,})\.(?:js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$ { # Nginx 会自动添加 Last-Modified,ETag 通常也建议开启 (默认可能已开启) etag on; add_header Cache-Control "public, max-age=31536000, immutable"; # expires 1y; # 也可以用 expires access_log off; # 可选:关闭这些静态文件的访问日志 log_not_found off; # 可选:关闭未找到这些文件的日志 } # 对其他普通静态资源(可能没有哈希,如 favicon.ico)设置较短缓存或验证 location ~* \.(?:ico|json|xml|txt)$ { add_header Cache-Control "public, max-age=86400"; # 例如缓存一天 etag on; } # 其他配置(如 Gzip, HTTPS 等) gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; # ... more gzip settings # 如果是 HTTPS # listen 443 ssl http2; # server_name yourdomain.com; # ssl_certificate /path/to/fullchain.pem; # ssl_certificate_key /path/to/privkey.pem; # include /path/to/options-ssl-nginx.conf; # ssl_dhparam /path/to/ssl-dhparams.pem; }

实现 (以 Node.js/Express 为例):

javascript
代码解读
复制代码
const express = require('express'); const path = require('path'); const serveStatic = require('serve-static'); // Express 内置或使用此库 const app = express(); const publicPath = path.join(__dirname, 'dist'); // 指向构建产物目录 // 中间件:为 HTML 设置 no-cache app.use((req, res, next) => { if (req.path === '/' || req.path.endsWith('.html')) { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // res.setHeader('Pragma', 'no-cache'); // For HTTP/1.0 compatibility // res.setHeader('Expires', '0'); // For proxies } next(); }); // 使用 serve-static 或 express.static 提供静态文件服务 // serve-static 允许更精细的缓存控制 const staticServe = serveStatic(publicPath, { // 对所有文件默认设置 ETag (serve-static 默认开启) etag: true, // 对所有文件默认设置 Last-Modified (serve-static 默认开启) lastModified: true, // 设置默认缓存控制,但会被特定规则覆盖 // index: false, // 防止 serve-static 自动服务 index.html (如果上面已处理) // 针对特定文件类型设置更长的缓存 setHeaders: (res, filePath) => { const filename = path.basename(filePath); // 正则匹配带哈希的文件名 (可能需要根据你的哈希长度调整 {8,}) if (/\.[a-f0-9]{8,}\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/i.test(filename)) { res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); } else if (filename === 'index.html') { // 确保 index.html 的 no-cache (虽然上面中间件已处理,双重保险) res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); } else { // 其他文件可以设置一个较短的缓存或默认值 // res.setHeader('Cache-Control', 'public, max-age=3600'); // 例如缓存1小时 } } }); app.use(staticServe); // 对于 SPA,确保刷新页面时能正确返回 index.html app.get('*', (req, res) => { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); // 确保这个回退的 HTML 也不缓存 res.sendFile(path.join(publicPath, 'index.html')); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); });

优点:

  • 补充了 URL 版本化策略,特别是控制了 HTML 文件的缓存。
  • 服务器端控制,对所有客户端(包括旧版浏览器和 WebView)都有效。

缺点:

  • 需要服务器配置权限。
  • 配置可能因服务器软件(Nginx, Apache, Node.js, Caddy 等)而异。
  • 如果资源 URL 不变,仅靠 no-cache 依赖客户端和服务端的验证机制,虽然比完全下载快,但仍有网络请求。

方案三:原生 WebView 配置与缓存清理

当你的 H5 页面主要运行在 App 内的 WebView 中时,可以在原生代码层面进行一些控制。

Android (Kotlin / Java):

kotlin
代码解读
复制代码
// Kotlin 示例 import android.webkit.WebView import android.webkit.WebSettings import android.webkit.WebViewClient // 在你的 Activity 或 Fragment 中找到 WebView 实例 val webView: WebView = findViewById(R.id.your_webview_id) // 获取 WebSettings val settings: WebSettings = webView.settings // 1. 配置缓存模式 (通常保持默认或根据需要调整) // settings.cacheMode = WebSettings.LOAD_DEFAULT // 默认行为,根据 HTTP 头决定是否缓存 settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK // 优先加载缓存,过期或不存在则网络加载 // settings.cacheMode = WebSettings.LOAD_NO_CACHE // 完全不使用缓存,每次都网络加载 (影响性能,调试用) // settings.cacheMode = WebSettings.LOAD_CACHE_ONLY // 只加载缓存,不进行网络请求 (离线模式) // 确保启用 JavaScript (通常都需要) settings.javaScriptEnabled = true // (可选) 启用 DOM Storage API (localStorage, sessionStorage) settings.domStorageEnabled = true // (可选) 启用数据库存储 API settings.databaseEnabled = true val databasePath = applicationContext.getDir("webviewdatabase", Context.MODE_PRIVATE).path settings.databasePath = databasePath // 已废弃,但某些旧版本可能需要 // (可选) 启用 Application Caches API (已废弃,但可能兼容旧网页) settings.setAppCacheEnabled(true); val appCachePath = applicationContext.getDir("webviewcache", Context.MODE_PRIVATE).path settings.setAppCachePath(appCachePath) settings.allowFileAccess = true // AppCache 需要文件访问权限 // 2. 手动清除缓存 (在适当的时候调用,例如 App 更新后首次打开,或提供给用户的清理选项) fun clearWebViewCache(webView: WebView) { // 清除资源缓存 (图片、JS、CSS 等) webView.clearCache(true) // 参数 'true' 表示也清除磁盘缓存 // 清除 Cookie (如果需要) // android.webkit.CookieManager.getInstance().removeAllCookies(null) // android.webkit.CookieManager.getInstance().flush() // 清除 DOM Storage (localStorage, sessionStorage) - 需要在 JS 侧执行 // webView.evaluateJavascript("localStorage.clear(); sessionStorage.clear();", null) // 清除 Web SQL Database (如果使用了) // android.webkit.WebStorage.getInstance().deleteAllData() // 注意:会清除所有 WebView 的 storage // 清除 Application Cache (如果使用了) // 需要配合 WebChromeClient 的 onReachedMaxAppCacheSize 等方法管理 // 更彻底的方式:清理 WebView 使用的数据目录 (可能影响所有 WebView 实例) // webView.clearFormData() // 清除表单数据 // webView.clearHistory() // 清除历史记录 // webView.clearSslPreferences() // 清除 SSL 配置 // 最彻底:删除 WebView 的缓存和数据文件 (谨慎使用!) // try { // val webViewCacheDir = File(applicationContext.cacheDir, "org.chromium.android_webview") // if (webViewCacheDir.exists()) { // deleteRecursive(webViewCacheDir) // } // val webViewDataDir = File(applicationContext.dataDir, "app_webview") // if (webViewDataDir.exists()) { // deleteRecursive(webViewDataDir) // } // } catch (e: Exception) { // Log.e("WebViewCache", "Error clearing webview data", e) // } Log.d("WebViewCache", "WebView cache cleared.") } // 递归删除文件/目录的辅助函数 // fun deleteRecursive(fileOrDirectory: File) { ... } // 设置 WebViewClient 以在 WebView 内加载 URL webView.webViewClient = WebViewClient() // 加载你的 H5 页面 webView.loadUrl("https://yourdomain.com/yourpage") // 在 App 更新后首次启动时调用清理缓存 // val prefs = getSharedPreferences("MyAppPrefs", MODE_PRIVATE) // val lastAppVersion = prefs.getInt("lastAppVersion", 0) // val currentAppVersion = BuildConfig.VERSION_CODE // if (currentAppVersion > lastAppVersion) { // clearWebViewCache(webView) // prefs.edit().putInt("lastAppVersion", currentAppVersion).apply() // }

iOS (Swift / Objective-C):

swift
代码解读
复制代码
// Swift 示例 import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { var webView: WKWebView! override func loadView() { let webConfiguration = WKWebViewConfiguration() // 1. 配置数据存储 (影响缓存、Cookie、LocalStorage 等) // 默认使用 WKWebsiteDataStore.default() - 持久化存储 // webConfiguration.websiteDataStore = WKWebsiteDataStore.default() // 使用非持久化存储 (类似浏览器的隐私模式,关闭 WebView 后数据丢失) // webConfiguration.websiteDataStore = WKWebsiteDataStore.nonPersistent() webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = self view = webView } override func viewDidLoad() { super.viewDidLoad() // 创建 URLRequest guard let myURL = URL(string: "https://yourdomain.com/yourpage") else { return } var request = URLRequest(url: myURL) // 2. 配置请求的缓存策略 (影响本次加载) // request.cachePolicy = .useProtocolCachePolicy // 默认,遵循 HTTP 缓存头 request.cachePolicy = .reloadIgnoringLocalCacheData // 忽略本地缓存,直接从源加载 (类似 Cmd/Ctrl+Shift+R) // request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData // 忽略本地和代理缓存 // request.cachePolicy = .returnCacheDataElseLoad // 优先用缓存,无论是否过期,没有才加载 // request.cachePolicy = .returnCacheDataDontLoad // 只用缓存,绝不加载 (离线) webView.load(request) // 3. 手动清除缓存 (在适当的时候调用) // clearWebViewCache() } func clearWebViewCache() { // 定义要清除的数据类型 // let websiteDataTypes = WKWebsiteDataStore.allWebsiteDataTypes() // 清除所有类型 let websiteDataTypes: Set<String> = [ WKWebsiteDataTypeDiskCache, // 磁盘缓存 (资源文件) WKWebsiteDataTypeMemoryCache, // 内存缓存 (可能效果不明显) // WKWebsiteDataTypeCookies, // Cookies // WKWebsiteDataTypeLocalStorage, // LocalStorage // WKWebsiteDataTypeSessionStorage, // SessionStorage (通常随会话结束) // WKWebsiteDataTypeWebSQLDatabases, // Web SQL Databases // WKWebsiteDataTypeIndexedDBDatabases, // IndexedDB // WKWebsiteDataTypeOfflineWebApplicationCache, // Application Cache // ... 其他类型 ] // 定义要清除数据的时间点 (Date(timeIntervalSince1970: 0) 表示清除所有时间的) let dateFrom = Date(timeIntervalSince1970: 0) // 获取默认的 Data Store (如果用了非持久化的,需要获取对应的 store) let dataStore = WKWebsiteDataStore.default() // 执行清除操作 dataStore.removeData(ofTypes: websiteDataTypes, modifiedSince: dateFrom) { print("WKWebView cache cleared for types: \(websiteDataTypes)") // 清除完成后的操作,例如重新加载页面 // self.webView.reload() // 如果需要强制从服务器重新加载 (忽略可能残余的内存缓存) self.webView.reloadFromOrigin() } } // 示例:App 更新后首次启动时清除缓存 func clearCacheOnAppUpdate() { let defaults = UserDefaults.standard let lastAppVersion = defaults.string(forKey: "lastAppVersion") let currentAppVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" if lastAppVersion != currentAppVersion { clearWebViewCache() defaults.set(currentAppVersion, forKey: "lastAppVersion") } } // MARK: - WKNavigationDelegate (可选) func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { print("Webview finished loading") // 可以在这里执行 JS 来清理 localStorage 等 // webView.evaluateJavaScript("localStorage.clear();") { (result, error) in // if let error = error { print("Error clearing localStorage: \(error)") } // } } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { print("Webview failed navigation: \(error)") } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { print("Webview failed provisional navigation: \(error)") } }

优点:

  • 提供了独立于 Web 服务器的缓存控制手段。
  • 可以在 App 更新、用户主动操作等特定时机强制清理缓存。

缺点:

  • 仅适用于 WebView 环境,对普通浏览器无效。
  • 过度清除缓存会影响性能和用户体验(加载变慢)。
  • 清除 localStorage 等需要额外处理(可能通过 JS 注入)。
  • 不同平台(Android/iOS)API 不同,需要分别实现。

方案四:Service Worker

Service Worker (SW) 是一个运行在浏览器背景中的 JavaScript 脚本,它可以拦截和处理网络请求,实现复杂的缓存策略、离线支持、推送通知等。

原理: 你可以编写一个 SW,当浏览器请求资源时:

  1. 拦截请求: SW 的 fetch 事件处理器会捕获这个请求。
  2. 决策缓存策略: SW 可以决定是:
    • Cache First: 优先从 SW 管理的缓存(Cache API)中查找资源,找不到再去网络请求,并将结果缓存起来。
    • Network First: 优先尝试从网络获取资源,成功则返回并更新缓存。如果网络失败,则从缓存中返回旧版本。
    • Stale-While-Revalidate: 同时从缓存和网络请求资源。如果缓存命中,立即返回缓存版本(速度快)。然后等待网络请求完成,如果网络返回了新版本,则更新缓存供下次使用。
    • Cache Only: 只从缓存获取。
    • Network Only: 只从网络获取(绕过缓存)。
  3. 管理缓存更新: 当部署新版本的 Web 应用时,新的 SW 文件会被下载。在 install 事件中,新的 SW 可以预缓存新版本的资源。在 activate 事件中,它可以清理旧版本的缓存。浏览器会在适当的时候(通常是所有引用旧 SW 的页面关闭后)切换到新的 SW。

实现 (简化示例):

javascript
代码解读
复制代码
// service-worker.js (或 sw.js) const CACHE_NAME = 'my-app-cache-v2'; // <-- 更改版本号以触发更新 const urlsToCache = [ '/', // 通常缓存 App Shell (主 HTML) '/index.html', // '/css/style.somehash.css', // 带哈希的资源通常不需要 SW 缓存,让浏览器 HTTP 缓存处理 // '/js/app.somehash.js', '/images/logo.png', '/offline.html' // 一个离线时展示的页面 ]; // 安装 Service Worker 时,预缓存资源 self.addEventListener('install', event => { console.log('SW: Install event'); // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(cache => { console.log('SW: Opened cache', CACHE_NAME); // 使用 addAll 原子性地添加资源,任何一个失败则整个操作失败 // 注意:如果 V1 版本的缓存还在,直接 addAll 新资源到 V2 缓存 return cache.addAll(urlsToCache); }) .then(() => { console.log('SW: Resources cached'); // 强制新 SW 安装后立即激活 (跳过等待) // 谨慎使用:可能中断正在使用旧缓存的页面 return self.skipWaiting(); }) .catch(error => { console.error('SW: Cache addAll failed:', error); }) ); }); // 激活 Service Worker 时,清理旧缓存 self.addEventListener('activate', event => { console.log('SW: Activate event'); const cacheWhitelist = [CACHE_NAME]; // 只保留当前版本的缓存 event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('SW: Deleting old cache:', cacheName); return caches.delete(cacheName); // 删除旧缓存 } }) ); }).then(() => { console.log('SW: Clients claimed'); // 让当前激活的 SW 控制所有打开的客户端页面 return self.clients.claim(); }) ); }); // 拦截网络请求 self.addEventListener('fetch', event => { console.log('SW: Fetch event for ->', event.request.url); // 示例:Stale-While-Revalidate 策略 (对非哈希资源或 HTML 较好) // 对于带哈希的资源,可能直接 NetworkOnly 或让浏览器缓存处理更好 if (event.request.mode === 'navigate' || (event.request.url.includes('/api/') === false)) { // 只处理导航请求或非 API 请求 event.respondWith( caches.open(CACHE_NAME).then(cache => { return cache.match(event.request).then(cachedResponse => { const fetchPromise = fetch(event.request).then(networkResponse => { // 如果请求成功,克隆一份放入缓存 if (networkResponse.ok) { cache.put(event.request, networkResponse.clone()); } return networkResponse; }).catch(error => { console.warn('SW: Network request failed, returning offline page possibly.', error); // 如果网络失败,可以返回一个离线页面 return caches.match('/offline.html'); }); // 优先返回缓存 (Stale),同时发起网络请求 (revalidate) return cachedResponse || fetchPromise; }); }) ); } else { // 对于 API 请求或其他不想缓存的,直接走网络 event.respondWith(fetch(event.request)); } // // 示例:Cache First 策略 // event.respondWith( // caches.match(event.request) // .then(response => { // // Cache hit - return response // if (response) { // console.log('SW: Serving from cache:', event.request.url); // return response; // } // // // Cache miss - go to network // console.log('SW: Serving from network:', event.request.url); // return fetch(event.request).then( // networkResponse => { // // Optional: Cache the new response // if (networkResponse.ok && event.request.method === 'GET') { // 只缓存成功的 GET 请求 // const responseToCache = networkResponse.clone(); // caches.open(CACHE_NAME) // .then(cache => { // cache.put(event.request, responseToCache); // }); // } // return networkResponse; // } // ).catch(error => { // console.error('SW: Fetch failed:', error); // // Optional: Return an offline page or fallback response // return caches.match('/offline.html'); // }); // }) // ); }); // 你可能还需要添加 'message', 'push', 'sync' 等事件监听器

注册 Service Worker (在你的主 JS 文件中):

javascript
代码解读
复制代码
// main.js or index.js if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/service-worker.js') // SW 文件相对于域名的路径 .then(registration => { console.log('SW registered: ', registration); // 可选:监听 SW 更新 registration.addEventListener('updatefound', () => { const newWorker = registration.installing; console.log('SW update found, new worker installing:', newWorker); newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // 新 SW 已安装,但旧的仍在控制页面 // 可以提示用户刷新页面以应用更新 console.log('New SW installed, waiting for activation. Refresh needed.'); // showUpdateUI(); // 显示一个更新提示给用户 } else { // 这是首次注册 SW console.log('SW installed for the first time.'); } } }); }); }).catch(registrationError => { console.log('SW registration failed: ', registrationError); }); }); // 可选:监听 controller change 事件,当新 SW 激活并开始控制页面时触发 navigator.serviceWorker.addEventListener('controllerchange', () => { console.log('SW controller changed, new worker activated. Reloading...'); // 自动刷新页面以使用新缓存 (如果合适) // window.location.reload(); }); }

优点:

  • 提供最精细的缓存控制和离线体验。
  • 可以实现复杂的缓存策略。
  • 独立于 HTTP 缓存头。

缺点:

  • 实现复杂,需要仔细处理 SW 的生命周期、更新和错误。
  • 兼容性问题:虽然主流浏览器和 WebView 支持良好,但仍需考虑旧版本。
  • 调试困难:SW 在后台运行,状态管理和调试比普通 JS 复杂。
  • HTTPS 要求:Service Worker 只能在 HTTPS 域(或 localhost)下注册和运行。
  • 可能增加首次加载时间(因为需要先下载和安装 SW)。

方案五:HTML Meta 标签 (效果有限,不推荐作为主要手段)

可以在 HTML 的 中添加 标签尝试阻止缓存:

html
代码解读
复制代码
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Expires" content="0">

原理: 理论上,这些标签指示浏览器不要缓存该 HTML 页面。

缺点:

  • 不可靠: 很多现代浏览器和代理缓存会忽略这些 meta 标签,优先遵循 HTTP 头。它们主要对非常旧的浏览器或特定场景(如从本地文件系统打开 HTML)可能有效。
  • 仅影响 HTML 文件本身: 不会影响外部引用的 JS, CSS, 图片等资源。
  • 性能差: 如果真的生效,会导致 HTML 每次都重新下载。

结论:不应依赖此方法来解决缓存问题,优先使用 HTTP 头。


综合策略与建议

通常,最佳实践是组合使用多种策略:

  1. 首选:文件名哈希 + HTTP 强缓存头。

    • 使用构建工具(Webpack/Vite)为所有 JS, CSS, 图片等静态资源生成带内容哈希的文件名 ([name].[contenthash].ext)。
    • 配置 Web 服务器(Nginx/Apache/Node)为这些带哈希的资源设置长期缓存的 HTTP 头 (Cache-Control: public, max-age=31536000, immutable)。
    • 配置 Web 服务器为 HTML 入口文件 (index.html) 设置禁止缓存或需要验证的 HTTP 头 (Cache-Control: no-cache 或 max-age=0, must-revalidate)。
  2. WebView 环境下的补充:

    • 如果发现 WebView 仍然存在顽固的缓存问题(有时 WebView 实现可能不完全遵循 HTTP 头),可以在原生 App 代码中,在适当的时机(如 App 版本更新后首次加载 H5)调用手动清除 WebView 缓存的 API(Android 的 webView.clearCache(true),iOS 的 WKWebsiteDataStore.default().removeData(...))。
    • 避免过度使用 WebSettings.LOAD_NO_CACHE 或 URLRequest.CachePolicy.reloadIgnoringLocalCacheData,因为它们会严重影响性能。仅在调试或特定必要场景下使用。
  3. 高级选项:Service Worker

    • 如果需要实现复杂的离线支持、更激进的缓存更新策略(如 Stale-While-Revalidate)或对缓存有极致的控制需求,可以引入 Service Worker。但要认识到其复杂性。Service Worker 可以与文件名哈希和 HTTP 头策略共存(例如,SW 可以优先服务 App Shell 和关键资源,对于带哈希的资源则让浏览器 HTTP 缓存处理)。
  4. 避免 Query String Cache Busting:

    • 虽然 app.js?v=1.2 或 app.js?t=timestamp 看起来简单,但很多代理和 CDN 可能默认配置为忽略查询参数进行缓存,导致这种方法失效。文件名哈希更可靠。如果非要用,确保你的 CDN 配置能识别查询参数作为缓存键的一部分。
  5. 测试!

    • 在不同环境(浏览器、Android WebView、iOS WKWebView)下彻底测试你的缓存策略。
    • 使用浏览器开发者工具的 "Network" 标签页检查资源的 HTTP 状态码(200 OK 表示从网络下载,304 Not Modified 表示验证后使用缓存,(from disk cache) 或 (from memory cache) 表示直接使用缓存)和 Cache-Control 等响应头。
    • 模拟 App 更新流程,检查 WebView 是否能正确加载新资源。

总结

解决前端缓存问题没有银弹,但通过结合使用文件名哈希和正确的 HTTP 缓存头配置,可以解决绝大多数场景下的问题,这也是目前业界标准的最佳实践。对于 WebView 环境,原生代码提供的缓存清理 API 是一个有用的补充工具。Service Worker 则为需要更高级功能的场景提供了强大的(但复杂的)解决方案。

注:本文转载自blog.csdn.net的CSDN资讯的文章"https://blog.csdn.net/csdnnews/article/details/87835359"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top