webview 通过 js 调用 java 方法

JS 调用 java 方法

WebView 允许开发者拓展 JavaScript API 命名空间,通过 Java 定义各自的组件然后令其在 JavaScript 环境中可用。例如:

1
2
3
4
webView = (IgmWebView) findViewById(R.id.webview);
webView.init();
api = new BridgeApi(pluginMgr);
webView.addJavascriptInterface(api, "MobileAPI");

其中 BridgeApi 中核心代码如下:

1
2
3
4
@JavascriptInterface
public String invoke(String jsBridge) {
return pluginMgr.invoke(jsBridge);
}

pluginMgr 对象的 invoke 方法中,通过原生的方式解析 jsBridge JSON 中的类名、方法名,然后反射调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public String invoke(String jsBridge) {
String res = "";
try {
IPlugin plugin = null;
JSONObject json = JSON.parseObject(jsBridge);
String className = json.getString("className");
plugin = entries.get(className);
if (plugin == null) {
Log.e("PluginMgr", "调用了插件不存在或未注解的插件:"+ className);
return null;
}
String methodName = json.getString("methodName");
JSONObject param = json.getJSONObject("param");
// 执行接口的execute方法
if (plugin != null) {
Method method = null;
try {
method = plugin.getClass().getMethod(methodName,new Class[]{JSONObject.class});
} catch (NoSuchMethodException e) {
method = plugin.getClass().getMethod(methodName,new Class[]{});
}
if (method.isAnnotationPresent(CMPluginMethod.class)) {
IOnInvokeMethod invokeMethod = new OnInvokeMethod(plugin, method, param);
if (method.isAnnotationPresent(CMPluginPermission.class)) {
CMPluginPermission anno = method.getAnnotation(CMPluginPermission.class);
boolean isGranted = permissionCheck(invokeMethod,anno.value(),anno.msg());
if (isGranted) {
res = (String) invokeMethod.invokeMethod();
}
} else {
res = String.valueOf(invokeMethod.invokeMethod());
}
} else {
Log.e("PluginMgr", "调用了插件:"+plugin.getClass().getName()+"未开放的方法:"+methodName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return res;
}

其中通过注解,限制了可以被 js 调用的 java 方法;entries 中维护了实现 IPlugin 接口的类对象单例。

js 中若想调用,只需要:

1
2
3
4
5
6
var params = {
className: plugin,
methodName: method,
param: param
};
window.MobileAPI.invoke(JSON.stringify(params));

回调实现返回值处理

上面的问题是,调用 java 方法后,不能直接得到返回值。

一个思路就是通过给每次调用标记一个 callbackId ,并把需要的回调方法与这个 id 放在 map 中,当 java 执行结束时,便可以通过 callbackId 找到等待的回调方法,并调用需要的 SUCCESSERROR 回调即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
api.invokeV4 = function(plugin, method, param, success, error) {
var callbackId = plugin + igm.callbackId++;
param.callbackId = callbackId;
var params = {
className: plugin,
methodName: method,
callbackId: callbackId,
param: param
};
if (success || error) {
igm.callbacks[callbackId] = {
success: success,
error: error
};
}
if (mobileType.indexOf('iPhone') > -1) {
return window.prompt(JSON.stringify(params), 'invoke');
}
return window.MobileAPI.invoke(JSON.stringify(params));
}

java 中调用 js 方法需要通过 webviewloadUrl 方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UIRunnable implements Runnable {
private String url;
private WebView wv;
public UIRunnable(WebView wv,String url){
this.wv = wv;
this.url = url;
}
@Override
public void run() {
if (!url.startsWith("javascript:")) {
url = "javascript:" + url;
}
wv.loadUrl(url);
}
}

然后在核心 WebView 的方法中通过 runOnUiThread 运行,即可执行该 js:

1
2
3
4
5
6
7
public void postLoadUrl(String url) {
if(context instanceof Activity) {
((Activity) context).runOnUiThread(new UIRunnable(this,url));
} else {
System.out.println("context is not Activity");
}
}