概述

做过混合开发的人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包装一层Native。然后通过Bridge技术的js调用本地的库。

在讲JSBridge技术之前。我们来看一下传统的实现方式。

Android端

Native调JS

native调用js比較简单,仅仅要遵循:”javascript: 方法名(‘參数,须要转为字符串’)”的规则就可以。

在4.4之前,调用的方式:

// mWebView = new WebView(this);
mWebView.loadUrl("javascript: 方法名('參数,须要转为字符串')"); //ui线程中运行
runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript: 方法名('參数,须要转为字符串')");
Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show();
}
});

4.4以后(包含4.4)。使用下面方式:

mWebView.evaluateJavascript("javascript: 方法名('參数,须要转为字符串')", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//这里的value即为相应JS方法的返回值
}
});

说明:

  • 4.4之前Native通过loadUrl来调用JS方法,仅仅能让某个JS方法运行,可是无法获取该方法的返回值
  • 4.4之后,通过evaluateJavascript异步调用JS方法,而且能在onReceiveValue中拿到返回值
  • 不适合传输大量数据(大量数据建议用接口方式获取)
  • mWebView.loadUrl(“javascript: 方法名(‘參数,须要转为字符串’)”);函数需在UI线程运行,由于mWebView为UI控件

JS调Native

Js调用Native须要对WebView设置@JavascriptInterface注解,这里有个漏洞。后面会给大家说明。

要想js能够Native,须要对WebView设置下面属性。

WebSettings webSettings = mWebView.getSettings();
//Android容器同意JS脚本
webSettings.setJavaScriptEnabled(true);
//Android容器设置侨连对象
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

这里我们看到了getJSBridge()。Native中通过addJavascriptInterface加入暴露出来的JS桥对象,然后再该对象内部声明相应的API方法。

private Object getJSBridge(){
Object insertObj = new Object(){
@JavascriptInterface
public String foo(){
return "foo";
} @JavascriptInterface
public String foo2(final String param){
return "foo2:" + param;
} };
return insertObj;
}

那么Html怎么调用Native的方法呢?

//调用方法一
window.JSBridge.foo(); //返回:'foo'
//调用方法二
window.JSBridge.foo2('test');//返回:'foo2:test'

说明:

  • 在Android4.2以上(api17后),暴露的api要加上注解@JavascriptInterface,否则会找不到方法。

  • 在api17曾经,addJavascriptInterface有风险,hacker能够通过反编译获取Native注冊的Js对象。然后在页面通过反射Java的内置 静态类。获取一些敏感的信息和破坏
  • JS调用Native暴露的api,而且能得到相应返回值

注:说到WebView中接口隐患的问题,这里大家能够參考WebViw漏洞利用,只是Android发展到如今,这个漏洞基本没有了。

iOS端

Native调JS

Native调用js的方法比較简单。Native通过stringByEvaluatingJavaScriptFromString调用Html绑定在window上的函数。只是应注意Oc和Swift的写法。

//Swift
webview.stringByEvaluatingJavaScriptFromString("方法名(參数)")
//OC
[webView stringByEvaluatingJavaScriptFromString:@"方法名(參数);"];

说明:

  • Native调用JS方法时,能拿到JS方法的返回值
  • 不适合传输大量数据(大量数据建议用接口方式获取)

JS调Native

Native中通过引入官方提供的JavaScriptCore库(iOS7以上),然后能够将api绑定到JSContext上(然后Html中JS默认通过window.top.*可调用)。

引入官方的库文件

#import <JavaScriptCore/JavaScriptCore.h>

Native注冊api函数(OC)

-(void)webViewDidFinishLoad:(UIWebView *)webView{
[self hideProgress];
[self setJSInterface];
} -(void)setJSInterface{ JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // 注冊名为foo的api方法
context[@"foo"] = ^() { //获取參数
NSArray *args = [JSContext currentArguments];
NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
//做一些自己的逻辑
//返回一个值 'foo:'+title
return [NSString stringWithFormat:@"foo:%@", title];
};
}

Html中JS调用Native方法

window.top.foo('test');

说明:

  • iOS7才出现这样的方式,在这之前,js无法直接调用Native,仅仅能通过JSBridge方式简单介绍调用
  • JS能调用到已经暴露的api,而且能得到相应返回值
  • iOS原生本身是无法被JS调用的,可是通过引入官方提供的第三方”JavaScriptCore”,就可以开放api给JS调用

JSBridge

什么是JSBridge

JSBridge:听其取名就是js和Native之前的桥梁,而实际上JSBridge确实是JS和Native之前的一种通信方式。简单的说,JSBridge就是定义Native和JS的通信,Native仅仅通过一个固定的桥对象调用JS,JS也仅仅通过固定的桥对象调用Native。JSBridge还有一个叫法及大家熟知的Hybrid app技术。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGlhbmd6aGlob25nOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

流程:H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调。

我们前面讲过了原生的WebView/UIWebView控件已经能够和Js实现数据通信了。那为什么还要JSBridge呢?

事实上使用JSBridge有非常多方面的考虑:

  • Android4.2下面,addJavascriptInterface方式有安全漏掉
  • iOS7下面,JS无法调用Native
  • url scheme交互方式是一套现有的成熟方案,能够完美兼容各种版本号。对曾经老版本号技术的兼容。

url scheme

url scheme是一种相似于url的链接,是为了方便app直接互相调用设计的。详细来讲假设是系统的url scheme,则打开系统应用,否则找看是否有app注冊这样的scheme,打开相应app。

注:这样的scheme必须原生app注冊后才会生效。

而在我们实际的开发中,app不会注冊相应的scheme,而是由前端页面通过某种方式触发scheme(如用iframe.src),然后Native用某种方法捕获相应的url触发事件,然后拿到当前的触发url,依据定义好的协议,分析当前触发了那种方法。

JSBridge技术实现

要实现JSBridge,我们须要按下面步骤分析:

  • 第一步:设计出一个Native与JS交互的全局桥对象
  • 第二步:JS怎样调用Native
  • 第三步:Native怎样得知api被调用
  • 第四步:分析url-參数和回调的格式
  • 第五步:Native怎样调用JS
  • 第六步:H5中api方法的注冊以及格式

JSBridge的完整流程可总结为:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGlhbmd6aGlob25nOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

设计Native与JS交互的全局桥对象

我们规定,JS和Native之间的通信必须通过一个H5全局对象JSbridge来实现。该对象有例如以下特点:

该对象名为”JSBridge”,是H5页面中全局对象window的一个属性。形如:

var JSBridge = window.JSBridge || (window.JSBridge = {});

该对象有例如以下方法:

  • registerHandler( String,Function )H5调用注冊本地JS方法,注冊后Native可通过JSBridge调用。

    调用后会将方法注冊到本地变量messageHandlers 中。

  • callHandler( String,JSON,Function )H5调用 调用原生开放的api,调用后实际上还是本地通过url scheme触发。调用时会将回调id存放到本地变量responseCallbacks中
  • _handleMessageFromNative( JSON )Native调用 原生调用H5页面注冊的方法,或者通知H5页面运行回调方法

JS调用Native

我们定义好了全局桥对象,能够通过它的callHandler方法来调用原生的api。

callHandler函数内部实现过程

在运行callHandler时,内部经历了下面步骤:

  1. 推断是否有回调函数,假设有,生成一个回调函数id,并将id和相应回调加入进入回调函数集合responseCallbacks中。
  2. 通过特定的參数转换方法,将传入的数据,方法名一起,拼接成一个url scheme
//url scheme的格式如
//基本实用信息就是后面的callbackId,handlerName与data
//原生捕获到这个scheme后会进行分析
var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
  1. 使用内部早就创建好的一个隐藏iframe来触发scheme
//创建隐藏iframe过程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe); //触发scheme
messagingIframe.src = uri;

注:正常来说是能够通过window.location.href达到发起网络请求的效果的。可是有一个非常严重的问题,就是假设我们连续多次改动window.location.href的值,在Native层仅仅能接收到最后一次请求,前面的请求都会被忽略掉。

所以JS端发起网络请求的时候,须要使用iframe。这样就能够避免这个问题。

Native通知api被调用

上一步。我们已经成功在H5页面中触发scheme,那么Native怎样捕获scheme被触发呢?

依据系统不同,Android和iOS分别有自己的处理方式。

Android

在Android中(WebViewClient里),通过shouldoverrideurlloading能够捕获到url scheme的触发。

public boolean shouldOverrideUrlLoading(WebView view, String url){
//假设返回false。则WebView处理链接url。假设返回true,代表WebView依据程序来运行url
return true;
}

iOS

iOS中,UIWebView有个特性:在UIWebView内发起的全部网络请求,都能够通过delegate函数在Native层得到通知。

这样,我们能够在webview中捕获url scheme的触发(原理是利用 shouldStartLoadWithRequest)

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = [request URL]; NSString *requestString = [[request URL] absoluteString];
//获取利润url scheme后自行进行处理

分析url-參数和回调的格式

在前面的步骤中,Native已经接收到了JS调用的方法,那么接下来,原生就应该依照定义好的数据格式来解析数据了,Native接收到Url后,能够依照这样的格式将回调參数id、api名、參数提取出来,然后按例如以下步骤进行。

  1. 依据api名,在本地找寻相应的api方法,而且记录该方法运行完后的回调函数id
  2. 依据提取出来的參数,依据定义好的參数进行转化
  3. 原生本地运行相应的api功能方法
  4. 功能运行完成后,找到这次api调用相应的回调函数id,然后连同须要传递的參数信息,组装成一个JSON格式的參数
  5. 通过JSBridge通知H5页面回调

Native调用JS

到了这一步,就该Native通过JSBridge调用H5的JS方法或者通知H5进行回调了。当中的messageJSON数据格式依据两种不同的类型。

JSBridge._handleMessageFromNative(messageJSON);     

Native通知H5页面进行回调

数据格式为: Native通知H5回调的JSON格式。

Native主动调用H5方法

Native主动调用H5方法时,数据格式是:{handlerName:api名,data:数据,callbackId:回调id}:

  • handlerName String型 须要调用的,h5中开放的api的名称
  • data JSON型 须要传递的数据,固定为JSON格式(由于我们固定H5中注冊的方法接收的第一个參数必须是JSON,第二个是回调函数)
  • callbackId String型 原生生成的回调函数id,h5运行完成后通过url scheme通知原生api成功运行,并传递參数

H5中api方法的注冊以及格式

前面有提到Native主动调用H5中注冊的api方法,那么h5中怎么注冊供原生调用的api方法呢?

JSBridge.registerHandler('testH5Func',function(data,callback){
alert('測试函数接收到数据:'+JSON.stringify(data));
callback&&callback('測试回传数据...');
});

如上代码,当中第一个data即原生传过来的数据,第二个callback是内部封装过一次的,运行callback后会触发url scheme,通知原生获取回调信息.

完好JSBridge方案

github上有一个开源项目,它里面的JSBridge做法在iOS上进一步优化了,所以參考他的做法,这里进一步进行了完好。地址marcuswestin/WebViewJavascriptBridge

JSBridge对象图解:

JSBridge实现完整流程:

总结

那么我们在实际的开发中,怎样针对Android和iOS的不同情况,统一出一种完整的方案。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGlhbmd6aGlob25nOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

另类实现:不採用url scheme方式

前面提到的JSBridge都是基于url scheme的,但事实上假设不考虑Android4.2下面,iOS7下面,事实上也能够用还有一套方案的。

  • Native调用JS的方法不变
  • JS调用Native是不再通过触发url scheme,而是採用自带的交互

    详细来讲:

    Android中,原生通过 addJavascriptInterface开放一个统一的api给JS调用,然后将触发url scheme步骤变为调用这个api,其余步骤不变。

OS中,原生通过JavaScriptCore里面的方法来注冊一个统一api,其余和Android中一样。

JSBridge深度剖析的更多相关文章

  1. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  2. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  3. Objective-C类成员变量深度剖析

    目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objec ...

  4. 大众点评开源分布式监控平台 CAT 深度剖析

    一.CAT介绍 CAT系统原型和理念来源于eBay的CAL的系统,CAT系统第一代设计者吴其敏在eBay工作长达十几年,对CAL系统有深刻的理解.CAT不仅增强了CAL系统核心模型,还添加了更丰富的报 ...

  5. 深度剖析WordPress主题结构(转)

    利用强大的技术,可以把基于wordpress的网站做成各种各样的形式,这除了要求wordpress主题开发人员精通html,PHP,JS,CSS等技术,还需要开发者掌握WordPress主题的框架. ...

  6. LCD深度剖析

    LCD 深度剖析 来源:http://blog.csdn.net/hardy_2009/article/details/6922900 http://blog.csdn.net/jaylondon/a ...

  7. WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)

    原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...

  8. 大兴雷克萨斯深度剖析2013款LS460L_深圳大兴雷克萨斯_太平洋汽车网

    大兴雷克萨斯深度剖析2013款LS460L_深圳大兴雷克萨斯_太平洋汽车网 大兴雷克萨斯深度剖析2013款LS460L

  9. Java反射机制剖析(四)-深度剖析动态代理原理及总结

    动态代理类原理(示例代码参见java反射机制剖析(三)) a)  理解上面的动态代理示例流程 a)  理解上面的动态代理示例流程 b)  代理接口实现类源代码剖析 咱们一起来剖析一下代理实现类($Pr ...

随机推荐

  1. 关于sum.misc.Unsafe的一点记录

    最近在读Undertow的源码,对于ServletPrintWriterDelegate类的实现比较感兴趣,做个记录. 源码github坐标:ServletPrintWriterDelegate.ja ...

  2. Event Loop详解

    1.进程,单线程与多线 进程: 运行的程序就是一个进程,比如你正在运行的浏览器,它会有一个进程. 线程: 程序中独立运行的代码段. 一个进程由单个或多个线程组成,线程是负责执行代码的. 2.单线程与多 ...

  3. BZOJ-3190 [JLOI2013]赛车

    转成二元一次不等式组,然后半平面交. #include <cstdlib> #include <cstdio> #include <cmath> #include ...

  4. BZOJ 3625 [Codeforces Round #250]小朋友和二叉树 ——NTT 多项式求逆 多项式开根

    生成函数又有奇妙的性质. $F(x)=C(x)*F(x)*F(x)+1$ 然后大力解方程,得到一个带根号的式子. 多项式开根有解只与常数项有关. 发现两个解只有一个是成立的. 然后多项式开根.求逆. ...

  5. shell总结

    1. shell脚本的变量赋值 变量赋值语句中的等号左右不能有空格 即 a = 4 //错误 a=4   //正确 2. shell脚步的执行需要权限 chmod +x shell.sh ./shel ...

  6. JavaScript 笔记(1) -- 基础 & 函数 & 循环 & ...

    目录(代码编写): 显示数据 语法 变量 & 变量类型 对象 函数 事件 字符串 运算符 条件语句 循环语句 Break 和 Continue 使用 JS 近两年,现整理下一些基本: HTML ...

  7. Vim查找替换及正则表达式的使用

    原文地址:http://tanqisen.github.io/blog/2013/01/13/vim-search-replace-regex/ 简单替换表达式 :[range]s/from/to/[ ...

  8. Qt5网络请求使用及WebRequest函数

    Qt5模拟curl进行HTTP的head请求, curl -I <url> : #include <QtCore> #include <QNetworkReply> ...

  9. c#反射,委托,事件

    1.反射,通过类名来实例化类 //用构造函数动态生成对象: Type t = typeof(NewClassw); Type[] pt = ]; pt[] = typeof(string); pt[] ...

  10. JavaScript闭包 循环输出i

    html <body> <p>产品一</p> <p>产品二</p> <p>产品三</p> <p>产品四& ...