WebViewJavascriptBridge

2022-11-19 16:59:11 浏览数 (2)

大家好,又见面了,我是你们的朋友全栈君。

Web 页面中的 JS 与 iOS Native 如何交互?JS 和 iOS Native 就好比两块没有交集的大陆,如果想要使它们相互通信就必须要建立一座“桥梁”。

WebViewJavascriptBridge 是盛名已久的 JSBridge 库,它仅使用了少量代码就实现了对于 Mac OS X 的 WebView 以及 iOS 平台的 UIWebView 和 WKWebView 三种组件的完美支持。

WebViewJavascriptBridge 主要是作为 Mac OS X 和 iOS 端(Native 端)与 JS 端相互通信,互相调用的桥梁。对于 Mac OS X 和 iOS 两种平台包含的三种 WebView 功能组件而言,WebViewJavascriptBridge 做了隐性适配,即仅用一套代码即可绑定不同平台的 WebView 组件实现同样功能的 JS 通信功能。 WebViewJavascriptBridge 对于 JS 端和 Native 端设计了对等的接口,不论是 JS 端还是 Native 端,注册本端的响应处理都是用 registerHandler 接口,调用另一端(给另一端发消息)都是用 callHandler 接口。

  1. UIWebView 使用 javaScriptCore.
  2. WKWebView 使用 WKUserContentController.

UIWebView 原生的交互原理 通过一个 JSContext 获取 UIWebView 的 JS 执行上下文。 然后通过这个上下文,进行 OC & JS 的双端交互。

代码语言:javascript复制
 _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    _jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"%@",@"获取 WebView JS 执行环境失败了!");
    };

WKWebView 原生交互原理

通过 userContentController 把需要观察的 JS 执行函数注册起来。 然后通过一个协议方法,将所有注册过的 JS 函数执行的参数传递到此协议方法中。

注册 需要 观察的 JS 执行函数

代码语言:javascript复制
 [webView.configuration.userContentController addScriptMessageHandler:self name:@"jsFunc"];

在 JS 中调用这个函数并传递参数数据

代码语言:javascript复制
window.webkit.messageHandlers.jsFunc.postMessage({name : "李四",age : 22});

OC 中遵守 WKScriptMessageHandler 协议。

代码语言:javascript复制
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 

此协议方法里的 WKScriptMessage 有 name & body 两个属性。 name 可以用来判断是哪个 JSFunc 调用了。body 则是 JSFunc 传递到 OC 的参数。

WebViewJavaScriptBridge

WebViewJavaScriptBridge 用于 WKWebView & UIWebView 中 OC 和 JS 交互。 它的基本原理是:

把 OC 的方法注册到桥梁中,让 JS 去调用。 把 JS 的方法注册在桥梁中,让 OC 去调用。

WebViewJavaScriptBridge 使用的基本步骤

  1. 首先在项目中导入 WebViewJavaScriptBridge 框架
代码语言:javascript复制
pod ‘WebViewJavascriptBridge’
  1. 导入头文件 #import <WebViewJavascriptBridge.h>
  2. 建立 WebViewJavaScriptBridge 和 WebView 之间的关系。
代码语言:javascript复制
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
  1. 在HTML 文件中,复制粘贴这两段 JS 函数。
代码语言:javascript复制
function setupWebViewJavascriptBridge(callback) {
        if (window.WebViewJavascriptBridge) {
          return callback(window.WebViewJavascriptBridge)
        }
        if (window.WVJBCallbacks) {
          return window.WVJBCallbacks.push(callback)
        }
        window.WVJBCallbacks = [callback] // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。
        var WVJBIframe = document.createElement('iframe') // 创建一个 iframe 元素
        WVJBIframe.style.display = 'none' // 不显示
        WVJBIframe.src = 'https://__bridge_loaded__' // 设置 iframe 的 src 属性
        document.documentElement.appendChild(WVJBIframe) // 把 iframe 添加到当前文导航上。
        setTimeout(function() {
          document.documentElement.removeChild(WVJBIframe)
        }, 0)
    }
    
    // 这里主要是注册 OC 将要调用的 JS 方法。
    setupWebViewJavascriptBridge(function(bridge){
       
    });

到此为止,基本的准备工作就做完了。现在需要往桥梁中注入 OC 方法 和 JS 函数了。

往桥梁中注入 OC 方法 和 JS 函数

1、往桥梁中注入 OC 方法。

代码语言:javascript复制
 [_jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"dataFrom JS : %@",data[@"data"]);
        
        responseCallback(@"扫描结果 : www.baidu.com");
    }];

这段代码的意思:

  1. scanClick 是 OC block 的一个别名。
  2. block 本身,是 JS 通过某种方式调用到 scanClick 的时候,执行的代码块。
  3. data ,由于 OC 这端由 JS 调用,所以 data 是 JS 端传递过来的数据。
  4. responseCallback OC 端的 block 执行完毕之后,往 JS 端传递的数据。

2、往桥梁中注入 JS 函数. 在 JS 的方法如何注入到桥梁呢?需要在第二段 JS 代码中,注入 JS 的函数。

代码语言:javascript复制
// 这里主要是注册 OC 将要调用的 JS 方法。
    setupWebViewJavascriptBridge(function(bridge){
        // 声明 OC 需要调用的 JS 方法。
        bridge.registerHanlder('testJavaScriptFunction',function(data,responseCallback){
            // data 是 OC 传递过来的数据.
            // responseCallback 是 JS 调用完毕之后传递给 OC 的数据
            alert("JS 被 OC 调用了.");
            responseCallback({data: "js 的数据",from : "JS"});
        })
    });

这段代码的意思:

  1. testJavaScriptFunction 是注入到桥梁中 JS 函数的别名。以供 OC 端调用。
  2. 回调函数的 data。 既然 JS 函数由 OC 调用,所以 data 是 OC 端传递过来的数据。
  3. responseCallback 。 JS 调用在被 OC 调用完毕之后,向 OC 端传递的数据。

基本就是:

OC 端注册 OC 的方法,OC 端调用 JS 的函数。 JS 端注册 JS 的函数,JS 端调用 OC 的方法。

场景

JS -> OC 的交互

在 HTML 中,有个按钮,点击这个按钮,修改 NavigationBar 的颜色。

  1. 在 OC 端,往桥梁注入一个修改 NavigationBar 颜色的 block.
  2. 在 JS 端,调用这个 block,来间接的达到修改颜色的目的。

首先,在 OC 中,通过 WebViewJavascriptBridge 注册一个修改 navigationBar 颜色的 Block。

代码语言:javascript复制
[_jsBridge registerHandler:@"colorClick" handler:^(id data, WVJBResponseCallback responseCallback) {
       self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];
        
        responseCallback(@"颜色修改完毕!");
    }];

然后再 JS 中,通过某种方式去调用这个 OC 的 block。

代码语言:javascript复制
WebViewJavascriptBridge.callHandler('colorClick',function(dataFromOC) {
            alert("JS 调用了 OC 注册的 colorClick 方法");
            document.getElementById("returnValue").value = dataFromOC;
        })

这里通过某种方式就是使用 WebViewJavascriptBridge.callHandler(‘OC 中block 别名’,callback)的方式来调用。

OC -> JS 的交互

OC 上有一个UIButton,点击这儿按钮,把 HTML body 的颜色修改成橙色。

首先,往桥梁中,注入一个修改 HTML body 颜色的 JSFunction。

代码语言:javascript复制
// 在这里声明 OC 需要主动调用 JS 的方法。
    setupWebViewJavascriptBridge(function(bridge) {
        bridge.registerHandler('changeBGColor',function(data,responseCallback){
            // alert('aaaaaa');
            document.body.style.backgroundColor = "orange";
            document.getElementById("returnValue").value = data;
        });
    }); 

然后在 OC 端通过桥梁调用这个 changeBGColor

代码语言:javascript复制
 [_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];

执行效果:


补充

OC 调用 JS 的三种情况。

代码语言:javascript复制
    // 单纯的调用 JSFunction,不往 JS 传递参数,也不需要 JSFunction 的返回值。
    [_jsBridge callHandler:@"changeBGColor"];
    // 调用 JSFunction,并向 JS 传递参数,但不需要 JSFunciton 的返回值。
    [_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];
    // 调用 JSFunction ,并向 JS 传递参数,也需要 JSFunction 的返回值。
    [_jsBridge callHandler:@"changeBGColor" data:@"传递给 JS 的参数" responseCallback:^(id responseData) {
        NSLog(@"JS 的返回值: %@",responseData);
    }];

JS 调用 OC 的三种情况。

代码语言:javascript复制
// JS 单纯的调用 OC 的 block
WebViewJavascriptBridge.callHandler('scanClick');

// JS 调用 OC 的 block,并传递 JS 参数
WebViewJavascriptBridge.callHandler('scanClick',"JS 参数");

// JS 调用 OC 的 block,传递 JS 参数,并接受 OC 的返回值。
WebViewJavascriptBridge.callHandler('scanClick',{data : "这是 JS 传递到 OC 的扫描数据"},function(dataFromOC){
            alert("JS 调用了 OC 的扫描方法!");
            document.getElementById("returnValue").value = dataFromOC;
        });

可以根据实际情况,选择合适的方法。

关于在 OC 中,往桥梁中注入 block 的注意点。

在当前控制器消失的时候,要记得把注入到桥梁中的 OC block,从桥梁中删除。 否则,可能会出现控制器无法释放的情况。

代码语言:javascript复制
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [_jsBridge removeHandler:@"scanClick"];
    [_jsBridge removeHandler:@"colorClick"];
    [_jsBridge removeHandler:@"locationClick"];
    [_jsBridge removeHandler:@"shareClick"];
    [_jsBridge removeHandler:@"payClick"];
    [_jsBridge removeHandler:@"goBackClick"];
}

Android

以上说的都是ios交互,安卓有一点点不同。

代码语言:javascript复制
function connectWebViewJavascriptBridge (callback) { 
     if (window.WebViewJavascriptBridge) {
           callback(WebViewJavascriptBridge)
     } else {
           document.addEventListener(
               'WebViewJavascriptBridgeReady'
                , function() {
                     callback(WebViewJavascriptBridge)
                },
                false
           );
     }
}


//和ios一样
connectWebViewJavascriptBridge (function(bridge) {

    //注册一个方法(方法名是“JS Echo”),客户端进行调用(方法名也是“JS Echo”),responseCallback是回调函数
    bridge.registerHandler('JS Echo', function(data, responseCallback) {
        console.log("JS Echo called with:", data)
        responseCallback(data)
    })

    //客户端已经注册好一个名为“ObjC Echo”的方法,H5直接进行调用(方法名也为“ObjC Echo”)就行,调用的时候可以传客户端需要的参数
    bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
        console.log("JS received response:", responseData)
    })
})

封装:

代码语言:javascript复制
/* eslint-disable */
function setAndroid () {
var bridge = {
default: this,
callHandler: function(b, a, c) {
var e = ''
'function' == typeof a && ((c = a), (a = {}))
a = { data: void 0 === a ? null : a }
if ('function' == typeof c) {
var g = 'dscb'   window.dscb  
window[g] = c
a._dscbstub = g
}
a = JSON.stringify(a)
if (window._dsbridge) e = _dsbridge.callHandler(b, a)
else if (
window._dswk ||
-1 != navigator.userAgent.indexOf('_dsbridge')
)
e = prompt('_dsbridge='   b, a)
return JSON.parse(e || '{}').data
},
register: function(b, a, c) {
c = c ? window._dsaf : window._dsf
window._dsInit ||
((window._dsInit = !0),
setTimeout(function() {
bridge.callHandler('_dsb.dsinit')
}, 0))
'object' == typeof a ? (c._obs[b] = a) : (c[b] = a)
},
registerAsyn: function(b, a) {
this.register(b, a, !0)
},
hasNativeMethod: function(b, a) {
return this.callHandler('_dsb.hasNativeMethod', {
name: b,
type: a || 'all'
})
},
disableJavascriptDialogBlock: function(b) {
this.call('_dsb.disableJavascriptDialogBlock', {
disable: !1 !== b
})
}
}
!(function() {
if (!window._dsf) {
var b = {
_dsf: { _obs: {} },
_dsaf: { _obs: {} },
dscb: 0,
WebViewJavascriptBridge: bridge,
close: function() {
bridge.callHandler('_dsb.closePage')
},
_handleMessageFromNative: function(a) {
var e = JSON.parse(a.data),
b = { id: a.callbackId, complete: !0 },
c = this._dsf[a.method],
d = this._dsaf[a.method],
h = function(a, c) {
b.data = a.apply(c, e)
bridge.call('_dsb.returnValue', b)
},
k = function(a, c) {
e.push(function(a, c) {
b.data = a
b.complete = !1 !== c
bridge.callHandler('_dsb.returnValue', b)
})
a.apply(c, e)
}
if (c) h(c, this._dsf)
else if (d) k(d, this._dsaf)
else if (((c = a.method.split('.')), !(2 > c.length))) {
a = c.pop()
var c = c.join('.'),
d = this._dsf._obs,
d = d[c] || {},
f = d[a]
f && 'function' == typeof f
? h(f, d)
: ((d = this._dsaf._obs),
(d = d[c] || {}),
(f = d[a]) &&
'function' == typeof f &&
k(f, d))
}
}
},
a
for (a in b) window[a] = b[a]
bridge.register('_hasJavascriptMethod', function(a, b) {
b = a.split('.')
if (2 > b.length) return !(!_dsf[b] && !_dsaf[b])
a = b.pop()
b = b.join('.')
return (b = _dsf._obs[b] || _dsaf._obs[b]) && !!b[a]
})
}
})();
return bridge
}
const jsBridge = {
init: function() {
if (/(Android)/i.test(navigator.userAgent.toLowerCase())) {
// setAndroid()
this.register = setAndroid().register
}
},
connectJsBridge: function(callback) {
if (window.WebViewJavascriptBridge) {
return callback(window.WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
var WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'https://__bridge_loaded__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function() {
document.documentElement.removeChild(WVJBIframe)
}, 0)
},
addBridgeProperty: function(bridge) {
const dependencies = ['getUserInfo', 'login', 'getLocation', 'share',..... ];
try {
dependencies.forEach(dependency => {
// if (!this[dependency]) {
Object.defineProperty(this, dependency, {
configurable: true,  // 注:允许重复定义属性(移除会造成重新定义报错)
get: () => {
return bridge.callHandler.bind(bridge, dependency)
}
})
// }
})
} catch (error) {
console.error(error)
}
// if (this.openWebPage) {
this.openWebPage = url => {
window.location.href = `app内嵌的域名?url=${encodeURIComponent(
url
)}`
}
// }
return this
},
ready: function(callback) {
this.connectJsBridge(bridge => {
callback(this.addBridgeProperty(bridge))
})
},
register: function(...args) {
window.WebViewJavascriptBridge.registerHandler(...args)
},
}
jsBridge.init()
export default jsBridge
//使用:
import JSBridge from './base/jsBridge'
JSBridge.ready((bridge: any) => {
bridge.getUserInfo((json: string | object) => {
console.log(json)
})
})

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/198352.html原文链接:https://javaforall.cn

0 人点赞