本文介绍: WebView移动端中的一个控件,它为 JS 运行提供了一个沙箱环境。WebView 能够加载指定url拦截页面发出的各种请求等各种页面控制功能,JSB 的实现依赖于 WebView 暴露的各种接口。由于历史原因,IOS以8为分界,Android以4.4为分界,分为高低两个版本。而它们的区别在于 —— 回调。高版本可以通过执行回调拿到 JS 执行完毕的返回值然后准确进行下一步操作。而低版本无法执行回调!Hybrid App核心

什么是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&gt;() {
        @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 方法返回值";
}

android端调用web端代码

上面的 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()" /&gt;

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

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

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

web 端调用有返回值的 android方法
诶,为什么调用的 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 拦截请求钩子方法:

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

通过何种方式发出请求
Web 端发出请求方式非常多样,例如 <a>iframe.srclocation.hrefajax 等,但 <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 实现方案,但目前在高版本系统中已经逐渐被淘汰,理由是它有如下几个劣势:

注入

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

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

原文地址:https://blog.csdn.net/qq_43624878/article/details/128618010

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_41176.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注