22、WebView使用:加载网页、JavaScript交互、缓存策略、与Native通信
WebView 这个东西,说白了就是 Android 里的一个浏览器内核容器。你把它往布局里一放,就能直接加载网页、运行 JavaScript。很多 Hybrid 应用、活动页、甚至一些工具类 App,底层都是靠它撑起来的。
我个人习惯把 WebView 当成一个“轻量级的浏览器子进程”来对待。它功能强大,但坑也不少。今天我就把 WebView 的四个核心维度——加载、交互、缓存、通信——掰开揉碎了讲一遍。
22.1 基础加载:从 URL 到本地 HTML
加载网页是最基本的操作。你只需要拿到 WebView 实例,调用 loadUrl() 就行。但这里有个细节:什么时候调?
我建议在 onCreate 里先做配置,再调加载。别在布局还没初始化完就去 load,容易出空指针。
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true); // 开启 JS
webView.loadUrl("https://example.com");
如果你想加载本地 assets 里的 HTML 文件,用 file:///android_asset/ 协议:
webView.loadUrl("file:///android_asset/index.html");
22.2 JavaScript 交互:双向通信
WebView 和 JS 的交互,是 Hybrid 开发的核心。说白了就是:Native 调 JS,JS 调 Native。
22.2.1 Native 调用 JS
用 evaluateJavascript() 方法。它比老式的 loadUrl("javascript:...") 更安全,还能拿到返回值。
webView.evaluateJavascript("javascript:getUserInfo()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// value 就是 JS 返回的字符串
Log.d("WebView", "JS返回: " + value);
}
});
嗯,这里要注意:evaluateJavascript 必须在主线程调用。我遇到过在子线程里调,结果回调一直不触发的情况。
22.2.2 JS 调用 Native
通过 @JavascriptInterface 注解暴露一个 Java 对象给 JS。
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);
}
}
// 在 WebView 中注册
webView.addJavascriptInterface(new JsBridge(), "Android");
JS 端这样调用:
window.Android.share("标题", "https://example.com");
@JavascriptInterface 注解的方法才有效。低于这个版本,所有暴露的方法都能被 JS 调用,存在安全风险。我曾经在维护一个老项目时踩过这个坑——JS 能直接调用 getDeviceId(),吓得我赶紧加了版本判断。
22.3 缓存策略:让页面飞起来
WebView 的缓存策略,直接影响加载速度和用户体验。我个人习惯分场景配置:
| 场景 | 推荐模式 | 说明 |
|---|---|---|
| 静态活动页 | LOAD_CACHE_ELSE_NETWORK | 优先用缓存,没有才联网 |
| 实时数据页面 | LOAD_DEFAULT | 按标准 HTTP 缓存策略 |
| 调试/开发 | LOAD_NO_CACHE | 每次都从网络加载 |
WebSettings settings = webView.getSettings();
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
settings.setAppCacheEnabled(true);
settings.setDatabaseEnabled(true);
settings.setDomStorageEnabled(true); // 这个很重要,很多页面依赖 localStorage
你想想看,如果用户在一个弱网环境下打开你的 App,页面却每次都从网络加载,那体验得多糟糕。缓存策略说白了就是:在新鲜度和速度之间做权衡。
22.4 Native 通信:不止是 JS 桥
除了 @JavascriptInterface,还有几种常见的通信方式:
- URL 拦截:WebViewClient 的
shouldOverrideUrlLoading方法可以拦截页面跳转。你可以自定义协议,比如myapp://share?title=xxx,然后在 Native 端解析并处理。 - WebViewClient 回调:
onPageStarted、onPageFinished、onReceivedError等,可以用来控制加载状态、显示错误页面。 - WebChromeClient:处理 JS 的弹窗、文件选择、权限请求等。比如
onJsAlert、onShowFileChooser。
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("myapp://")) {
// 解析自定义协议
handleCustomProtocol(url);
return true; // 拦截,不让 WebView 自己加载
}
return false;
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
// 加载失败时显示本地错误页面
view.loadUrl("file:///android_asset/error.html");
}
});
22.5 知识体系总览
下面这张图,是我梳理的 WebView 核心知识结构。你可以把它当成一个检查清单:
22.6 避坑指南
最后,分享几个我实际项目中踩过的坑:
- 内存泄漏:WebView 持有 Activity 引用,如果不及时销毁,Activity 无法被 GC。我习惯在
onDestroy里调用webView.destroy(),并把 WebView 从父布局中移除。 - 硬件加速冲突:某些 WebView 页面在开启硬件加速时会出现花屏。如果遇到,可以尝试关闭硬件加速:
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)。 - 文件选择器:
onShowFileChooser在 Android 7.0 以上需要 FileProvider 配合。我曾经在适配时忘了加 FileProvider,结果用户选择图片后直接崩溃。 - User-Agent:有些页面会根据 UA 判断设备类型。你可以自定义 UA:
settings.setUserAgentString("Mozilla/5.0 (Linux; Android 10; ...)")。
WebView 这东西,用好了是利器,用不好就是坑。但只要你把加载、交互、缓存、通信这四个维度理清楚,大部分问题都能迎刃而解。
公众号:蓝海资料掘金营,微信deep3321