WiFi调试实战指南从零开始搞定Android真机无线调试
「WiFi调试时页面白屏,连 console 都不打」
今天上线前最后一步:用手机连公司 WiFi 调试 H5 页面,结果点开就白屏——连 console.log('init') 都没跑。不是 404,不是报错,就是静默挂掉。我第一反应是「又来了」,这已经是今年第三次在 WiFi 调试环节翻车了,而且每次原因都不一样。
先说结论:这次是 HTTPS 混合内容(mixed content)被浏览器主动拦截 + service worker 缓存污染双重叠加,但排查过程属实离谱——我花了一个半小时,从重装 Chrome、换手机、关防火墙、抓包到翻旧 commit,全试了一遍。
踩坑提醒:如果你的项目用了 PWA 或自建 SW,又恰好在局域网 WiFi 下调试,那这个组合拳大概率会打你一脸。
「为什么只在 WiFi 下崩,4G/5G 好好的?」
因为公司内网 WiFi 有统一代理和 HTTPS 中间人证书(MITM),所有流量会被重签。而我们开发环境 API 是 https://jztheme.com/api,前端 JS 里写的也是 https 地址。但代理一插手,浏览器发现证书链不合法(不是 Let’s Encrypt 签的,是内部 CA),就直接干掉了整个页面资源加载——包括 HTML、JS、CSS,连 DOM 都没构建完。
更骚的是:我本地 dev server 用的是 http://localhost:3000,手机扫二维码访问的是 http://192.168.1.100:3000,按理说不该走 HTTPS 拦截。但……我们线上 build 后的 index.html 里,硬编码了 ,而这个静态资源路径被 Nginx 重写规则悄悄转发到了 https://jztheme.com/static/... ——没错,就是那个被 MITM 的域名。
后来试了下发现:只要把 index.html 里的 script 标签改成相对路径(./static/js/main.xxxx.js)或绝对 HTTP 路径(http://192.168.1.100:3000/static/...),页面就能正常加载。但问题没完——JS 加载了,API 又 502。因为 fetch 请求发往 https://jztheme.com/api,同样被中间人证书拦住,且浏览器连 Network 面板都不显示这个请求(静默失败)。
「Service Worker 还在火上浇油」
这里我踩了个坑:以为关掉 DevTools 的「Disable cache」就万事大吉。结果忘了 SW 已经缓存了上次成功加载的 index.html(里面还是 https 资源链接),导致即使我改了代码,手机上 reload 的还是旧 HTML。折腾了半天发现,清缓存根本没清 SW 的 cache ——得手动 unregister + 强制刷新两次才生效。
顺手贴个我现在的 SW 调试脚本(dev-only):
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// 开发环境强制 skipWaiting & unregister
if (location.hostname === 'localhost' || location.hostname.includes('192.168')) {
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.unregister());
});
// 并且阻止注册
return;
}
navigator.serviceWorker.register('/sw.js').catch(console.error);
});
}
注意:别在生产环境用这段逻辑。这只是为了 WiFi 调试时避免缓存污染加的临时补丁。
「最终解法:三步走,不优雅但亲测有效」
第一步:开发环境下自动切换 API 和静态资源协议
// utils/env.js
export const getApiUrl = () => {
const host = window.location.hostname;
if (host === 'localhost' || host.startsWith('192.168.') || host.startsWith('10.')) {
return 'http://192.168.1.100:3000/api'; // 对应你的本地后端地址
}
return 'https://jztheme.com/api';
};
// webpack.config.js 或 vite.config.ts 里也做类似判断
// 让 public/index.html 中的 script src 动态生成
第二步:HTML 模板里用环境变量注入资源路径(我用的是 Vite,配了 define):
<!-- public/index.html -->
<script type="module" src="%BASE_URL%static/js/main.%VITE_HASH%.js"></script>
并在 vite.config.ts 里加:
define: {
'process.env.NODE_ENV': JSON.stringify(mode),
'%BASE_URL%': mode === 'development'
? 'http://192.168.1.100:3000/'
: '/',
'%VITE_HASH%': 'xxxxxx'
}
第三步:WiFi 调试时,手机浏览器打开 chrome://flags/#unsafely-treat-insecure-origin-as-secure,把你的局域网地址(比如 http://192.168.1.100:3000)加进去,再勾选 Strict site isolation 下面的 Allow running insecure content ——虽然不推荐长期开着,但临时调试真香。
顺便提一句:iOS Safari 更狠,它压根不认这个 flag,所以 iOS 调试必须靠 Charles 抓包 + SSL Proxying 手动信任证书,或者干脆用 http-server -p 8080 -c-1 启一个无缓存 HTTP 服务,绕过所有 HTTPS 相关限制。
「还有两个小尾巴没完美解决」
一是 WebSocket 连接,在 WiFi 下偶尔会卡在 CONNECTING 状态几秒,查了下是代理对 ws 协议支持不稳定,目前妥协方案是加个 3s 超时 fallback 到轮询;二是某些安卓老机型(比如三星 S8)在 WiFi 下首次加载 JS 会偶发解析失败,加了 crossorigin="anonymous" 属性后缓解,但没根治——暂时归为「能用就行」范畴。
以上是我踩坑后的总结,希望对你有帮助。如果你有更好的方案(比如用 mDNS 做局域网服务发现、或者用 WebRTC DataChannel 绕过代理),欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。
