WebView.addJavascriptInterface(Object, String) 將 Java 物件暴露給 JavaScript,JS 端可透過指定名稱呼叫 Java 方法。
public class JsBridge {
private Context context;
public JsBridge(Context context) {
this.context = context;
}
@JavascriptInterface // API 17+ 必須加此註解,否則方法不可見
public void shareToSocial(String title, String content, String url) {
// 在主執行緒執行 UI 操作
new Handler(Looper.getMainLooper()).post(() -> {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, title);
intent.putExtra(Intent.EXTRA_TEXT, content + " " + url);
context.startActivity(Intent.createChooser(intent, "分享到"));
});
}
@JavascriptInterface
public String getAppVersion() {
try {
return context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0).versionName;
} catch (Exception e) {
return "unknown";
}
}
}
WebView webView = findViewById(R.id.webview);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true); // 必須啟用 JS
settings.setDomStorageEnabled(true); // 支援 localStorage
// 註冊介面,JS 端透過 window.AndroidBridge 呼叫
webView.addJavascriptInterface(new JsBridge(this), "AndroidBridge");
// 載入頁面
webView.loadUrl("file:///android_asset/share.html"); // 或 https://...
<button onclick="shareArticle()">分享</button>
<script>
function shareArticle() {
var title = document.title;
var content = "精彩文章推薦";
var url = window.location.href;
// 呼叫 Java 方法
if (window.AndroidBridge) {
window.AndroidBridge.shareToSocial(title, content, url);
} else {
alert("原生介面未就緒");
// 降級:Web Share API 或複製連結
if (navigator.share) {
navigator.share({title, text: content, url});
}
}
}
// 獲取 App 版本
window.AndroidBridge.getAppVersion(function(version) {
console.log("App 版本:" + version);
});
| 風險 | 對策 |
|---|---|
| 任意代碼執行 (API < 17) | 僅在 API 17+ 使用,強制加 @JavascriptInterface 註解,僅暴露必要方法 |
| XSS 注入 | 載入可信內容 (assets/自有域名);setAllowFileAccess(false)、setAllowContentAccess(false) |
| 釣魚網站盜用介面 | onPageStarted 檢查 URL 域名,非白名單 removeJavascriptInterface |
| 敏感資料洩漏 | 不暴露取得 Token/密碼/個資的方法;僅暴露業務動作 (分享、支付、登入) |