25、WebView 的定制与扩展:WebView 配置、JavaScript 接口注入、自定义协议拦截、WebViewClient 与 WebChromeClient
WebView 是 Android 平台上承载 Web 内容的容器。但说实话,默认的 WebView 就是个“毛坯房”——能跑,但不好用。真正让它发挥价值的,是定制与扩展能力。
这一章,我会带你深入 WebView 的四个核心扩展点:WebSettings(配置)、addJavascriptInterface(JS 接口注入)、自定义协议拦截,以及两个关键回调类——WebViewClient 和 WebChromeClient。
嗯,这些内容我几乎在每个项目里都会用到。从简单的 H5 混合开发,到复杂的浏览器内核定制,都绕不开它们。
25.1 WebSettings:把毛坯房精装修
WebSettings 是 WebView 的配置中心。你想想看,一个浏览器要支持 JavaScript、要缓存、要缩放……这些开关全在这里。
我个人习惯在 WebView 初始化后,第一时间拿到 WebSettings 对象,然后按需配置。下面是我常用的配置模板:
WebSettings settings = webView.getSettings();
// JavaScript 必须开,否则 H5 动不了
settings.setJavaScriptEnabled(true);
// 缓存策略:有网用默认,没网用缓存
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setDomStorageEnabled(true);
// 缩放相关
settings.setSupportZoom(true);
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false); // 隐藏缩放按钮
// 渲染加速
settings.setRenderPriority(WebSettings.RenderPriority.HIGH);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
// User-Agent 定制(我经常用来区分客户端)
String ua = settings.getUserAgentString();
settings.setUserAgentString(ua + " MyApp/1.0");
还有一个容易被忽略的点:混合内容。如果你的页面是 HTTPS,但引用了 HTTP 的资源,默认会被阻塞。这时候需要:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
嗯,这个坑我踩过。有一次对接第三方支付页面,HTTPS 页面里加载了 HTTP 的图片,结果图片全裂了。加上这行代码就好了。
25.2 JavaScript 接口注入:让 H5 和 Native 对话
addJavascriptInterface 是 WebView 最强大的功能之一。它允许你把 Java 对象暴露给 JavaScript 调用。说白了,就是搭了一座桥。
来看一个典型例子:
// 定义接口类
public class JsBridge {
@JavascriptInterface
public void share(String title, String url) {
// 调用 Native 分享功能
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, title + " " + url);
intent.setType("text/plain");
context.startActivity(Intent.createChooser(intent, "分享到"));
}
@JavascriptInterface
public String getToken() {
return "your_token_here";
}
}
// 注入到 WebView
webView.addJavascriptInterface(new JsBridge(), "NativeBridge");
然后在 H5 里这样调用:
<button onclick="NativeBridge.share('标题', 'https://example.com')">分享</button>
<script>
var token = NativeBridge.getToken();
console.log(token);
</script>
我在项目中遇到过一个问题:H5 频繁调用 Native 方法获取数据,导致页面卡顿。后来我加了一层缓存,把高频数据(比如 token、设备信息)在注入时一次性传给 JS,而不是每次调用都走桥接。性能提升很明显。
25.3 自定义协议拦截:接管网络请求
WebView 默认处理 http/https 协议。但如果你想拦截自定义协议(比如 myapp://openPage),或者拦截特定请求做本地处理,就需要用到 shouldInterceptRequest。
这个回调在 WebViewClient 里。我经常用它来做两件事:
- 拦截自定义协议:比如 myapp://scan,唤起扫码页面
- 本地资源替换:把远程图片替换为本地资源,加速加载
代码示例:
webView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
// 拦截自定义协议
if (url.startsWith("myapp://")) {
handleCustomProtocol(url);
return new WebResourceResponse(null, null, null);
}
// 替换本地资源
if (url.contains("logo.png")) {
try {
InputStream is = context.getAssets().open("logo_local.png");
return new WebResourceResponse("image/png", "utf-8", is);
} catch (IOException e) {
e.printStackTrace();
}
}
return super.shouldInterceptRequest(view, request);
}
});
我曾经用这个功能做过离线包方案:把 H5 的静态资源打包到 APK 里,拦截请求时优先返回本地文件。这样页面加载速度从 3 秒降到了 200 毫秒。效果非常显著。
25.4 WebViewClient 与 WebChromeClient:两个关键回调
这两个类经常被混淆。我简单总结一下:
| 回调类 | 主要职责 | 常用方法 |
|---|---|---|
| WebViewClient | 页面加载、请求拦截、错误处理 | onPageStarted, onPageFinished, shouldOverrideUrlLoading, shouldInterceptRequest, onReceivedError |
| WebChromeClient | 浏览器 UI 相关:进度、标题、弹窗、权限 | onProgressChanged, onReceivedTitle, onJsAlert, onGeolocationPermissionsShowPrompt |
说白了,WebViewClient 管的是“页面本身”,WebChromeClient 管的是“浏览器外壳”。
来看一个完整的例子:
// WebViewClient:控制页面行为
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
// 拦截 tel: 协议,调用系统拨号
if (url.startsWith("tel:")) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
context.startActivity(intent);
return true;
}
return false;
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
// 加载失败时显示自定义错误页
view.loadUrl("file:///android_asset/error.html");
}
});
// WebChromeClient:控制浏览器 UI
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
// 更新进度条
progressBar.setProgress(newProgress);
if (newProgress == 100) {
progressBar.setVisibility(View.GONE);
}
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
// 用 Native 弹窗代替 JS 弹窗
new AlertDialog.Builder(context)
.setMessage(message)
.setPositiveButton("确定", (d, w) -> result.confirm())
.show();
return true;
}
});
我记得有一次,用户反馈点击 H5 里的链接没反应。排查后发现是 shouldOverrideUrlLoading 里对所有 URL 都返回了 true,但没有做任何处理。结果就是所有链接都被“吃掉”了。嗯,这种低级错误,犯过一次就不会再犯了。
25.5 综合实践:一个可复用的 WebView 封装
最后,我分享一个我在项目中常用的封装思路。把配置、注入、拦截、回调整合到一个类里:
public class CustomWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.startsWith("myapp://")) {
// 处理自定义协议
return true;
}
return false;
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
// 本地资源替换逻辑
return null;
}
}
// 使用
WebView webView = findViewById(R.id.webview);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
// ... 其他配置
webView.addJavascriptInterface(new JsBridge(), "NativeBridge");
webView.setWebViewClient(new CustomWebViewClient());
webView.setWebChromeClient(new MyWebChromeClient());
webView.loadUrl("https://example.com");
这样做的好处是:配置和逻辑分离,代码清晰,便于维护。你想想看,如果所有代码都堆在 Activity 里,后期改起来多痛苦。
好了,这一章的内容就到这里。WebView 的定制与扩展,说白了就是四个字:配置、注入、拦截、回调。把这四个点吃透,你就能应对绝大多数混合开发场景。