JS 与 App 的双向桥接:addJavascriptInterface 和 evaluateJavascript
App → JS:evaluateJavascript
// App 端
webView.evaluateJavascript(
"document.title",
value -> Log.d("WebView", "页面标题:" + value)
);
注意:value 是 JSON 字符串(包括引号),直接 Log.d 会看到带引号的 "页面标题",需要手动 strip。
注入 JS 函数:
webView.evaluateJavascript(
"myFunction('hello from Android')",
null // 不关心返回值
);
JS → App:addJavascriptInterface
// App 端,一个普通类 + @JavascriptInterface 注解
class Bridge {
@JavascriptInterface
public void showToast(String msg) {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
// 暴露给 WebView
webView.addJavascriptInterface(new Bridge(), "AndroidBridge");
网页 JS 里调用:
// 点击按钮时调用
document.getElementById("btn").addEventListener("click", () => {
window.AndroidBridge.showToast("你好,原生世界");
});
注意安全:@JavascriptInterface 从 Android 4.2 (API 17) 开始强制要求。低于 4.2 的设备不加注解也能调用,但这是个已知的安全漏洞——恶意网页可以通过反射访问 Android Runtime,不要忽视这个注解。
调用时机问题
addJavascriptInterface必须在loadUrl之前 —— JS 代码在 HTML 加载时就会执行,接口晚了就接不上evaluateJavascript必须在网页加载完成后 —— 否则 JS 还没定义好,会抛 ReferenceError
正确顺序:
// 1. 先注册接口
webView.addJavascriptInterface(new Bridge(), "AndroidBridge");
// 2. 再注册 WebViewClient
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
// 3. onPageFinished 里注入 JS
view.evaluateJavascript("initPage()", null);
}
});
// 4. 最后 loadUrl
webView.loadUrl("file:///android_asset/index.html");
实战场景
| 场景 | 用哪个 |
|---|---|
| 网页要调相机 / 定位 / 支付 | addJavascriptInterface 暴露原生 API |
| App 要读取网页 DOM(标题、输入框内容) | evaluateJavascript + return JS 表达式 |
| App 要控制网页行为(触发某个 JS 函数) | evaluateJavascript |
| 双向事件通信 | JS 调 @JavascriptInterface 传数据 → App 处理 → App evaluateJavascript 回调 |
安全忠告
永远不要暴露敏感方法 —— 只要网页代码跑在你的 WebView 里,恶意 JS 理论上可以调用任何被 addJavascriptInterface 暴露的方法。暴露的接口必须只做"轻量、无副作用"的事。