WebViewJavascriptBridge

news/2024/7/20 20:59:42 标签: ios

Web 页面中的 JS 与 iOS Native 如何交互?JS 和 iOS Native 就好比两块没有交集的大陆,如果想要使它们相互通信就必须要建立一座“桥梁”是盛名已久的 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 的双端交互。

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

复制

WKWebView 原生交互原理

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

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

 [webView.configuration.userContentController addScriptMessageHandler:self name:@"jsFunc"];

复制

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

window.webkit.messageHandlers.jsFunc.postMessage({name : "李四",age : 22});

复制

OC 中遵守 WKScriptMessageHandler 协议。

- (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 框架
pod ‘WebViewJavascriptBridge’

复制

  1. 导入头文件 #import <WebViewJavascriptBridge.h>
  2. 建立 WebViewJavaScriptBridge 和 WebView 之间的关系。
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];

复制

  1. 在HTML 文件中,复制粘贴这两段 JS 函数。
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 方法。

 [_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 的函数。

// 这里主要是注册 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。

[_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。

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。

// 在这里声明 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

 [_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];

复制

执行效果:


补充

OC 调用 JS 的三种情况。

    // 单纯的调用 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 的三种情况。

// 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,从桥梁中删除。 否则,可能会出现控制器无法释放的情况。

- (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交互,安卓有一点点不同。

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)
    })
})

复制

封装:

/* 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)
})
})

http://www.niftyadmin.cn/n/190086.html

相关文章

IDEA 常用快捷键

编辑 快捷键含义CtrlSpace代码提示AltInsert生成代码Ctrl/注释 //CtrlShift/注释 /*...*/AltEnter导入包&#xff0c;自动修正CtrlAltL格式化代码CtrlAltO优化导入的类和包CtrlD复制行CtrlY删除行ShiftEnter向下插入一行 浏览 快捷键含义CtrlN查找类CtrlShiftN查找文件CtrlG跳…

2023 海外工具站 3 月复盘

3 月的碎碎念&#xff0c;大致总结了商业人生、付费软件、创业方向选择、创业感性还是理性、如何解决复杂问题及如何成长这几个方面的内容。 商业人生 商业人生需要试错能力和快速信息收集与验证校准&#xff1b; 商业逻辑需要试错能力&#xff0c;收集各种渠道信息后整理决…

JS手写防抖和节流函数(超详细版整理)

1、什么是防抖和节流防抖&#xff08;debounce&#xff09;&#xff1a;每次触发定时器后&#xff0c;取消上一个定时器&#xff0c;然后重新触发定时器。防抖一般用于用户未知行为的优化&#xff0c;比如搜索框输入弹窗提示&#xff0c;因为用户接下来要输入的内容都是未知的&…

Linux的文件权限

Linux安全系统的核心是用户账户。每个能进入Linux系统的用户都会分配到用户唯一的账户。用户对文件的权限取决于登录系统的用户&#xff0c;用户是创建用户分配的用户ID&#xff0c;UID是数值。Linux系统使用特定的文件和工具来跟踪和管理系统上的用户账户。 /etc/passwd文件保…

【计组】CPU的工作过程

目录 ​编辑 一、取指令 二、解析指令 三、执行指令 四、总结 1.计算机如何分辨此时取出的是数据还是指令呢&#xff1f; 2.在具体实现时&#xff0c;MAR与MDR是集成于CPU中的&#xff0c;但是在逻辑结构层面&#xff0c;这两个寄存器仍然是属于主存储器的。 分为取指令、…

k8s自动化安装脚本(kubeadm-1.26.3)

介绍 通过kubeadm进行一键式部署k8s集群根据不同的启动方式&#xff0c;可部署单节点、一主多从、多主多从高可用的k8s集群通过ansible快速部署k8s的基础组件(helm、nfs、ingress、monitoring【联网|离线镜像】、kuboard) 软件架构 通过部署包中的run.sh进行统一入口&#x…

【ABAP】ME55双击跳转MD04增强

最近收到了一个需求&#xff0c;大致的要求是在标准报表ME55的ALV短文本列双击后跳转到MD04的详情。刚开始没有找到增强点想用间接的办法实现&#xff0c;在ME55上增加一列&#xff0c;展示想看到的内容&#xff0c;最后由于需要展示的内容太多&#xff0c;该方案被舍弃。 经过…

计算广告(七)

广告排序技术 为什么要做预估 谈到CTR/CVR/DeepCVR&#xff0c;尤其在互联网广告这块&#xff0c;简而言之&#xff0c;就是给某个网络服务使用者推送一个广告&#xff0c;该广告被点击的概率/浅层转化/深层转化概率 由于现有广告排序遵循ecpm ctr * cvr * deep_cvr * bid …