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");
小提示:加载本地 HTML 时,记得把 CSS、JS 文件也放在 assets 目录下。路径引用用相对路径就行。

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");
警告:从 Android 4.2 开始,@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 回调onPageStartedonPageFinishedonReceivedError 等,可以用来控制加载状态、显示错误页面。
  • WebChromeClient:处理 JS 的弹窗、文件选择、权限请求等。比如 onJsAlertonShowFileChooser
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");
    }
});
核心思路:Native 通信的本质是“事件驱动”。JS 触发事件 → Native 监听并处理 → 返回结果给 JS。这个闭环一旦建立,Hybrid 开发就变得非常灵活。

22.5 知识体系总览

下面这张图,是我梳理的 WebView 核心知识结构。你可以把它当成一个检查清单:

WebView 核心 加载网页 JS 交互 缓存策略 Native 通信 loadUrl() 本地 assets evaluateJavascript @JavascriptInterface 缓存模式 DOM Storage URL 拦截 WebViewClient WebChromeClient

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