现在最新的xcode都只支持iOS8.0以上的版本了,所以iOS应该直接使用性能高、功能多的WKWebView接口。UIWebView的注入对象方式需要依赖KVC,且有坑,不建议使用。本文都以WKWebView的接口来设计。
数据从native传递到js只有一种方法:
[webView evaluateJavaScript:@"some-js-code" completionHandler:nil]
webView.loadUrl("javascript:some-js-code")
iOS和Android都使用注入对象的方式给js调用,不通过alert
等方式来从js传递信息给native。不是不行,而是不够优雅,也不够方便。
如果这个交互框架是为了混合开发,那么应该约定不存在由native主动向js传递数据的情况,也就是交互都是由js发起的。如果需要native不断向js传递数据,那也应该由js先通知native“可以开始传了”。
传递的是个json对象:
{
"action": "action_name",
"id": "random_value",
"callback": "function_name", // optional
"data": {...} // optional
}
Math.random()
WebView传过来的也是个json对象:
{
"action": "action_name",
"id": "random_value"
"result": "ok"
"data": {...} // optional
}
在ios和android,注入的对象名字都是由native决定的,每个项目自己约定就好了。示例是使用liuhxJsFramework
。
注入的对象在android是全局变量,也即是window
的成员变量。在ios是window.webkit.messageHandlers
的成员变量。
在ios,传递数据的函数名是固定的,只能是postMessage
。android是由native自己定的。为了统一,让android也叫postMessage
会好点。
参数在ios可以传各种基本类型,android只能传String。
所以需要用一个函数callNative
来统一封装这些差异:
function callNative(object) {
if (window.liuhxJsFramework) {
// Android
window.liuhxJsFramework.postMessage(JSON.stringify(object))
} else if (window.webkit && window.webkit.messageHandlers.liuhxJsFramework) {
// iOS WKWebView
window.webkit.messageHandlers.liuhxJsFramework.postMessage(object)
} else {
alert("此功能需要在WebView中使用!")
}
}
function doGetIp() {
var info = {
"action": "getIp",
"id": Math.random().toString(),
"callback": "getIpCallback"
}
callNative(info)
}
function getIpCallback(object) {
if (object.result === 'ok') {
document.getElementById('ip_result').innerHTML = object.data.ip;
} else {
document.getElementById('ip_result').innerHTML = object.result;
}
}
function callNative(object) {
if (window.liuhxJsFramework) {
// Android
window.liuhxJsFramework.postMessage(JSON.stringify(object))
} else if (window.webkit && window.webkit.messageHandlers.liuhxJsFramework) {
// iOS WKWebView
window.webkit.messageHandlers.liuhxJsFramework.postMessage(object)
} else {
alert("此功能需要在WebView中使用!")
}
}
完整的js示例代码放在 https://github.com/hursing/js-webview/blob/master/android/app/src/main/assets/test-framework.html
iOS和android的设计是一致的,只是使用语言和api不同。思路如下:
action
都有独立的handler
来负责处理和回复。为此设计一个接口(ios的protocol和java的interface)来表示它,每添加一种action
就是多一个它的子类。它有2个职责: action
名action
相关的整个json对象,按照实际需求做处理,如需要,生成结果json对象,再通过WebView回复给js。WebViewInjector
来管理所有跟注入有关的逻辑,它的实例的生命周期和WebView几乎相同,并单向依赖WebView。具体职责是: handler
实例handler
来处理DefaultHandler
回复result
为unsupported
。// ViewController.m
self.injector = [[WebViewInjector alloc] init];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:self.injector name:@"liuhxJsFramework"];
config.userContentController = controller;
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.injector injectToWebView:self.webView];
[self.view addSubview:self.webView];
// WebViewInjector.m
#pragma mark - WKScriptMessageHandler methods
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSDictionary *body = message.body;
if (![body isKindOfClass:[NSDictionary class]]) {
return;
}
NSString *action = body[@"action"];
id handler = s_jsHandlers[action];
if (!handler) {
handler = [DefaultHandler sharedInstance];
}
[handler handleJsFromWebView:self.webView info:body];
}
// JsHandler.m
void invokeCallback(WKWebView *webView, NSDictionary *fromJs, NSMutableDictionary *toJs) {
NSString *callback = fromJs[@"callback"];
if (!callback) {
return;
}
toJs[@"id"] = fromJs[@"id"];
toJs[@"action"] = fromJs[@"action"];
NSData *data = [NSJSONSerialization dataWithJSONObject:toJs options:0 error:nil];
NSString *resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *js = [NSString stringWithFormat:@"%@(%@)", callback, resultString];
[webView evaluateJavaScript:js completionHandler:nil];
}
// MainActivity.java
mInjector = new WebViewInjector();
mInjector.injectToWebView(mWebView);
// WebViewInjector.java
@SuppressLint("SetJavaScriptEnabled")
public void injectToWebView(WebView webView) {
mWebView = webView;
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(this, "liuhxJsFramework");
}
@JavascriptInterface
public void postMessage(String jsonString) {
try {
// 如果有需要,可以使用GSON或fastjson转换成bean
JSONObject object = new JSONObject(jsonString);
String action = object.getString("action");
JsHandler handler = sHandlerMap.get(action);
if (handler == null) {
handler = sDefaultHandler;
}
handler.handleJs(mWebView, object);
} catch (Exception e) {
e.printStackTrace();
}
}
static void invokeCallback(final WebView webView, JSONObject fromJs, JSONObject toJs) {
String callback;
try {
callback = fromJs.getString("callback");
if (callback.isEmpty()) {
return;
}
toJs.put(sKeyId, fromJs.getString(sKeyId));
toJs.put(sKeyAction, fromJs.getString(sKeyAction));
} catch (Exception e) {
e.printStackTrace();
return;
}
final String url = "javascript:" + callback + "(" + toJs.toString() + ")";
webView.post(new Runnable() {
@Override
public void run() {
webView.loadUrl(url);
}
});
}
请查看 https://github.com/hursing/js-webview
说明:
test-framework.html
ios截图: