完整解决方案:H5与Native交互之JSBridge技术
优采云 发布时间: 2022-09-24 17:10完整解决方案:H5与Native交互之JSBridge技术
许多做过混合开发的人都知道 Ionic 和 PhoneGap 等框架。这些框架在web的基础上包裹了一层Native,然后利用Bridge技术让js调用视频、位置、音频等功能。本文就是介绍Bridge这一层的交互原理。通过阅读本文,可以了解js、ios和android的底层通信原理,以及JSBridge的封装技术和调试方法。
一、原则
下面介绍IOS与Android和Javascript的底层交互原理
IOS
在解释原理之前,我们先来了解一下iOS的UIWebView组件。我们来看看苹果官方的介绍:
您可以使用 UIWebView 类在您的应用程序中嵌入 Web 内容。为此,您只需创建一个 UIWebView 对象,将其附加到窗口,并向其发送加载 Web 内容的请求。您还可以使用该类在网页历史记录中前后移动,甚至可以通过编程方式设置一些网页内容属性。
上面的意思是UIWebView是一个可以加载网页的对象,它具有浏览记录功能,加载的网页内容是可编程的。说白了,UIWebView 有一个类似浏览器的功能。我们可以用它来打开页面,做一些自定义的功能,比如让js调用某个方法来获取手机的GPS信息。
但需要注意的是,Safari浏览器使用的浏览器控件和UIwebView组件并不相同,两者在性能上存在较大差距。幸运的是,苹果在发布 iOS8 时,添加了一个 WKWebView 组件。如果你的APP只考虑支持iOS8及以上,那么你可以使用这个新的浏览器控件。
原生 UIWebView 类提供以下属性和方法
属性:
方法:
Native(Objective-C 或 Swift)调用 Javascript 方法
Native通过UIWebView组件的stringByEvaluatingJavaScriptFromString方法调用Javascript语言,返回js脚本的执行结果。
// Swift
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
从上面的代码可以看出,它实际上是调用了窗口下的一个对象。如果我们想让native调用我们js写的方法,那么这个方法必须是window下可以访问的。但是从全局来看,我们只需要暴露一个JSBridge这样的对象就可以调用native,所以这里我们可以对native代码做一个简单的封装:
//下面为伪代码
webview.setDataToJs(somedata);
webview.setDataToJs = function(data) {
webview.stringByEvaluatingJavaScriptFromString("JSBridge.trigger(event, data)")
}
Javascript 调用本机(Objective-C 或 Swift)方法
反过来,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类似,android中也支持ios中的schema方式。
javascript 调用原生方法
目前android中调用native的方式有3种:
1.使用shouldOverrideUrlLoading方法通过schema方法解析url协议。这个js的调用方式和ios一样,都是用iframe调用原生代码。
2.通过将原生js代码直接注入webview页面,使用addJavascriptInterface方法实现。
在android中的实现如下:
class JSInterface {
@JavascriptInterface //注意这个代码一定要加上
public String getUserData() {
return "UserData";
}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");
以上代码将AndroidJS对象注入到页面的window对象中。可以直接在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) {
}
}
Native 调用 javascript 方法
在android中,使用webview的loadUrl调用,如:
// 调用js中的JSBridge.trigger方法
webView.loadUrl("javascript:JSBridge.trigger('webviewReady')");
二、库的封装js调用原生封装
上面我们了解了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对象。
项目常用方法抽象
在项目的实践中,我们逐渐抽象出一些通用的方法,基本可以满足项目的需要。如下:
1.getData(datatype, callback, extra) H5从Native APP获取数据
使用场景:当H5需要从Native APP获取一些数据时,可以调用该方法。
参数类型是否必填示例值说明
数据类型
字符串
是的
用户信息
数据类型
回调
功能
是的
回调函数
额外的
对象
没有
传递给 Native APP 的数据对象
示例代码:
JSBridge.getData('userInfo',function(data) {
console.log(data);
});
2.putData(datatype, data) H5告诉Native APP一些数据
使用场景:H5告诉Native APP一些数据,可以调用这个方法。
参数类型是否必填示例值说明
数据类型
字符串
是的
用户信息
数据类型
数据
对象
是的
{ 用户名:'zhangsan',年龄:20 }
传递给 Native APP 的数据对象
示例代码:
JSBridge.putData('userInfo', {
username: 'zhangsan',
age: 20
});
3.gotoWebview(url, page, data) 原生APP打开一个新的Webview窗口并打开对应的网页参数类型必填示例值说明
网址
字符串
是的
网页链接地址,一般只要传URL参数就可以了
页面
字符串
没有
网络
网页类型,默认为web
数据
对象
没有
额外的参数对象
示例代码:
// 示例1:打开一个网页
JSBridge.gotoWebview('http://www.youzan.com');
// 示例2:打开一个网页,并且传递额外的参数给Native APP
JSBridge.gotoWebview('http://www.youzan.com', 'goodsDetail', {
goods_id: 10000,
title: '这是商品的标题',
desc: '这是商品的描述'
});
4.gotoNative(page, data) 从H5页面跳转到Native APP类型的native接口参数必填示例值说明
页面
字符串
是的
登录页面
本机页面标识符,例如 loginPage
数据
对象
没有
{ 用户名:'zhangsan',年龄:20 }
额外的参数对象
示例代码:
// 示例1:打开Native APP登录页面
JSBridge.gotoNative('loginPage');
// 示例2:打开Native APP登录页面,并且传递用户名给Native APP
JSBridge.gotoNative('loginPage', {
username: '张三'
});
5.doAction(action, data) 函数上的一些动作参数 Type 是否必填 示例值 说明
动作
字符串
是的
复制
核心方法:PHP如何使用curl实现数据抓取
PHP如何使用curl实现数据抓取介绍如何使用curl实现数据抓取?以下是为您提供的实现代码。您可以参考代码以获取更多详细信息。代理服务器代理服务器地址httpwwwcnproxycomproxy1htmlHongKongChina的速度比较好 curl_setoptchCURLOPT_PROXYcu
rl_setoptchCURLOPT_URLurlcurl_setoptchCURLOPT_RETURNTRANSFER1returndontprintcurl_setoptchCURLOPT_TIMEOUT30设置超时时间curl_setoptchCURLOPT_USERAGENTMozilla40compatibleMSIE501WindowsNT50curl_setoptchCURLOPT_FOLLOWLOCATION1302redirectcurl_setoptchCURLOPT_MAXREDIRS7HTTp定向级别curl_multi_add_handlemhch把curlresource放进multicurlhandler里handle[i]ch执行domrccurl_multi_execmhrunningifwait_usec0每个connect要间隔多久usleepwa
it_usec250000025secwhilemrcCURLM_CALL_MULTI_PERFORMwhilerunningmrcCURLM_OKifcurl_multi_selectmh-1domrccurl_multi_execmhrunningwhilemrcCURLM_CALL_MULTI_PERFORM读取资料foreachhandleasichcontentcurl_multi_getcontentchdata[i]curl_errnoch0contentfalse移除handleforeachhandleaschcurl_multi_remove_handlemhchcurl_multi_closemhreturndataurlsarrayhttpmapbaiducomreasync_get_urlurlsechore[0]