Hybrid
Hybrid,字面意思,“混合的”,在Android中,指的是混合开发,也就是Native和H5混合开发。
主要是实现思想是用H5做页面,以JS为桥梁,调用Native方法,Native用WebView装载H5页面,控制H5页面,这样实现了H5与Native的交互。这样做的好处是各个端只需要一套页面,维护成本低,而且H5可以随便修改,App不用升级也可以更新,曲线实现了“热更新”。但是在性能上,肯定没有纯Native好,而且在体验上也没有Native那么好。
传统交互
Android上设置可以使用JS
WebView webView = (WebView) findViewById(R.id.web_view);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
Android调用JS方法
JS方法:
// 有返回值
function sum(num1, num2) {
return num1+num2;
}
// 无返回值
function say(message) {
document.getElementById("xx").value = message;
}
Android调用JS有返回值的方法:
webView.evaluateJavascript("sum(5, 4)", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Toast.makeText(MainActivity.this, "value is " + value, Toast.LENGTH_SHORT).show();
}
});
不过这个方法只有在Android 19以上才能使用!!
Android调用JS无返回值的方法:
webView.loadUrl("javascript:say('hello')");
比较简单!!
接下来我们看看JS怎么调用Android的方法。
JS调用Android方法
Android准备一个被JS调用的类
public class JsInterface {
@JavascriptInterface
public void showToast(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
}
将准备的类加入到WebView中:
webView.addJavascriptInterface(new JsInterface(), "control");
这样Android就准备好了!!看看JS怎么调用
<html>
<title>主页面</title>
<script type="text/javascript">
function showToast(message) {
window.control.showToast(message);
}
</script>
<body>
<input type="button" value="toast" onclick="showToast('hello')">
</body>
</html>
其中,control就是我们在WebView添加给JS执行类的别名(我这里看做别名),JS调用control时,相当于使用了JsInterface类。
缺点
我们在使用这种传统方式实现交互时,在Android 4.2以下,听说会有漏洞,会被XSS攻击,我也不是太懂安全方面的知识,见谅。在Android 4.2以上,已经有解决办法了,就是在JS要调用的方法上,加入 @JavascriptInterface 注解:
@JavascriptInterface
public void showToast(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
这种的话,兼容性就没那么强了,我们来看看其他解决Native和H5交互的方案!
JSBridge
JSBridge的主要流程:
- 在js脚本中把对应的方法名,参数等写成一个符合协议的uri,并且通过window.prompt方法发送给java层。
- 在java层的onJsPrompt方法中接受到对应的message之后,通过JsCallJava类进行具体的解析。
- 在JsCallJava类中,我们解析得到对应的方法名,参数等信息,并且在map中查找出对应的类的方法。
- 在得到对应的方法之后,就去调用它,最后调用loadUrl方法调用js的回调方法。
如果想详细了解,请参考 Android中JSBridge的原理与实现 。
当我查阅代码的时候,发现JSBridge原理不太一样了,不知道是不是改了想法
JSBridge的Github地址:https://github.com/lzyzsd/JsBridge
先看了一下 BridgeWebViewClient 类,因为这个类继承 WebViewClient,根据JSBridge原理,应该是重写onJsPrompt才对的,可发现代码重写的是shouldOverrideUrlLoading,这个方法主要是拦截了前端的url :
public class BridgeWebViewClient extends WebViewClient {
private BridgeWebView webView;
public BridgeWebViewClient(BridgeWebView webView) {
this.webView = webView;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
webView.flushMessageQueue();
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
...
}
既然这样,我们看看js怎么调用,主要是callHandler方法:
function callHandler(handlerName, data, responseCallback) {
_doSend({
handlerName: handlerName,
data: data
}, responseCallback);
}
调用了_doSend方法:
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
看到这里,我们应该猜出来,“这个版本”的JSBridge利用“iframe”标签,设置src属性(意思是加载目标地址),java层BridgeWebViewClient重写shouldOverrideUrlLoading拦截目标地址,并不是在重写的onJsPrompt中直接查找方法。
看了一下源码,被绕得有点晕。js调用时,不是直接说要调用java哪个方法,而是先告诉java,js队列中有消息了,让java去获取。java获取到了js的消息队列,然后遍历这个队列,找到每个message对应的是哪个handler,然后再调用,最后callback返回结果。可能对源码还不熟悉,目前还不不知道为什么不直接告诉java调用哪个方法或者handler,非要java去js获取?以后有时间再梳理!!
使用JSBridge
Android 调用 JS
// JS代码中注册
WebViewJavascriptBridge.registerHandler("JavaCallJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("从Java端接收的数据: = " + data);
responseCallback("Js端处理完了");
});
// Java代码中调用
mWebView.callHandler("JavaCallJs", "向Js端传入的数据", new CallBackFunction() {
@Override public void onCallBack(String data) {
// TODO Auto-generated method stub
Log.i(TAG, "Js端处理完后返回数据: " + data);
}
});
JS 调用 Android
// Java代码中
mWebView.registerHandler("JsCallJava", new BridgeHandler() {
@Override public void handler(String data, CallBackFunction function) {
Log.i(TAG, "Js端返回数据: " + data);
function.onCallBack("Java端处理完了");
}
});
//JS代码调用
WebViewJavascriptBridge.callHandler(
'JsCallJava', { 'param': '中文测试'},
function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
使用起来很简单!!
在网上查阅资料,发现这样的JSBridge也有一些问题:
所遇到的问题都是由iframe.src引起的:
1. iframe.src的url长度有大小限制,过大则直接会丢失
2. iframe.src的reload,即src重新复制,如果频率太快,则也会直接丢失
3. 解决完这两个问题后,在Java代码中,responseCallback这个HashMap里面的数据不用remove了,在handleReturnData里,因为上游延迟了发送。
主要的原因是iframe标签弊端太多导致的。
关于Hybrid开发暂时探索这么多!!!
感谢
好好和h5沟通!几种常见的hybrid通信方式
Android中JSBridge的原理与实现
Android JsBridge源码分析
JSBridge源码