WebView和JavaScrip交互基础
在 Android 开发中,WebView 是一个强大的组件,不仅可以加载网页或 HTML 内容,还能通过 JavaScript 与原生 Android 代码进行双向交互。这种交互允许 WebView 中的网页调用 Android 方法,或 Android 代码调用网页中的 JavaScript 函数,从而实现混合应用(Hybrid App)的功能。以下是 WebView 与 JavaScript 交互的基础指南,涵盖基本用法、代码示例、注意事项和实践建议。
一、WebView 与 JavaScript 交互概述
- 目标:
- JavaScript 调用 Android:网页中的 JavaScript 代码调用 Android 原生方法(如显示 Toast、访问本地数据)。
- Android 调用 JavaScript:Android 代码执行网页中的 JavaScript 函数(如传递数据、触发事件)。
- 核心机制:
- 使用
addJavascriptInterface
暴露 Android 方法给 JavaScript。 - 使用
WebView.loadUrl("javascript:...")
或WebView.evaluateJavascript
执行 JavaScript 代码。 - 适用场景:
- 混合应用开发(如 H5 页面调用原生功能)。
- 动态更新 UI(如通过 JavaScript 修改网页内容)。
- 数据交互(如 Android 传递数据给网页,或网页返回数据)。
- 安全风险:
- JavaScript 代码可能被恶意注入,需确保加载内容可信。
addJavascriptInterface
在 API 17 以下有安全漏洞。
二、准备工作
- 权限声明:
如果加载远程网页,需在AndroidManifest.xml
中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
- 添加 WebView:
在布局文件中添加WebView
:
<!-- res/layout/activity_main.xml -->
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- 启用 JavaScript:
确保 WebView 支持 JavaScript(默认禁用):
webView.getSettings().setJavaScriptEnabled(true);
- 注意:
- 安全性:仅对受信任的网页启用
setJavaScriptEnabled
。 - HTTPS:Android 10+ 默认禁用 HTTP,推荐使用 HTTPS 或配置
android:usesCleartextTraffic="true"
(不推荐)。 - API 兼容:API 17+ 推荐使用
evaluateJavascript
替代loadUrl
执行 JavaScript。
三、WebView 与 JavaScript 交互基本用法
以下是实现 JavaScript 与 Android 双向交互的详细步骤和代码示例。
1. JavaScript 调用 Android 方法
通过 addJavascriptInterface
将 Android 方法暴露给 JavaScript。
- 步骤:
- 定义一个带有
@JavascriptInterface
注解的接口类。 - 将接口对象绑定到 WebView。
- 在 HTML/JavaScript 中调用暴露的方法。
- 代码示例:
import android.content.Context;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true); // 启用 JavaScript
// 绑定 JavaScript 接口
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
// 加载本地 HTML
webView.loadUrl("file:///android_asset/index.html");
}
// 定义 JavaScript 接口
public class WebAppInterface {
private Context context;
public WebAppInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public void showToast(String message) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public String getData() {
return "Data from Android";
}
}
}
- HTML 文件(
assets/index.html
):
<!DOCTYPE html>
<html>
<head>
<title>WebView JavaScript</title>
</head>
<body>
<h1>WebView JavaScript Interaction</h1>
<button onclick="callAndroidToast()">Show Toast</button>
<button onclick="getAndroidData()">Get Android Data</button>
<p id="result"></p>
<script>
function callAndroidToast() {
Android.showToast("Hello from JavaScript!");
}
function getAndroidData() {
document.getElementById("result").innerText = Android.getData();
}
</script>
</body>
</html>
- 说明:
addJavascriptInterface(new WebAppInterface(this), "Android")
:将接口对象绑定到 JavaScript 的Android
对象。@JavascriptInterface
:标记方法可被 JavaScript 调用。- HTML 中的
Android.showToast
和Android.getData
调用 Android 方法。 - 运行后,点击按钮会显示 Toast 或更新网页内容。
2. Android 调用 JavaScript 方法
通过 WebView.loadUrl
或 evaluateJavascript
执行网页中的 JavaScript 函数。
- 步骤:
- 在 HTML 中定义 JavaScript 函数。
- 在 Android 代码中调用
webView.loadUrl("javascript:func()")
或webView.evaluateJavascript
。 - (可选)处理 JavaScript 返回值。
- 代码示例:
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
// 加载本地 HTML
webView.loadUrl("file:///android_asset/index.html");
// 按钮触发 JavaScript
Button callJsButton = findViewById(R.id.callJsButton);
callJsButton.setOnClickListener(v -> {
// 方法 1: 使用 loadUrl
webView.loadUrl("javascript:showMessage('Hello from Android!')");
// 方法 2: 使用 evaluateJavascript (API 19+)
webView.evaluateJavascript("getDataFromAndroid('Data from Android')", value -> {
// 处理返回值
Log.d("WebView", "JS Return: " + value);
});
});
}
}
- 布局文件(
res/layout/activity_main.xml
):
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/callJsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Call JavaScript" />
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
- HTML 文件(
assets/index.html
):
<!DOCTYPE html>
<html>
<head>
<title>WebView JavaScript</title>
</head>
<body>
<h1>WebView JavaScript Interaction</h1>
<p id="message"></p>
<script>
function showMessage(message) {
document.getElementById("message").innerText = message;
}
function getDataFromAndroid(data) {
return "Received: " + data;
}
</script>
</body>
</html>
- 说明:
loadUrl("javascript:...")
:执行 JavaScript 代码,无返回值。evaluateJavascript
(API 19+):支持获取返回值,推荐使用。- 示例中,点击按钮会调用网页的
showMessage
函数更新内容,或调用getDataFromAndroid
并获取返回值。
3. 配置 WebViewClient 和 WebChromeClient
为确保交互正常,需配置 WebViewClient
和 WebChromeClient
。
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url); // 在 WebView 内加载链接
return true;
}
@Override
public void onPageFinished(WebView view, String url) {
Log.d("WebView", "Page loaded: " + url);
}
});
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
// 处理 JavaScript alert 弹窗
new AlertDialog.Builder(view.getContext())
.setMessage(message)
.setPositiveButton("OK", (dialog, which) -> result.confirm())
.setCancelable(false)
.show();
return true;
}
});
四、完整代码示例
以下是一个完整的示例,展示 JavaScript 调用 Android 和 Android 调用 JavaScript:
import android.content.Context;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
// 配置 WebViewClient
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
// 配置 WebChromeClient
webView.setWebChromeClient(new WebChromeClient());
// 绑定 JavaScript 接口
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
// 加载本地 HTML
webView.loadUrl("file:///android_asset/index.html");
// 调用 JavaScript
Button callJsButton = findViewById(R.id.callJsButton);
callJsButton.setOnClickListener(v -> {
webView.evaluateJavascript("showMessage('Hello from Android!')", value -> {
Log.d("WebView", "JS Return: " + value);
});
});
}
public class WebAppInterface {
private Context context;
public WebAppInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public void showToast(String message) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public String getData() {
return "Data from Android";
}
}
}
- 布局文件(
res/layout/activity_main.xml
):
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/callJsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Call JavaScript" />
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
- HTML 文件(
assets/index.html
):
<!DOCTYPE html>
<html>
<head>
<title>WebView JavaScript</title>
</head>
<body>
<h1>WebView JavaScript Interaction</h1>
<button onclick="Android.showToast('Hello from JS!')">Show Toast</button>
<button onclick="showAndroidData()">Get Android Data</button>
<p id="message"></p>
<p id="result"></p>
<script>
function showMessage(message) {
document.getElementById("message").innerText = message;
}
function showAndroidData() {
document.getElementById("result").innerText = Android.getData();
}
</script>
</body>
</html>
- 运行效果:
- 点击 HTML 按钮,调用 Android 的
showToast
显示 Toast,或显示 Android 返回的数据。 - 点击 Android 按钮,调用 JavaScript 的
showMessage
更新网页内容。
五、注意事项
- 安全问题:
- JavaScript 注入:启用
setJavaScriptEnabled(true)
时,仅加载受信任的网页,避免 XSS 攻击。 - API 17 以下漏洞:
addJavascriptInterface
在低版本有安全风险,建议设置minSdkVersion
为 17 或使用 WebView 安全库。 - HTTPS:优先使用 HTTPS URL,Android 10+ 默认禁用 HTTP。
- 性能优化:
- 避免加载复杂网页,减少内存占用。
- 启用缓存:
java webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); webView.getSettings().setAppCacheEnabled(true); webView.getSettings().setAppCachePath(getCacheDir().getAbsolutePath());
- 错误处理:
- 处理加载失败:
java webView.setWebViewClient(new WebViewClient() { @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { Log.e("WebView", "Error: " + error.getDescription()); } });
- 检查网络状态:
java ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo == null || !networkInfo.isConnected()) { Toast.makeText(this, "No network", Toast.LENGTH_SHORT).show(); }
- 生命周期管理:
- 暂停/恢复 WebView:
@Override protected void onPause() { super.onPause(); webView.onPause(); webView.pauseTimers(); } @Override protected void onResume() { super.onResume(); webView.onResume(); webView.resumeTimers(); }
- 销毁 WebView:
java @Override protected void onDestroy() { super.onDestroy(); webView.destroy(); }
- API 兼容:
- 使用
evaluateJavascript
(API 19+)替代loadUrl
。 - 定期更新系统 WebView(通过 Google Play)。
六、学习建议与实践
- 学习路径:
- 掌握
addJavascriptInterface
实现 JavaScript 调用 Android。 - 学习
evaluateJavascript
执行 JavaScript 代码。 - 理解
WebViewClient
和WebChromeClient
的作用。 - 探索复杂交互(如传递复杂数据、处理回调)。
- 学习混合开发(Hybrid App)架构。
- 实践项目:
- 简单项目:实现 JavaScript 调用 Android 显示 Toast。
- 进阶项目:Android 传递 JSON 数据给 JavaScript,动态更新网页。
- 高级项目:开发一个简单的 Hybrid App,结合 REST API 和 WebView。
- 调试工具:
- Chrome DevTools:通过
chrome://inspect
调试 WebView(需启用开发者模式)。 - Logcat:查看 JavaScript 和 Android 日志。
- Charles/Fiddler:抓包分析网络请求。
- 推荐资源:
- Android 官方文档:https://developer.android.com/reference/android/webkit/WebView
- WebView 调试:https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews
- JavaScript 交互:https://developer.android.com/guide/webapps/webview#BindingJavaScript
七、总结
- JavaScript 调用 Android:使用
addJavascriptInterface
暴露方法,JavaScript 通过对象(如Android
)调用。 - Android 调用 JavaScript:使用
loadUrl
或evaluateJavascript
执行 JavaScript 函数。 - 核心配置:启用 JavaScript,设置
WebViewClient
和WebChromeClient
。 - 注意事项:安全(避免 XSS、HTTPS)、性能(缓存)、生命周期管理。
- 推荐:优先使用
evaluateJavascript
,仅加载受信任内容。 - 实践:实现简单交互,逐步开发 Hybrid App。
如果需要更详细的代码示例(如复杂数据交互、Hybrid App 架构)或特定场景的讲解,请告诉我!