
之前做一个数据可视化功能,需要在Android的WebView里用ECharts画图表——原生画图表太麻烦,HTML+JS几行代码就能搞定,但图表数据要从Java层获取,还得支持JS调Java弹Toast、跳页面。结果一调试就出问题:JS要么调不到Java方法,要么调了直接崩溃,折腾半天才发现是配置和注解的小细节没做好。相信做Android混合开发的同学,都遇到过这种“JS和Java交互”的坑,今天把完整解决方案和避坑点整理出来,你跟着做就能少走弯路。
在混合开发里,WebView加载的HTML/JS是“前端”,Android原生代码是“后端”,两者需要配合才能实现完整功能。比如我这次的需求:
所以JS调Java的核心场景就是:前端需要依赖原生能力时,主动调用Java方法拿数据或触发操作——这也是淘宝、陌陌等App用混合开发的关键原因:既省成本,又能兼顾原生体验。
以“WebView加载本地HTML,JS调Java拿数据画图表、弹Toast、跳页面”为例,一步步拆解实现过程。
首先要在布局里加WebView,然后在Activity里初始化——关键是要开启JS执行权限,否则JS代码根本跑不起来。
简单放一个占满屏幕的WebView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/wv_chart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>重点要做3件事:开启JS支持、加载HTML文件、暴露Java接口给JS调用:
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private WebView wvChart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 获取WebView实例
wvChart = findViewById(R.id.wv_chart);
// 2. 获取WebView设置对象,开启JS支持
WebSettings webSettings = wvChart.getSettings();
webSettings.setJavaScriptEnabled(true); // 必须开启,否则JS无法执行
// 3. 加载HTML文件(本地HTML放在assets文件夹下)
wvChart.loadUrl("file:///android_asset/index.html");
// 4. 暴露Java接口给JS:参数1是接口实例,参数2是JS调用时的对象名(这里叫"sunny")
wvChart.addJavascriptInterface(new JsCallJavaInterface(), "sunny");
}
// 步骤2:定义JS可调用的Java接口类
public final class JsCallJavaInterface {
// 注意:给JS调用的方法,必须加@JavascriptInterface注解!
@JavascriptInterface
public String getChartData() {
// 模拟从Java层获取图表数据(比如从数据库、接口请求来的)
JSONObject dataObj = new JSONObject();
try {
dataObj.put("direct", 10); // 直接访问量
dataObj.put("email", 40); // 邮件营销量
dataObj.put("random", 5); // 随机访问量
} catch (JSONException e) {
Log.e("MainActivity", "JSON构造失败:" + e.getMessage());
e.printStackTrace();
}
Log.i("MainActivity", "给JS返回的数据:" + dataObj.toString());
return dataObj.toString(); // 把数据以JSON字符串形式返回给JS
}
// JS调用Java弹Toast
@JavascriptInterface
public void showToast(String msg) {
// Toast必须在主线程弹,这里用Handler确保主线程执行
new Handler(getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
}
});
}
// JS调用Java打开新Activity
@JavascriptInterface
public void jumpToNewPage() {
new Handler(getMainLooper()).post(new Runnable() {
@Override
public void run() {
// 跳转到SunnyActivity(需要自己创建这个Activity)
startActivity(new Intent(MainActivity.this, SunnyActivity.class));
}
});
}
}
}HTML文件要放在assets文件夹下(如果没有这个文件夹,在main目录下新建一个),里面包含ECharts图表代码和JS调用Java的逻辑。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JS调用Java示例</title>
<!-- 引入ECharts(也可以下载到本地引入) -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
/* 图表容器占满屏幕 */
#chartContainer {
width: 100%;
height: 400px;
margin-bottom: 20px;
}
/* 按钮样式 */
button {
padding: 10px 20px;
font-size: 16px;
margin: 0 10px;
}
</style>
</head>
<body>
<!-- 图表容器 -->
<div id="chartContainer"></div>
<!-- 测试按钮:JS调用Java弹Toast、跳页面 -->
<button onclick="callJavaShowToast()">点我弹Toast</button>
<button onclick="callJavaJumpPage()">点我打开新页面</button>
<script type="text/javascript">
// 1. JS调用Java获取图表数据,初始化ECharts
function initChart() {
// 调用Java的getChartData()方法:通过暴露的"sunny"对象调用
var javaDataStr = sunny.getChartData();
// 把JSON字符串转成JS对象
var chartData = JSON.parse(javaDataStr);
// 初始化ECharts实例
var myChart = echarts.init(document.getElementById('chartContainer'));
// 配置图表选项
var option = {
title: { text: '访问量来源统计(JS调用Java获取数据)' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
xAxis: {
type: 'category',
data: ['直接访问', '邮件营销', '随机访问']
},
yAxis: { type: 'value' },
series: [{
name: '访问量',
type: 'bar',
data: [chartData.direct, chartData.email, chartData.random]
}]
};
// 渲染图表
myChart.setOption(option);
}
// 2. JS调用Java弹Toast
function callJavaShowToast() {
sunny.showToast('JS调用Java弹的Toast!');
}
// 3. JS调用Java打开新页面
function callJavaJumpPage() {
sunny.jumpToNewPage();
}
// 页面加载完成后初始化图表
window.onload = initChart;
</script>
</body>
</html>为了测试“JS调Java跳页面”,需要新建一个简单的SunnyActivity,布局随便放个文本就行:
// SunnyActivity.java
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class SunnyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 简单显示一个文本(也可以加布局)
setContentView(R.layout.activity_sunny);
}
}对应的布局文件(activity_sunny.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JS调用Java打开的新页面!"
android:textSize="20sp" />
</LinearLayout>Handler(getMainLooper()).post()把操作切换到主线程,否则会报“Can't create handler inside thread that has not called Looper.prepare()”错误。如果不想把HTML放在本地assets文件夹,而是放在服务器上(比如http://xxx.com/chart.html),只需要改WebView加载的URL:
// 把本地加载改成加载服务器URL
wvChart.loadUrl("http://xxx.com/chart.html");注意:需要在AndroidManifest.xml里加网络权限,否则无法访问服务器:
<uses-permission android:name="android.permission.INTERNET" />运行App后,会看到3个关键效果:
整个过程没有复杂逻辑,核心就是“WebView开启JS支持+暴露Java接口+JS调用接口名”——只要避开上面的2个坑,JS和Java交互就能一次成功。
最后再提醒一句:混合开发虽然高效,但涉及到敏感操作(比如支付、登录)时,尽量用原生代码,避免JS调用带来的安全风险。如果只是普通的数据交互和功能触发,按这个方案来做就很稳妥~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。