作过混合开发的不少人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包了一层Native,而后经过Bridge技术使得js能够调用视频、位置、音频等功能。本文就是介绍这层Bridge的交互原理,经过阅读本文你能够了解到js与ios及android底层的通信原理及JSBridge的封装技术及调试方法。javascript
下面分别介绍IOS和Android与Javascript的底层交互原理java
在讲解原理以前,首先来了解下iOS的UIWebView组件,先来看一下苹果官方的介绍:android
You can use the UIWebView class to embed web content in your application. To do so, you simply create a UIWebView object, attach it to a window, and send it a request to load web content. You can also use this class to move back and forward in the history of webpages, and you can even set some web content properties programmatically.ios
上面的意思是说UIWebView是一个可加载网页的对象,它有浏览记录功能,且对加载的网页内容是可编程的。说白了UIWebView有相似浏览器的功能,咱们使用能够它来打开页面,并作一些定制化的功能,如可让js调某个方法能够取到手机的GPS信息。git
但须要注意的是,Safari浏览器使用的浏览器控件和UIwebView组件并非同一个,二者在性能上有很大的差距。幸运的是,苹果发布iOS8的时候,新增了一个WKWebView组件,若是你的APP只考虑支持iOS8及以上版本,那么你就可使用这个新的浏览器控件了。github
原生的UIWebView类提供了下面一些属性和方法web
属性:objective-c
方法:编程
Native调用Javascript语言,是经过UIWebView
组件的stringByEvaluatingJavaScriptFromString
方法来实现的,该方法返回js脚本的执行结果。swift
// Swift
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];复制代码
从上面代码能够看出它其实就是调用了window
下的一个对象,若是咱们要让native来调用咱们js写的方法,那这个方法就要在window
下能访问到。但从全局考虑,咱们只要暴露一个对象如JSBridge对native调用就行了,因此在这里能够对native的代码作一个简单的封装:
//下面为伪代码
webview.setDataToJs(somedata);
webview.setDataToJs = function(data) {
webview.stringByEvaluatingJavaScriptFromString("JSBridge.trigger(event, data)")
}复制代码
反过来,Javascript调用Native,并无现成的API能够直接拿来用,而是须要间接地经过一些方法来实现。UIWebView有个特性:在UIWebView内发起的全部网络请求,均可以经过delegate函数在Native层获得通知。这样,咱们就能够在UIWebView内发起一个自定义的网络请求,一般是这样的格式:jsbridge://methodName?param1=value1¶m2=value2
因而在UIWebView的delegate函数中,咱们只要发现是jsbridge://开头的地址,就不进行内容的加载,转而执行相应的调用逻辑。
发起这样一个网络请求有两种方式:1. 经过localtion.href;2. 经过iframe方式;
经过location.href有个问题,就是若是咱们连续屡次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。
使用iframe方式,以唤起Native APP的分享组件为例,简单的封闭以下:
var url = 'jsbridge://doAction?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);复制代码
而后Webview就能够拦截这个请求,而且解析出相应的方法和参数。以下代码所示:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
print("shouldStartLoadWithRequest")
let url = request.URL
let scheme = url?.scheme
let method = url?.host
let query = url?.query
if url != nil && scheme == "jsbridge" {
print("scheme == \(scheme)")
print("method == \(method)")
print("query == \(query)")
switch method! {
case "getData":
self.getData()
case "putData":
self.putData()
case "gotoWebview":
self.gotoWebview()
case "gotoNative":
self.gotoNative()
case "doAction":
self.doAction()
case "configNative":
self.configNative()
default:
print("default")
}
return false
} else {
return true
}
}复制代码
在android中,native与js的通信方式与ios相似,ios中的经过schema方式在android中也是支持的。
目前在android中有三种调用native的方式:
1.经过schema方式,使用shouldOverrideUrlLoading
方法对url协议进行解析。这种js的调用方式与ios的同样,使用iframe来调用native代码。
2.经过在webview页面里直接注入原生js代码方式,使用addJavascriptInterface
方法来实现。
在android里实现以下:
class JSInterface {
@JavascriptInterface //注意这个代码必定要加上
public String getUserData() {
return "UserData";
}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");复制代码
上面的代码就是在页面的window对象里注入了AndroidJS
对象。在js里能够直接调用
alert(AndroidJS.getUserData()) //UserDate复制代码
3.使用prompt,console.log,alert方式,这三个方法对js里是属性原生的,在android webview这一层是能够重写这三个方法的。通常咱们使用prompt,由于这个在js里使用的很少,用来和native通信反作用比较少。
class YouzanWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 这里就能够对js的prompt进行处理,经过result返回结果
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
}
}复制代码
在android里是使用webview的loadUrl
进行调用的,如:
// 调用js中的JSBridge.trigger方法
webView.loadUrl("javascript:JSBridge.trigger('webviewReady')");复制代码
上面咱们了解了js与native通信的底层原理,因此咱们能够封装一个基础的通信方法doCall
来屏蔽android与ios的差别。
YouzanJsBridge = {
doCall: function(functionName, data, callback) {
var _this = this;
// 解决连续调用问题
if (this.lastCallTime && (Date.now() - this.lastCallTime) < 100) {
setTimeout(function() {
_this.doCall(functionName, data, callback);
}, 100);
return;
}
this.lastCallTime = Date.now();
data = data || {};
if (callback) {
$.extend(data, { callback: callback });
}
if (UA.isIOS()) {
$.each(data, function(key, value) {
if ($.isPlainObject(value) || $.isArray(value)) {
data[key] = JSON.stringify(value);
}
});
var url = Args.addParameter('youzanjs://' + functionName, data);
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);
} else if (UA.isAndroid()) {
window.androidJS && window.androidJS[functionName] && window.androidJS[functionName](JSON.stringify(data));
} else {
console.error('未获取platform信息,调取api失败');
}
}
}复制代码
上面android端咱们使用了addJavascriptInterface方法来注入一个AndroidJS对象。
在项目的实践中,咱们逐渐抽象出一些通用的方法,这些方法基本上都是能够知足项目的需求。以下所示:
使用场景:H5须要从Native APP获取某些数据的时候,能够调用这个方法。
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
datatype | String | 是 | userInfo | 数据类型 |
callback | Function | 是 | 回调函数 | |
extra | Object | 否 | 传递给Native APP的数据对象 |
示例代码:
JSBridge.getData('userInfo',function(data) {
console.log(data);
});复制代码
使用场景:H5告诉Native APP一些数据,能够调用这个方法。
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
datatype | String | 是 | userInfo | 数据类型 |
data | Object | 是 | { username: 'zhangsan', age: 20 } | 传递给Native APP的数据对象 |
示例代码:
JSBridge.putData('userInfo', {
username: 'zhangsan',
age: 20
});复制代码
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
url | String | 是 | www.youzan.com | 网页连接地址,通常都只要传递URL参数就能够了 |
page | String | 否 | web | 网页page类型,默认为web |
data | Object | 否 | 额外参数对象 |
示例代码:
// 示例1:打开一个网页
JSBridge.gotoWebview('http://www.youzan.com');
// 示例2:打开一个网页,而且传递额外的参数给Native APP
JSBridge.gotoWebview('http://www.youzan.com', 'goodsDetail', {
goods_id: 10000,
title: '这是商品的标题',
desc: '这是商品的描述'
});复制代码
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
page | String | 是 | loginPage | Native页面标示符,例如loginPage |
data | Object | 否 | { username: 'zhangsan', age: 20 } | 额外参数对象 |
示例代码:
// 示例1:打开Native APP登陆页面
JSBridge.gotoNative('loginPage');
// 示例2:打开Native APP登陆页面,而且传递用户名给Native APP
JSBridge.gotoNative('loginPage', {
username: '张三'
});复制代码
参数 | 类型 | 是否必须 | 示例值 | 说明 |
---|---|---|---|---|
action | String | 是 | copy | 操做功能类型,例如分享、复制 |
data | Object | 否 | { content: '这是要复制的内容' } | 额外参数 |
示例代码:
// 示例1:调用Native APP复制一段文本到剪切板
JSBridge.doAction('copy', {
content: '这是要复制的内容'
});
// 示例2:调用Native APP的分享组件,分享当前网页到微信
JSBridge.doAction('share', {
title: '分享标题',
desc: '分享描述',
link: 'http://www.youzan.com',
imgs_url: 'http://wap.koudaitong.com/v2/common/url/create?type=homepage&index%2Findex=&kdt_id=63077&alias=63077'
});复制代码
(1)首先须要打开Safari的调试模式,在Safari的菜单中,选择“Safari”→“Preference”→“Advanced”,勾选上“Show Develop menu in menu bar”选项,以下图所示。
(4)Safari链接上UIWebView以后,咱们就能够直接在Safari中直接修改HTML、CSS,以及调试Javascript。
本文由 @kk @劲风 共同创做,首发于有赞技术博客: tech.youzan.com/jsbridge/