首页 > 其他分享 >JSBridge:混合开发中的双向通信

JSBridge:混合开发中的双向通信

时间:2023-02-04 18:35:09浏览次数:35  
标签:web 调用 JSB 双向通信 混合 JSBridge JS android Native

什么是WebView

WebView 是移动端中的一个控件,它为 JS 运行提供了一个沙箱环境。WebView 能够加载指定的 url,拦截页面发出的各种请求等各种页面控制功能,JSB 的实现就依赖于 WebView 暴露的各种接口。

由于历史原因,IOS以8为分界,Android以4.4为分界,分为高低两个版本。而它们的区别在于 —— 回调。高版本可以通过执行回调拿到 JS 执行完毕的返回值,然后准确进行下一步操作。而低版本无法执行回调!

什么是 JSB

Hybrid App 的核心。我们开发的 h5 页面运行在端上的 WebView 容器之中,很多业务场景下 h5 需要依赖端上提供的信息/能力,这时我们需要一个可以连接原生运行环境和 JS 运行环境的桥梁 。 这个桥梁就是 JSB,JSB 让 Web 端和 Native 端得以实现双向通信。

JSB的目的

JSB 的目的就是“让 Native 可以调用 web 端的 JavaScript 代码,让 web 端可以调用 Native 的原生代码”。

native 调用 web 端代码

无论 Android 还是 iOS,在调用 web 端代码的时候,必须是调用的“挂载在window上的函数”。拿一个例子来说:

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="调用JS方法"
android:onClick="onJSFunction1"/>
public void onJSFunction1 (View v) {
mWebView.evaluateJavascript("javascript:onFunction('android调用JS方法')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(s);
builder.setNegativeButton("确定", null);
builder.create().show();
}
});
}

​evaluateJavascript​​ 就是调用 JS 中的方法:onFunction1,并传入参数,在回调中进行处理 —— 这个回调至关重要。

在 js 中:

window.onFunction = function(str) {
alert(str);
return "这是 onFunction 方法的返回值";
}

JSBridge:混合开发中的双向通信_Android

上面的 java 代码中,​​mWebView​​ 是实例化的 腾讯X5内核组件。在它的配置中首先注意到这样一行代码:

/**
* 允许加载的网页执行 JavaScript 方法
*/
webSettings.setJavaScriptEnabled(true);

允许网页执行 JS 方法。这个非常重要:它是 Native 是否能够向 web 通信的关键!这一点下面我们会提到。

在进行完一些基础配置后,我们会构建一个 JSBridge 对象:

addJavascriptInterface(
new MyJaveScriptInterface(mContext, this),
"AndroidJSBridge");

这个对象就叫做“AndroidJSBridge”,在这个处理中,这个 JSB 对象会被挂载到网页的 window 对象下,从而作为原生端和 web 端通信的桥梁。

web 调用 native 端代码

上面说了在初始化后 window 对象下会有一个 AndroidJSBridge 对象,在网页中也可以直接通过 ​​window.AndroidJSBridge​​ 拿到这个对象,从而调用 Android 端提供给网页端的方法(这里以Android为例):
现在 Android 提供了这样一个方法:

@JavascriptInterface
public void androidTestFunction1 (String str) {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage(str);
builder.setNegativeButton("确定", null);
builder.create().show();
}

目的是在 APP 弹出一个 Alert 对话框,对话框中的内容为 JavaScript 传入的字符串。

注意:对于 Android 来说,当 js 调用方法并传参时,Android方法接收的参数只能是“基本数据类型”。对于“复杂数据类型”,只能通过​​JSON.stringify(Object)​​ 转化成 string 类型。
而 iOS 则不受此限制。

在 web 项目中,拿原生项目来说,我们直接这么写即可:

<input type="button" value="调用androidTestFunction1" @click="toAndroidFunction1()" />

<script>
function toAndroidFunction1() {
window.AndroidJSBridge.androidTestFunction1('调用 android 下的 function1 方法')
}
</script>

JSBridge:混合开发中的双向通信_android_02


当然,在 android 方法中,我们也可以直接 ​​return​​ 数据,到了 web 端就是“回调”了。

function toAndroidFunction2() {
let result = window.AndroidJSBridge.androidTestFunction1('androidTestFunction2方法的返回值');
alert(result);
}

JSBridge:混合开发中的双向通信_Android_03


诶,为什么调用的 ​​alert​​ 也是弹出的 android 原生弹出框?

因为在初始化的时候,原生对 alert 进行了一次“劫持”:

/**
* 监听网页中的url加载事件
*/
private void initChromeClient () {
setWebChromeClient(new WebChromeClient(){

/**
* alert()
* 监听alert弹出框,使用原生弹框代替alert。
*/
@Override
public boolean onJsAlert(WebView webView, String s, String s1, JsResult jsResult) {

AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage(s1);
builder.setNegativeButton("确定", null);
builder.create().show();
jsResult.confirm();

return true;
}
});
}

双向通信

Native向web发送消息

其实原理上面已经说了:

Native 向 Web 发送消息基本原理上是在 WebView 容器中动态地执行一段 JS 脚本,通常情况下是调用一个挂载在全局上下文的方法。
具体来说 —— Native 端可以直接调用挂载在 window 上的全局方法并传入相应的函数执行参数,并且在函数执行结束后 Native 端可以直接拿到执行成功的返回值。

场景:页面上是一个web page,在其上有一个原生控件 Button 和 Input。在输入框中输入一段代码更改 web 页面上某个标签的 ​​innerHTML​​:

<EditText
android:id="@+id/editText_name"
android:layout_width="edit_parent"
android:layout_height="edit_content"
android:hint="请输入你想要执行的js代码" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Native向Web发送消息" />
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import cn.sunday.hybridappdemo.views.X5WebView;

public class MainActivity extends AppCompatActivity {
private X5WebView mWebView;

private Button button;
private EditText editText_name;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.Button_quedin);
this.editText_name = (EditText) findViewById(R.id.editText_name);

button.setOnClickListener(new View.OnClickListener() {//注册监听
@Override //监听点击事件
public void onClick(View v) {
String name = editText_name.getText().toString();
mWebView.evaluateJavascript("javascript:onShowPageNative(" + name + ")");
}
})
})
}
window.onShowPageNative = function(str) {
new Function(str);
}

将文本框输入的字符视为 JS 字符串并调用相关 API 直接执行

你是否想到了著名的用于解决跨域问题的jsonp?其实很多地方都使用了“将代码作为字符串传递再执行”的原理。最出名的就是微信小程序的(双线程)混合架构模型中view层向逻辑层通信。

Web 向 Native 发送消息

Web 向 Native 发送消息本质上就是某段 JS 代码的执行端上是可感知的,目前业界主流的实现方案有两种,分别是拦截式和注入式。

拦截式

和浏览器类似 WebView 中发出的所有请求都是可以被 Native 容器感知到的,因此拦截式具体指的是 Native 拦截 Web 发出的 URL 请求,双方在此之前约定一个 JSB 请求格式,如果该请求是 JSB 则进行相应的处理,若不是则直接转发。

Native 拦截请求的钩子方法:

  • Android:shouldOverrideUrlLoading
  • IOS:
  • 8+:decidePolicyForNavigationAction
  • 8-:shouldStartLoadWithRequest

拦截式的流程存在几个问题:

通过何种方式发出请求?
Web 端发出请求的方式非常多样,例如 ​​<a>​​ 、​​iframe.src​​、​​location.href​​、ajax 等,但 ​​<a>​​ 需要用户手动触发,​​location.href​​ 可能会导致页面跳转,安卓端拦截 ajax 的能力有所欠缺,因此绝大多数拦截式实现方案均采用 iframe 来发送请求。

如何规定 JSB 的请求格式?
一个标准的 URL 由 ​​<scheme>://<host>:<port><path>​​ 组成,相信大家都有过从微信或手机浏览器点击某个链接意外跳转到其他 App 的经历,如果有仔细留意过这些链接的 URL 你会发现目前主流 App 都有其专属的一个 scheme来作为该应用的标识,例如微信的 URL scheme 就是 ​​weixin://​​。JSB 的实现借鉴这一思路,定制业务自身专属的一个 URL scheme 来作为 JSB 请求的标识,例如字节内部的 ​​bytedance://​​ 。

// Web 通过动态创建 iframe,将 src 设置为符合双端规范的 url scheme
const CUSTOM_PROTOCOL_SCHEME = 'vdian'

function web2Native(event) {
const messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + event;
document.documentElement.appendChild(messagingIframe);

setTimeout(() => {
document.documentElement.removeChild(messagingIframe);
}, 200)
}

拦截式在双端都具有非常好的向下兼容性,曾经是最主流的 JSB 实现方案,但目前在高版本的系统中已经逐渐被淘汰,理由是它有如下几个劣势:

  • 连续发送时可能会造成消息丢失(可以使用消息队列解决该问题)
  • URL 字符串长度有限制
  • 性能一般,URL request 创建请求有一定的耗时(Android 端 200-400ms)
注入式

注入式的原理是通过 WebView 提供的接口向 JS 全局上下文对象(window)中注入对象或者方法,当 JS 调用时,可直接执行相应的 Native 代码逻辑,从而达到 Web 调用 Native 的目的。
—— 也就是上面说的“web 端调用 Native 端代码”。

这种方法简单而直观,并且不存在参数长度限制和性能瓶颈等问题,目前主流的 JSB SDK 都将注入式方案作为优先使用的对象。

标签:web,调用,JSB,双向通信,混合,JSBridge,JS,android,Native
From: https://blog.51cto.com/u_15296224/6037228

相关文章

  • 17.爬取天天基金中万家精选混合A (519185)的净值数据
    1#爬虫2#该项目是爬取天天基金网某只基金的净值数据34#1.引入包5#网络请求6importjson78importrequests9#正则10importre11#数据......
  • Hive:二级分区、动态分区和混合分区
    1二级分区所谓二级分区,就是一个表有两个分区,概念很简单。当然Hive支持一个表有多个分区这里有一份测试数据,是每个月的销量数据今天的例子以这份数据来演示下面......
  • R语言LME4混合效应模型研究教师的受欢迎程度|附代码数据
    全文链接:http://tecdat.cn/?p=11724最近我们被客户要求撰写关于混合效应模型的研究报告,包括一些图形和统计输出。文中本教程对多层_回归_模型进行了基本介绍介绍本教程......
  • 解决vite+vue3混合开发白屏问题
    开发环境:vite4.0+vue3.2使用场景:vite打包后将包嵌入app使用。问题描述:打包后app显示白屏。解决方案:默认的构建目标是能支持原生ESM语法的script标签、原生ESM动态导......
  • 解决WIN10与WIN7、XP混合环境的共享文件夹互访问题
    局域网里往往混有WIN10与WIN7,甚至XP的机器,此时你会发现WIN10的机器“特立独行”,访问不了其它机器,反之亦然。造成这种现象的原因有好几个,本文依次解决。开放SMB1.0协议:因为WI......
  • 混合式APP开发框架
    在企业移动战略布局中,app已成为连接业务与用户最主要的载体,同样其开发技术目前也处于十分成熟的阶段。随着软件技术的日新月异的更新换代,基于原生开发的移动端越来越没落。......
  • 混合模式Blend
    遇到的问题1) Camera直接输出画面时,混合效果和混合公式对不上底图Quad_Bg是蓝色(0,0,1,1),混合图Quad_Src是红色(1,0,0,0.5),混合模式为BlendDstColorZero,OneZ......
  • 洛谷 P1208混合牛奶 题解
    一道贪心算法不是很明显的题目,其实一般的递推也可以做。 大体思路:肯定优先购买单价最低的奶农的牛奶,那么就需要先根据牛奶单价进行排序,这里用结构体会更好一点。之后在......
  • m基于GA遗传优化+SA模拟退火的混合改进算法的多产品多机器生产优化matlab仿真
    1.算法描述       这里,我们首先介绍一下改进算法的基本原理,按照前面说的,这里我们主要将GA和SA进行合并。        这里,我研究了下,将两种算法做如下方法的......
  • m基于GA遗传优化+SA模拟退火的混合改进算法的多产品多机器生产优化matlab仿真
    1.算法描述这里,我们首先介绍一下改进算法的基本原理,按照前面说的,这里我们主要将GA和SA进行合并。这里,我研究了下,将两种算法做如下方法的结合:首先,在之前做的改进GA......