混合编程
Android 混合编程主要需要实现的是JSBridge,其中JSBridge是H5与Native通信的桥梁,其作用是实现H5与Native之间的双向通信。
JSBridge实现关键点
- Java如何调用JavaScript
- JavaScript如何调用Java
- 方法参数以及回调如何处理
- 通信协议的制定
Java如何调用JavaScript
js调用Android的方法有以下四种:
- WebView 的 JavascriptInterface
- WebViewClient.shouldOverrideUrlLoading()
- WebChromeClient.onConsoleMessage()
- WebChromeClient.onJsPrompt()、onJsAlert()、onJsConfirm()
我们先对此四种方案进行一个详细的描述,最后选择一个方案即可。本文章中采用了第四种方案。
为什么选择onJsPrompt
WebChromeClient.onJsPrompt()
在WebView有一个方法,叫setWebChromeClient,可以设置WebChromeClient对象,而这个对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的对应的方法,即window.alert,window.confirm,window.prompt,WebChromeClient对象中的三个方法对应的就会被触发,那这三个方法到底要使用哪个呢?
- 这三个方法的区别,可以详见w3c JavaScript 消息框 。
- 一般来说,我们是不会使用onJsAlert的,为什么呢?因为js中alert使用的频率还是非常高的,一旦我们占用了这个通道,alert的正常使用就会受到影响,而confirm和prompt的使用频率相对alert来说,则更低一点。
- 那么到底是选择confirm还是prompt呢,其实confirm的使用频率也是不低的,比如你点一个链接下载一个文件,这时候如果需要弹出一个提示进行确认,点击确认就会下载,点取消便不会下载,类似这种场景还是很多的,因此不能占用confirm。
- 而prompt则不一样,在Android中,几乎不会使用到这个方法,就是用,也会进行自定义,所以我们完全可以使用这个方法。该方法就是弹出一个输入框,然后让你输入,输入完成后返回输入框中的内容。因此,占用prompt是再完美不过了。
方法参数以及回调处理
- 任何IPC通信都涉及到参数序列化的问题,同理,Java与JavaScript之间只能传递基础类型(包括基本类型和字符串),不包括其他对象或者函数。所以可以采用json格式来传递数据。
- 为了实现异步返回结果,所以JavaScript与Java相互调用不能直接获取返回值,只能通过回调的方式来获取返回结果。
通信协议的制定
要进行正常的通信,通信协议的制定是必不可少的。我们回想一下熟悉的http请求url的组成部分。形如http://host:port/path?param=value, 我们参考http,制定JSBridge的组成部分。
具体实战
调用流程
在js中,可以采用如下方法调用java方法:
var JSBridge = {
call: function(className, method, params, callback) {
var uri = 'jsbridge://' + className + ':' + callback + '/' + method + '?' + params;
window.prompt(uri, "");
}
}
// 下面会调用java中的 bridge.showToast方法
JSBridge.call('bridge', 'showToast', {'msg':'Hello JSBridge'}, function(res) {
alert(JSON.stringify(res))
});
在java中, 可以如下实现:
// 进入prompt回调
public class JSBridgeWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(JSBridge.callJava(view, message));
return true;
}
}
// 调用java逻辑
public class JSBridge {
...
public static String callJava(WebView webView, String uriString) {
String methodName = "";
String className = "";
String param = "{}";
String port = "";
if (!TextUtils.isEmpty(uriString) && uriString.startsWith("JSBridge")) {
Uri uri = Uri.parse(uriString);
className = uri.getHost();
param = uri.getQuery();
port = uri.getPort() + "";
String path = uri.getPath();
if (!TextUtils.isEmpty(path)) {
methodName = path.replace("/", "");
}
}
// 基于上面的className、methodName和port path调用对应类的方法
if (exposedMethods.containsKey(className)) {
HashMap<String, Method> methodHashMap = exposedMethods.get(className);
if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
Method method = methodHashMap.get(methodName);
if (method != null) {
try {
method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return null;
}
}
// 直接进入showToast函数的实现
public static void showToast(WebView webView, JSONObject param, final Callback callback) {
String message = param.optString("msg");
Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
if (null != callback) {
try {
JSONObject object = new JSONObject();
object.put("key", "value");
object.put("key1", "value1");
callback.apply(getJSONObject(0, "ok", object));
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 上述程序的callback.apply方法实现如下: 即通过webView.loadUrl实现java调用js的方法