前端技术演进(二):前端与协议

4,363 阅读22分钟
这个来自之前做的培训,删减了一些业务相关的,参考了很多资料(参考资料列表),谢谢前辈们,么么哒 😘

目前前后端的通信一般都是通过协议来完成的,这里介绍和前端开发相关的各类协议。

HTTP

HTTP 协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是万维网的数据通信的基础。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法,现在基本上什么类型的文件都可以传输了,比如图片、CSS、JS、数据报文等。

HTTP一般基于TCP/IP通信协议来传递数据,它有如下特点:

  1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  2. 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  3. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  4. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
  5. 支持B/S及C/S模式。

HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息。一个完整的URL一般包括以下几部分:

协议 域名 端口 虚拟目录 文件名 参数

比如:http://test.google.com:80/test/test.html?query=admin#home

请求消息Request

通常一个HTTP请求消息包含如下内容:请求行、请求头、空行、消息主体。

image.png | center | 466x165

比如:

 GET /books/?sex=man&name=Professional HTTP/1.1
 Host: www.example.com
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
 Gecko/20050225 Firefox/1.0.1
 Connection: Keep-Alive
  • 请求行:GET /books/?sex=man&name=Professional HTTP/1.1 用来说明请求类型,要访问的资源以及所使用的HTTP版本。比较常见的请求类型有GET,POST,PUT,DELETE,OPTIONS等。
  • 请求头:从第二行起为请求头部,用来说明服务器要使用的附加信息。
  • 空行:请求头部后面的空行是必须的,即使请求数据为空,也必须有空行。
  • 请求数据:也叫请求主体,可以添加任意的其他数据,这个例子的请求数据为空。

带有请求数据的POST请求:

 POST / HTTP/1.1
 Host: www.example.com
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
 Gecko/20050225 Firefox/1.0.1
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 40
 Connection: Keep-Alive

 sex=man&name=Professional  

响应消息Response

一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。通常一个HTTP请求消息包含如下内容:状态行、消息报头、空行、响应正文。

比如:

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>
  • 状态行:由HTTP协议版本号, 状态码, 状态消息 三部分组成。
  • 消息报头:用来说明客户端要使用的一些附加信息。
  • 空行:消息报头后面的空行是必须的。
  • 响应正文,服务器返回给客户端的文本信息。

状态码

状态码由三位数字组成,第一个数字定义了响应的类别,共分五种类别:

  • 1xx:指示信息--表示请求已接收,继续处理。
  • 2xx:成功--表示请求已被成功接收、理解、接受。
  • 3xx:重定向--要完成请求必须进行更进一步的操作。
  • 4xx:客户端错误--请求有语法错误或请求无法实现。
  • 5xx:服务器端错误--服务器未能实现合法的请求。

常见的状态码有如下几种:

  • 200 OK 客户端请求成功。
  • 301 Moved Permanently 请求永久重定向。
  • 302 Moved Temporarily 请求临时重定向。
  • 304 Not Modified 文件未修改,可以直接使用缓存的文件。
  • 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
  • 401 Unauthorized 请求未经授权。
  • 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因。
  • 404 Not Found 请求的资源不存在,例如,输入了错误的URL。
  • 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
  • 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。

HTTP和TCP/IP协议的关系

image.png | left | 827x1208

HTTP/2

HTTP2即超文本传输协议2.0版本,是HTTP协议的下一个版本。因为标准委员会不打算再发布子版本了,所以直接叫HTTP/2,而不叫HTTP/2.0。

HTTP2相对于之前的HTTP协议有以下几个优点:

  • HTTP 2采用完全二进制的格式来传输数据。同时HTTP 2对消息头采用HPACK压缩传输,最大限度地节省了传输带宽。相比于HTTP 1.x 每次请求都会携带大量冗余头信息(例如浏览器Cookie信息等),HTTP2具有很大的优势。
  • HTTP 2使用TCP多路复用的方式来降低网络请求连接建立和关闭的开销,多个请求可以通过一个TCP连接来并发完成。
  • HTTP2支持传输流的优先级和流量控制机制。HTTP2中每个文件传输流都有自己的传输优先级,并可以通过服务器来动态改变,服务器会保证优先级高的文件流先传输。例如在未来的浏览器端渲染中,服务器端就可以优先传输CSS文件保证页面的渲染,然后在CSS文件全部传输完成后加载JavaScript脚本文件。
  • 支持服务器端推送。服务端能够在特定条件下把资源主动推送给客户端。

HTTP 1.1会让资源排队加载,如下图所示:

image.png | center | 720x319

但当我们开启了HTTP/2之后,有了TCP多路复用,个数几乎没有限制了,如下图所示:

image.png | center | 720x571

HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。

image.png | center | 827x645

目前支持HTTP2协议传输的浏览器依然很少,随着技术的发展和浏览器的更新迭代,HTTP2的时代终会到来,但我们依然不能在短时间内企图通过它来帮我们进行页面优化。

HTTPS

HTTPS(超文本传输安全协议 Hypertext Transfer Protocol Secure)经由HTTP进行通信,但利用SSL/TLS来加密数据包。HTTPS的主要思想是在不安全的网络上创建一安全信道。通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披 SSL 协议这层外壳的 HTTP。HTTP的URL由“http://”起始且默认使用端口80,HTTPS的URL由“https://”起始且默认使用端口443。

SSL 是独立于 HTTP 的协议,所以不光是 HTTP 协议,其他运行在应用层的 SMTP 和 Telnet 等协议均可配合 SSL(Secure Socket Layer) 协议使用。

image.png | center | 342x157

HTTP是不安全的,攻击者通过监听和中间人攻击等手段,可以获取网站帐户和敏感信息等。人们对 HTTPS 有一个普遍的错误认识,认为只有处理敏感通信的网站才需要 HTTPS。 每个未受保护的 HTTP 请求都可能暴露与用户行为和身份有关的信息。尽管访问一次未受保护的网站可能看上去无害,但一些入侵者会查看汇总的用户浏览活动,以推断他们的行为和意图,从而进行去匿名化攻击,查出匿名用户的身份。例如,员工可能在阅读未受保护的医疗文章时不经意地向其雇主泄露敏感的健康信息。

公钥和私钥

加密和解密同用一个密钥的方式称为共享密钥加密(Common key crypto system),也被叫做对称密钥加密。

image.png | center | 600x456.23632385120345

以共享密钥方式加密时必须将密钥也发给对方。可究竟怎样才能安全地转交?在互联网上转发密钥时,如果通信被监听那么密钥就可会落入攻击者之手,同时也就失去了加密的意义。

SSL 采用一种叫做公开密钥加密(Public-key cryptography)的加密处理方式。公开密钥加密使用一对非对称的密钥。一把叫做私有密钥(private key),另一把叫做公开密钥(public key)。顾名思义,私有密钥不能让其他任何人知道,而公开密钥则可以随意发布,任何人都可以获得。

使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密。利用这种方式,不需要发送用来解密的私有密钥,也不必担心密钥被攻击者窃听而盗走。

image.png | center | 600x464.6080760095012

HTTPS 采用混合加密机制:

image.png | center | 600x538.3863080684596

证书颁发机构

公开密钥加密方式还是存在一些问题的。那就是无法证明公开密钥本身就是货真价实的公开密钥。

证书颁发机构 (CA) 是一个组织,对公钥和与公共 DNS 名称之间的映射进行证实。例如,客户端如何知道特定公钥是否为 www.foobar.com 的真实公钥?按理说,无法知道。CA 证实特定密钥是特定网站的真实密钥,它使用自己的私钥来加密签名该网站的公钥。此签名在计算上是无法伪造的。浏览器(和其他客户端)维护信任锚存储库,它包含知名 CA 拥有的公钥,并且它们使用这些公钥来加密验证 CA 的签名。

SSL握手流程

image.png | center | 827x886

最后使用共享密钥来进行以后的通信,详细流程:

image.png | center | 827x1564

Websocket

在实际的前端应用项目中,除了使用应答模式的HTTP协议进行普通网络资源文件的请求加载外,有时也需要建立客户端与服务端之间的实时连接进行通信,例如网页实时聊天的应用场景,这就必须涉及浏览器端的实时通信协议了。对于这些对实时性要求较高的应用场景,普通的HTTP协议就并不适用。虽然前端可以通过Ajax定时向服务端轮询的方式来持续获取服务端的消息,但是这种方式效率相对较低。

WebSocket是浏览器端和服务器端建立实时连接的一种通信协议,可以在服务器和浏览器端建立类似Socket方式的消息通信。相对于HTTP1.1协议,WebSocket协议的优势是方便服务器和浏览器之间的双向数据实时通信。

7层网络模型

这里只是类似Socket方式,WebSocket 是建立在 TCP/IP 协议之上,属于应用层的协议,而 Socket 是在应用层和传输层中的一个抽象层,它是将 TCP/IP 层的复杂操作抽象成几个简单的接口来提供给应用层调用。简单回顾一下7层网络模型:

image.png | center | 827x586

简单来说,我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容。如果想要使传输的数据有意义,则必须使用到应用层协议。应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。

TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、listen、connect、accept、send、read和write等等。

image.png | center | 827x1172

Websocket使用

WebSocket 的实现分为握手,数据发送/读取,关闭连接。

image.png | center | 827x505

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};      

在线演示:html5demos.com/web-socket/

DDP

DDP ( Distributed Data Protocol,分布式数据协议)是一种新型的客户端与服务器端的实时通信协议,由于兼容性的原因,目前使用还不广泛。

DDP使用JSON的数据格式在客户端和浏览器之间进行数据传输通信,所以对于前端开发者来说使用非常方便。有名的Meteor Web框架的双向实时数据更新机制底层使用的就是DDP,这种协议模式下客户端可向服务器端发起远程过程调用,客户端也可以订阅服务端数据,在服务端数据变化时,服务器会向客户端发起通知,触发浏览器响应的操作。

//创建服务端
const DDPClient = require('ddp');
const client = new DDPClient({
  host: 'localhost',
  port: 3000
});

//监听消息
client.on('message', function(data, flags){
  console.log('[DDP消息]: ', data);
});

//创建连接
client.connect(function(){
  client.subscribe('post',[],function(){
    console.log('[post订阅消息]');
  });
});

协议标准:github.com/meteor/mete…

RESTful

REST (Representational State Transfer,表述性状态转化)并不是某一种具体的协议,而是定义了一种网络应用软件之间的架构关系并提出了一套与之对应的网络之间交互调用的规则。与之类似的例如早期的WebSevice,WebSevice现在基本都不用了。

在REST形式的软件应用服务中,每个资源都有一个与之对应的URI地址,资源本身都是方法调用的目标,方法列表对所有资源都是一样的,而且这些方法都推荐使用HTTP协议的标准方法,例如GET、POST、PUT、DELETE等。如果一个网络应用软件的设计是按照REST定义的,我们就可以认为它使用的交互调用的方法设计遵循RESTful规范。换种方式理解,RESTful 是一种软件架构之间交互调用数据的协议风格规范,它建议以一种通用的方式来定义和管理数据交互调用接口。

例如:对于书籍book的记录管理接口,有增、刪、改、查操作,于是我们定义接口:

  • path/addBook
  • path/deleteBook
  • path/updateBook
  • path/getBook

看上去好像没有什么问题。后来,另一个项目也有类似的接口定义,却可能叫作:

  • path/appendBook
  • path/delBook
  • path/modifyBook
  • path/getBook

接着有一天,项目负责人可能会说,要升级接口来满足新的需求,于是我们又添加了:

  • path/addBook2
  • path/deleteBook2
  • path/updateBook2
  • path/getBook2

这样用起来是没有什么问题,但是这些随意的定义会增加数据接口维护难度和项目继续开发的成本。

这时,我们或许会考虑使用文档或规范,规定一定要使用add来添加,新的接口版本号放前面 path/v2/addBook,开发的人必须严格按照文档规范去写。这样做很好,但依然不够完善,原因有以下几点:

  • 因为项目工作常常排期紧张,你可能没时间去写文档,或者后面接手的人不想去看文档。
  • 开发修改功能后很可能来不及或忘记去更新文档。
  • 无论文档写得多清楚,我们看起来效率总是很低。

这时如果有一个风格更好的通用规范来定义数据交互接口,就不用这么麻烦了。所以我们完全可以利用RESTful设计的规范特性来解决上面遇到的问题。对于书籍记录操作接口的命名可以如下操作:

HTTP方法URI描述
POSTpath/v1/book新增书籍信息
DELETEpath/v1/book删除书籍信息
PUTpath/v1/book更新书籍信息
GETpath/v1/book获取书籍信息

使用RESTful规范来重新设计接口后,一切就变得很清晰自然,这样新的工程师接手项目时,只要他足够了解RESTful规范,几乎没有时间成本。即使他不了解RESTful规范,也可以很快地去了解,这就可以避免他去读那份看似完善其实冗长杂的文档。

这里涉及到了几个设计的原则:

  • “资源”表示一种实体,所以应该是名词,URL不应该有动词,动词应该放在HTTP协议中。
  • 按照标准,不应该在URL中包含版本号,应该放在HTTP请求头信息的Accept字段中,不过这样不够直观,所以一般的方式还是把版本号放在URL中,算是一个反模式。

RESTful API 的主要设计原则就是这些,总结来说就是结合HTTP的固有方式来表征资源的状态变化描述,而不是通过动词加名词的方式来设计。

Github RESTful API:developer.github.com/v3/

GraphQL

GraphQL 对 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进。

GraphQL 解决的最重要的3个问题分别是:

  • 需要进行多次往返以获取所需的数据:典型的 REST API 请求多个资源时得载入多个 URL,而 GraphQL 可以通过一次请求就获取你应用所需的所有数据。
  • 客户端依赖于服务端:消除了服务器对数据内容进行硬编码的需要。我们可以把客户端与服务端分离开来,单独进行维护和改进。前后端彻底分离。
  • 糟糕的前端开发体验:使用 GraphQL,开发人员可以声明式地来表达其用户界面的数据需求,不用总是关注数据是如何获取的。

RESTful APIs vs GraphQL APIs

一个简单的示例:我们要做一个星球大战人物信息展示的UI界面:需要显示人物的姓名,出生年份,星球名称以及所有他们参演的电影的名称。

这个 UI 的 JSON 数据可能类似于:

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planet": {
        "name": "Tatooine"
      },
      "films": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}

如果使用 React.js ,一般会这样表示视图:

// The Container Component:
<PersonProfile person={data.person} ></PersonProfile>

// The PersonProfile Component:
Name: {person.name}
Birth Year: {person.birthYear}
Planet: {person.planet.name}
Films: {person.films.map(film => film.title)}

如果使用 RESTful API,我们可能这样请求数据:

1、获取人物信息:

GET - /people/{id}
{
  "name": "Darth Vader",
  "birthYear": "41.9BBY",
  "planetId": 1
  "filmIds": [1, 2, 3, 6],
  ...
}

2、获取星球信息:

GET - /planets/1

3、获取所有电影信息:

GET - /films/1
GET - /films/2
GET - /films/3
GET - /films/6

演示

我们需要发送6个请求才能获取到所有需要的数据,每个获取数据的方法都是命令式的。每个接口返回的信息还有很多字段不是我们所需要的。为了解决这个问题,我们可能会新增加一个接口,比如:

GET - /people/{id}/films

但是这样就不是纯粹的RESTful API了,而且后端要额外的创建这个接口,用来满足前端的数据要求,如果增减字段或对象,后端还要添加接口或者重新编码。

如果使用GraphQL,我们可以这样来查询:

GET or POST - /graphql?query={...}

比如参数使用:

{
  person(personID: 4) {
    name,
    birthYear,
    homeworld {
      name
    },
    filmConnection {
      films {
        title
      }
    }
  }
}

演示

一个请求就完成了所有数据的获取。

GraphQL的灵活性也会带来一些问题,比如增加复杂度,资源耗尽攻击,N+1查询等。FB针对N+1给出了 dataloader 的方案。

Github GraphQL API:developer.github.com/v4/

在线调试:developer.github.com/v4/explorer…

与Native交互

Hybrid App是在Native App应用的基础上结合了Web App应用所形成的模式,一般称之为混合App。从技术开发上来看,相比于传统的桌面浏览器端的Web App,它具有以下几方面的特征:

  • 可用的系统网络资源更少。由于移动设备CPU、内存、网卡、网络连接多方面的限制,HybridApp的前端页面可用的系统资源远远小于桌面浏览器。就网络连接来说,大部分移动设备的使用者使用的仍是3G、4G的网络,带宽和流量均有限制,和桌面浏览器的带宽接入相比还是有着本质上的区别。
  • 支持更新的浏览器特性。目前智能设备浏览器种类相对较少,且随着硬件设备的快速更新,主流的浏览器以WebKit内核居多,支持较新的浏览器特性。不像桌面浏览器那样需要考虑较低版本Internet Explorer的兼容性问题。
  • 可实现离线应用。Hybrid的一个优势是可以通过新的浏览器特性或Native的文件读取机制进行文件级的文件缓存和离线更新。这是桌面浏览器,上较难做到的。这些离线机制常常可以用来弥补Hybrid App网络系统资源不足的缺点,让浏览器脚本更快从本地缓存中加载。
  • 较多的机型考虑。由于目前移动设备平台不统一,而且不同设备机型系统的浏览器实现仍有一定的区别,因此Hybrid App应用需要考虑不同设备机型的兼容性问题。
  • 支持与Native交互。Hybrid App的另一个特点是结合了移动端Native特性,可以在前端页面中调用客户端Native的能力,例如摄像头、定位、传感器、本地文件访问等。

Web调用Native

在HTML5中调用Native程序一般有几种较通用的方法:

一、通过URI请求。

Native应用可在移动端系统中注册一个Scheme协议的URI,这个URI可在系统的任意地方授权访问来调起一段原生方法或一个原生的界面。同样,Native 的WebView控件中的JavaScript脚本的请求也可以匹配调用这一通用的Scheme协议。例如我们通过对 window.location.href 赋值或使用iframe的方式发送一个URI的请求,这个请求可以被Native应用的系统捕获并调起Native应用注册匹配的这个Scheme协议内容。

比如微信的scheme为(weixin://)。

image.png | center | 596x625

二、通过addJavascriptInterface(Android)或JavaScriptCore(iOS)注入方法到页面中调用。

Android,原生Webview需要先注册可供前端调用的JS函数:

// Android容器允许JS脚本,必须要
webSettings.setJavaScriptEnabled(true);
// Android容器设置侨连对象
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

// Android4.2版本及以上,本地方法要加上注解@JavascriptInterface,否则会找不到方法。
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;  
}

然后H5中即可调用原生中注册的函数:

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

iOS,需要引入JavaScriptCore库:

#import <JavaScriptCore/JavaScriptCore.h>

然后原生需要注册API:

//webview加载完毕后设置一些js接口
-(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];
    };
    
}

之后前端就可以调用了:

// 调用方法,用top是确保调用到最顶级,因为iframe要用top才能拿到顶级
window.top.foo('test'); // 返回:'foo:test'

三、改写浏览器原有对象。

通过修改原来浏览器的window某些方法,然后拦截固定规则的参数,然后分发给Java对应的方法去处理。这里常用的是以下四个方法:

  • alert,可以被webview的onJsAlert监听
  • confirm,可以被webview的onJsConfirm监听
  • console.log,可以被webview的onConsoleMessage监听
  • prompt,可以被webview的onJsPrompt监听

Native调用Web

需要先使用JavaScript 在HTML5页面全局中声明相对应的方法。

然后Native向HTML5发起调用,Android平台一般通过loadUrl,iOS通常通过stringByEvaluatingJavaScriptFromString实现。

Android调HTML5:

// 异步执行JS代码,并获取返回值    
mWebView.evaluateJavascript("javascript: 方法名('参数,需要转为字符串')", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            // 这里的value即为对应JS方法的返回值
        }
});

iOS调HTML5:

// 可以取得JS函数执行的返回值
// 方法必须是Html页面绑定在最顶层的window上对象的
// 如window.top.foo
[webView stringByEvaluatingJavaScriptFromString:@"方法名(参数);"];

JSBridge

image.png | center | 690x167

JSBridge是HTML5与Native通信的桥梁,其作用是实现HTML5与Native间的双向通信。JSBridge综合了上面的技术,更多的是一种形式、一种思想,各家的实现方式也略有差异。比如微信JSSDK,就是基于WeixinJSBridge,微信浏览器中的页面,通过WeixinJSBridge调用微信提供的一些原生功能。