Android WebView如何拦截并修改WebSocket请求头?
在Android WebView中发起WebSocket请求时,需要在请求发送前修改其请求头。尝试使用自定义WebViewClient的shouldInterceptRequest方法,但该方法仅能拦截HTTP请求,无法拦截WebSocket请求——通过request.getUrl().toString()记录请求URL,发现WebSocket请求均未被拦截。
现需解决:
- Android WebView中是否存在拦截并修改WebSocket请求头的方法?
- 若不存在,是否有替代框架或方案可实现该需求?
注:曾在Electron JS中通过session.defaultSession.webRequest.onBeforeSendHeaders成功修改WebSocket请求头,需适配Android的类似解决方案。
相关代码:
class MainActivity extends AppCompatActivity { private WebView webView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = findViewById(R.id.webview); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new MyWebViewClient()); webView.loadUrl("https://panel.autotcrypto.com"); } private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return false; } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { String url = request.getUrl().toString(); if (url.contains("ws")) { // 尝试修改请求头的代码(对WebSocket无效) } return super.shouldInterceptRequest(view, request); } } }
一、原生WebView的限制说明
Android系统原生WebView的shouldInterceptRequest方法仅针对HTTP/HTTPS请求生效,WebSocket属于独立的协议通道,不走HTTP拦截流程,因此原生WebView没有直接拦截并修改WebSocket请求头的API。
二、可行替代方案
方案1:JS注入替换WebSocket(适用于页面可控场景)
如果WebView加载的页面是你方可控的,可以通过注入JavaScript重写页面的WebSocket构造函数,在握手阶段添加允许的自定义请求头(浏览器对WebSocket自定义头有严格限制,仅支持部分标准头或Sec-WebSocket-*前缀的头)。
步骤示例:
- 在WebView页面加载完成后注入JS:
webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 注入重写WebSocket的JS代码 view.evaluateJavascript( "(function() {" + "const OriginalWebSocket = window.WebSocket;" + "window.WebSocket = function(url, protocols) {" + // 构造时可尝试添加自定义头(需符合浏览器规则) "const ws = new OriginalWebSocket(url, protocols);" + // 若需传递自定义信息,也可通过URL参数间接实现(服务端配合解析) "return ws;" + "};" + "})();", null ); } });
注意:浏览器禁止WebSocket握手请求携带任意自定义头,仅允许如
Authorization这类部分标准头,或与WebSocket相关的Sec-WebSocket-*头,具体需参考浏览器规范。
方案2:Native接管WebSocket请求(完全可控)
通过OkHttp实现自定义WebSocket客户端,配合WebView的JS交互,完全接管页面的WebSocket请求,实现任意修改请求头的需求。
核心思路:
- 注入JS替换页面的
WebSocket对象,让页面通过JS调用Native的WebSocket客户端; - Native用OkHttp创建WebSocket连接,在请求拦截器中修改请求头;
- 建立连接后,将WebSocket的消息、状态变化通过JS回调同步给页面。
代码示例:
public class MainActivity extends AppCompatActivity { private WebView webView; private WebSocket webSocket; private OkHttpClient okHttpClient; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = findViewById(R.id.webview); WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); // 添加JS交互接口 webView.addJavascriptInterface(new WebSocketJsBridge(), "androidWebSocket"); // 初始化OkHttpClient,添加请求头拦截器 okHttpClient = new OkHttpClient.Builder() .addInterceptor(chain -> { Request originalRequest = chain.request(); // 修改WebSocket握手请求头 Request modifiedRequest = originalRequest.newBuilder() .addHeader("Custom-Header", "custom-value") .addHeader("Authorization", "Bearer your-token") .build(); return chain.proceed(modifiedRequest); }) .build(); webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 注入替换WebSocket的JS代码 injectWebSocketOverrideJS(); } }); webView.loadUrl("https://panel.autotcrypto.com"); } private void injectWebSocketOverrideJS() { String jsCode = "(function() {" + "window.WebSocket = function(url, protocols) {" + "const wsInstance = {" + "onopen: null," + "onmessage: null," + "onerror: null," + "onclose: null," + "send: function(msg) {" + "androidWebSocket.send(msg);" + "}," + "close: function(code, reason) {" + "androidWebSocket.close(code || 1000, reason || 'Normal close');" + "}" + "};" + "window.WebSocket.currentInstance = wsInstance;" + "androidWebSocket.connect(url, protocols || '');" + "return wsInstance;" + "};" + "})();"; webView.evaluateJavascript(jsCode, null); } // JS交互桥接类 private class WebSocketJsBridge { @JavascriptInterface public void connect(String url, String protocols) { // 创建WebSocket请求 Request request = new Request.Builder().url(url).build(); webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() { @Override public void onOpen(WebSocket webSocket, Response response) { // 通知页面连接成功 webView.post(() -> { webView.evaluateJavascript("if(window.WebSocket.currentInstance && window.WebSocket.currentInstance.onopen) window.WebSocket.currentInstance.onopen();", null); }); } @Override public void onMessage(WebSocket webSocket, String text) { // 通知页面收到消息 webView.post(() -> { String jsCall = String.format("if(window.WebSocket.currentInstance && window.WebSocket.currentInstance.onmessage) window.WebSocket.currentInstance.onmessage({data: '%s'});", text.replace("'", "\\'")); webView.evaluateJavascript(jsCall, null); }); } @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { // 通知页面连接失败 webView.post(() -> { webView.evaluateJavascript("if(window.WebSocket.currentInstance && window.WebSocket.currentInstance.onerror) window.WebSocket.currentInstance.onerror();", null); }); } @Override public void onClosing(WebSocket webSocket, int code, String reason) { webSocket.close(code, reason); } @Override public void onClosed(WebSocket webSocket, int code, String reason) { // 通知页面连接关闭 webView.post(() -> { String jsCall = String.format("if(window.WebSocket.currentInstance && window.WebSocket.currentInstance.onclose) window.WebSocket.currentInstance.onclose({code: %d, reason: '%s'});", code, reason.replace("'", "\\'")); webView.evaluateJavascript(jsCall, null); }); } }); } @JavascriptInterface public void send(String message) { if (webSocket != null) { webSocket.send(message); } } @JavascriptInterface public void close(int code, String reason) { if (webSocket != null) { webSocket.close(code, reason); } } } @Override protected void onDestroy() { super.onDestroy(); if (webSocket != null) { webSocket.close(1000, "Activity destroyed"); } } }
该方案完全可控,可任意修改WebSocket握手请求头,缺点是需要适配页面的WebSocket调用逻辑,处理所有事件回调,实现成本较高。
方案3:第三方WebView框架(不推荐)
曾经的CrossWalk WebView支持拦截WebSocket请求,但目前CrossWalk已停止维护,存在兼容性和安全风险,不建议使用。
总结
- 原生WebView无直接拦截修改WebSocket请求头的方法;
- 页面可控时优先选择JS注入方案(注意浏览器头限制);
- 需完全控制请求头时,采用OkHttp+JS交互的Native接管方案。
内容的提问来源于stack exchange,提问作者EprG

