h5页面如何与原生交互

news2024/11/15 7:42:48

本文讲述h5页面跟原生通信,比如在app内,调用相机,获取相册内的图片,在app内拉起微信小程序等等,h5页面没有这么多权限能够直接调用移动端的原生能力,这个时候就需要与原生进行通讯,传递一个信号给原生这边,然后原生直接调用手机端的能力。

下面分别讲解h5与Android,ios系统通信

JS与Android

Android处理

其实h5能够在app内中存在,就是在app中有webview的存在,在webview中可以是一个h5的链接,这样这个h5页面就能够app内展现。

Android中,往webview里面注入WebViewJavascriptBridge.js

image.png

我们能够看到在android往webview注入了一段js代码,接下来我们看看这段代码的逻辑。

image.png

这里往webview中的document注入事件webViewJavascriptBridgeReady,向window中添加一个对象WebViewJavascriptBridge。

注意这个WebViewJavascriptBridge对象中包含了init,send,registerHandler,callHandler,_handleMessageFormNative函数。

接下来看这个init函数中的逻辑

image.png

使用 iframe 创建消息队列的主要原因是:

  1. 实现跨域通信。iframe 可以被嵌入到不同域的页面中,使得父子页面可以跨域通信。
  2. 解耦通信方。使用 iframe,可以使得父页面和子页面的 JavaScript 环境相互隔离。父页面无法直接访问子页面的变量和函数,只能通过消息通信。这减少了两端的依赖,有利于后期维护。
  3. 兼容性好。iframe 比较老的技术,广泛支持各种浏览器,不需要担心兼容性问题。
  4. 简单易用。向 iframe 发送消息和监听消息非常简单,不需要额外的库或工具。

image.png

这里可以发现,初始化就完成了创建消息队列,初始化默认消息队列,还有最重要的发送消息。

接下来就看h5怎么接收和处理这些通信了。

h5页面处理

到这里,应该也能猜到一些了,因为上面Android注入了一段js代码,从中创建了webViewJavascriptBridgeReady事件,而且往window中添加了一个对象。

那h5这边初始化也很简单,就是判断有没有window.WebViewJavascriptBridge,有的话,就直接可以传输了,没有的话,就需要监听webViewJavascriptBridgeReady事件,当这个事件执行了,就可以进行通信了。


    if (window.WebViewJavascriptBridge) {
        //do your work here
    } else {
        document.addEventListener(
            'WebViewJavascriptBridgeReady'
            , function() {
                //do your work here
            },
            false
        );
    }

而这个window.WebViewJavascriptBridge也就是常说的通讯桥。无论是h5调用原生方法,还是原生想要触发h5方法,都是需要使用到通讯桥。

如果未定义 window.WebViewJavascriptBridge ,则将所有 JsBridge 函数调用放入 window.WVJBCallbacks 数组中,当触发 WebViewJavascriptBridgeReady 事件时,此任务队列将被刷新。

function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) {
    return callback(WebViewJavascriptBridge);
}
    if (window.WVJBCallbacks) {
    return window.WVJBCallbacks.push(callback);
}
    window.WVJBCallbacks = [callback];
}

调用 setupWebViewJavascriptBridge ,然后使用桥注册处理程序或调用 Java 处理程序:

这里的registerHandler和callHandler应该很熟悉,因为在Android中注入的js代码中就有的这两个方法,这两个方法就在window.WebViewJavascriptBridge对象上。

setupWebViewJavascriptBridge(function(bridge) {
    bridge.registerHandler('JS to Android', function(data, responseCallback) {
        console.log("Android received response:", data);
        responseCallback(data);
    });
    bridge.callHandler('Android to JS', {'key':'value'}, function(responseData) {
           console.log("JS received response:", responseData);
    });
});

registerHandler方法是h5页面传递给Android的数据

callHandler方法是Android传递给h5的数据

总结一下工作流程:

  • JS端判断是否有window.WebViewJavascriptBridge对象;
  • 有的话就进入回调,进行通信;
  • 没有的话就需要, 将所有 JsBridge 函数调用放入 window.WVJBCallbacks 数组中,当触发 WebViewJavascriptBridgeReady 事件时,此任务队列将被刷新;

JS与IOS

IOS处理

在ios处理中,需要介绍一下WebViewJavascriptBridge

image.png

WebViewJavascriptBridge 是用于在 WKWebView,UIWebView 和 WebView 中的 Obj-C 和 JavaScript 之间发送消息的 iOS / OSX 桥接器。

这里本质跟Android是一样的,也是注入了一段js代码,然后注入到webview组件中,实现原生与js的交互。

image.png

我们先看看js处理的逻辑

image.png

这里跟Android还是很像,往window中注入一个通讯桥,通讯桥的名称是WebViewJavascriptBridge,然后在通信桥上有registerHandler,callHandler,disableJavscriptAlertBoxSafetyTimeout,_fetchQueue,_handleMessageFromObjC等方法。

接下来看一下如何往webview中注入的

image.png

与上面的Android不同的是,这是ObjC语言编写的,Android是Java语言。

// WKNavigationDelegate 协议方法,用于监听 Request 并决定是否允许导航
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    // webView 校验
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

    // 核心代码
    if ([_base isWebViewJavascriptBridgeURL:url]) { // 判定 WebViewJavascriptBridgeURL
        if ([_base isBridgeLoadedURL:url]) { // 判定 BridgeLoadedURL
            // 注入 JS 代码
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) { // 判定 QueueMessageURL
            // 刷新消息队列
            [self WKFlushMessageQueue];
        } else {
            // 记录未知 bridge msg 日志
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    // 调用 _webViewDelegate 对应的代理方法
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

h5页面处理

这里跟Android处理不同,启动通讯桥时,在h5页面也需要创建一个iframe标签来进行通信

function setupWebViewJavascriptBridge(callback) {
	if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
	if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
	window.WVJBCallbacks = [callback];
	// 创建一个 iframe
	var WVJBIframe = document.createElement('iframe');
	// 设置 iframe 为不显示
	WVJBIframe.style.display = 'none';
	// 将 iframe 的 src 置为 'https://__bridge_loaded__'
	WVJBIframe.src = 'https://__bridge_loaded__';
	// 将 iframe 加入到 document.documentElement
	document.documentElement.appendChild(WVJBIframe);
	setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

Note: 假 Request 的发起有两种方式,-1:location.href -2:iframe。通过 location.href 有个问题,就是如果 JS 多次调用原生的方法也就是 location.href 的值多次变化,Native 端只能接受到最后一次请求,前面的请求会被忽略掉,所以这里 WebViewJavascriptBridge 选择使用 iframe。

因为加入了 src 为 https://__bridge_loaded__ 的 iframe 元素,我们上面截获 url 的代理方法就会拿到一个 https://__bridge_loaded__ 的 url,由于 https 满足判定 WebViewJavascriptBridgeURL,将会进入核心代码区域接着会被判定为 BridgeLoadedURL 执行注入 JS 代码的方法,即 [_base injectJavascriptFile];

这样就形成了初始化桥,与android中的将所有 JsBridge 函数调用放入 window.WVJBCallbacks 数组中,后面要处理的逻辑一样了。

- (void)injectJavascriptFile {
    // 获取到 WebViewJavascriptBridge_JS 的代码
    NSString *js = WebViewJavascriptBridge_js();
    // 将获取到的 js 通过代理方法注入到当前绑定的 WebView 组件
    [self _evaluateJavascript:js];
    // 如果当前已有消息队列则遍历并分发消息,之后清空消息队列
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}

最后,调用 setupWebViewJavascriptBridge ,然后使用桥注册处理程序并调用 ObjC 处理程序:

setupWebViewJavascriptBridge(function(bridge) {
	
    /* Initialize your app here */

    bridge.registerHandler('JS to IOS', function(data, responseCallback) {
        console.log("IOS received response:", data)
        responseCallback(data)
    })
    bridge.callHandler('IOS to JS', {'key':'value'}, function responseCallback(responseData) {
           console.log("JS received response:", responseData)
    })
})

最后总结一下WebViewJavascriptBridge 的工作流:

  • JS 端加入 src 为 https://__bridge_loaded__ 的 iframe
  • Native 端检测到 Request,检测如果是 __bridge_loaded__ 则通过当前的 WebView 组件注入 WebViewJavascriptBridge_JS 代码
  • 注入代码成功之后会加入一个 messagingIframe,其 src 为 https://__wvjb_queue_message__
  • 之后不论是 Native 端还是 JS 端都可以通过 registerHandler 方法注册一个两端约定好的 HandlerName 的处理,也都可以通过 callHandler 方法通过约定好的 HandlerName 调用另一端的处理(两端处理消息的实现逻辑对称)

看到这里,其实Android和IOS系统最大的不同,相信大家已经感受到了,那就处理流程顺序不太一样。

但其实在公司的项目里面,上面这些知识只是基础,而公司内,都会制定一套完整的sdk来,各个端对齐,而sdk中,也不是简单的通信,还会有各种兼容,比如当前运行的系统,是否在指定的app内,app的版本信息,各个通信函数是否支持最低的版本号…

如果需要封装自己项目中的webview组件,还需要另外实现HTTP cookie注入,自定义User-Agent,白名单或者权限校验等等功能,更进一步还需要对webview组件进行初始化速度,页面渲染速度以及页面缓存策略的优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/736004.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

链表、列表、列表项

链表、列表、列表项 FreeRTOS 列表与列表项 List_t //List_t 列表typedef struct xLIST{listFIRST_LIST_INTEGRITY_CHECK_VALUE //校验值volatile UBaseType_t uxNumberOfItems; // 列表中的列表项数量:用于记录列表中列表项的个数(不包含 xListEnd&#xff0…

AcWing 1497:树的遍历

【题目来源】https://pintia.cn/problem-sets/994805342720868352/exam/problems/994805485033603072https://www.acwing.com/problem/content/description/1499/【题目描述】 一个二叉树,树中每个节点的权值互不相同。 现在给出它的后序遍历和中序遍历,…

Verilog 基础知识(一) Verilog 基础语法与注意事项

基础知识 0.1 模块(Module) Verilog中的module可以看成一个具有输入输出端口的黑盒子,该黑盒子有输入和输出接口(信号),通过把输入在盒子中执行某些操作来实现某项功能。(类似于C语言中的函数) 图1 模块示意图 0.1.1 模块描述 图1 所示的顶层模块(top…

1.Git使用技巧-常用命令2

1.Git使用技巧-常用命令2 文章目录 1.Git使用技巧-常用命令2一、本地仓库整理二、查看本地仓库查看过滤 二、逆向操作1. 检出覆盖本地文件 workspace -> NULL1.1 删除workspace 文件 2. 本地仓库回滚3. 远程仓库1. 远程仓库未基于代码做修改 逆操作总结 三、删除1. 删除远程…

基于 Jieba 和 Word2vec 的关键词词库构建

最近有空,把论文中用到的技术和大家分享一下(以组件化的形式),本篇将讲述如何从大量的语料中获取诸多关键词和构建关键词词库或 xx 关键词词库(细分领域)。举例以购物网站的在线评论作为语料库,…

Python实现PSO粒子群优化算法优化Catboost回归模型(CatBoostRegressor算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 PSO是粒子群优化算法(Particle Swarm Optimization)的英文缩写,是一…

Git--多人协作开发

文章目录 前言一、多人协作一开发目标将dev分支内容合并至master分支 二、多人协作二开发者1操作开发者2操作突发情况内容合并至master分支 三、解决git branch -a打印已被删除的远程分支的方法总结 前言 目前,我们所完成的工作如下 : 基本完成Git的所有本地库的相关操作,git…

学无止境·MySQL(4-2)(多表查询加强版)

多表练习试题 试题2(加强版)1、创建表2、表中添加数据3、找出销售部门中年纪最大的员工的姓名4、求财务部门最低工资的员工姓名5、列出每个部门收入总和高于9000的部门名称6、求工资在7500到8500元之间,年龄最大的人的姓名及部门7、找出销售部…

【Python】面向对象 - 封装 ② ( 访问私有成员 | 对象无法访问私有变量 / 方法 | 类内部访问私有成员 )

文章目录 一、访问私有成员1、对象无法访问私有变量2、对象无法访问私有方法3、类内部访问私有成员 一、访问私有成员 1、对象无法访问私有变量 在下面的 Python 类 Student 中 , 定义了私有的成员变量 , # 定义私有成员__address None该私有成员变量 , 只能在类内部进行访问 …

分析图中常见的这种“箭头”,如何用GIS绘制?

小伙伴们, 在各种分析图、空间结构图中, 是不是经常看到这种“扇形”的箭头, 同时伴随着一些“引绿入城”等分析语言。 于是,你有没有好奇, 这种箭头是怎么做出来的? 强大的GIS是否可以绘制出这种箭头? 还是那句话—— 别问,问就是能! 其实ArcMap 和 ArcGIS P…

【大数据实战电商推荐系统】概述版

文章目录 第1章 项目体系框架设计(说明书)第2章 工具环境搭建(说明书)第3章 项目创建并初始化业务数据3.1 IDEA创建Maven项目(略)3.2 数据加载准备(说明书)3.3 数据初始化到MongoDB …

41. 同时在线人数问题

文章目录 题目需求思路一实现一学习链接题目来源 题目需求 现有各直播间的用户访问记录表(live_events)如下。 表中每行数据表达的信息为:一个用户何时进入了一个直播间,又在何时离开了该直播间。 现要求统计各直播间最大同时在…

Stable Diffusion系列课程上:安装、提示词入门、常用模型(checkpoint、embedding、LORA)、放大算法、局部重绘、常用插件

文章目录 一、Stable Diffusion简介与安装二、文生图(提示词解析)2.1 提示词入门2.2 权重2.3 负面提示词( Negative prompt)2.4 出图参数设置2.5 新手念咒方法 三、图生图3.1 图生图入门3.2 随机种子解析3.3 图生图拓展 四、模型4…

MySQL基础篇第3章(运算符)

文章目录 1、算术运算符1.1 加法与减法运算符1.2 乘法与除法运算符1.3 求模&#xff08;求余&#xff09;运算符 2、比较运算符2.1 等号运算符2.2 安全等于<>2.3 不等于运算符2.4 空运算符2.5 非空运算符2.6 最小值运算符2.7 最大值运算符2.8 BETWEEN AND运算符2.9 IN运算…

cmake操作目录

目录 cmake如何使用子目录 demo cmake生成build目录结构 如果指定子目录编译文件名字(binaryDir) 如果指定子目录编译的路径(binaryDir) 子目录相关的作用域 demo 子目录中定义project cmake如何使用子目录 如果项目比较小的话,我们将所有源码文件放到一个目录里面是没…

wpf border控件和Effect学习

Border&#xff08;边框&#xff09;控件绘制一个边框、一个背景。 常用的属性&#xff0c; Background&#xff1a;填充 Border 边界之间的区域或者说是绘制该区域的背景&#xff0c;是一个Brush对象。 BorderBrush&#xff1a;用于绘制外部边框颜色&#xff0c;是Bru…

Vue 和 React 前端框架的比较

Vue 和 React 前端框架的比较 本文研究了流行的前端框架 Vue 和 React 之间的区别。通过对它们的学习曲线、视图层处理方式、组件化开发、响应式数据处理方式和生态系统及社区支持进行比较分析&#xff0c;得出了它们在不同方面的优劣和特点。该研究对于开发者在选择合适的前端…

QC51XX---I2c使用

QCC51XX---系统学习目录_嵌入式学习_force的博客-CSDN博客 今天我们聊聊I2C的使用。在开发过程中多多少少会加入一些外围传感器可以给用户带来更好的使用体验。例如,利用接近传感器识别戴上耳机和取下耳机,从而去自动暂停播放音乐,或接听转移通话。又例如,用触摸或敲击替代…

【vant】打开vant表单的正确形式(基于vant表单的二次封装)

前言 最近在用vant做关于移动端的项目,由于表单字段太多,不想写直接写到template中,这样太繁琐了,所以我们以把表单弄成schema配置形式: // data.ts import type { ItemSchema } from /typing/helper; import { StudentField } from /components; import { getDictTextByCode…

在tplink路由器xdr6088中运行Docker潘多拉(pengzhile/pandora)遇到无法访问的问题

在xdr6088中搜索安装pengzhile/pandora一切正常&#xff0c;但是按照常规运行docker后&#xff0c;直接访问8899端口无法打开页面&#xff0c;进入终端 运行如下命令 /usr/local/bin/python /usr/local/bin/pandora-cloud -s 0.0.0.0:8899 即可成功运行&#xff0c;然后客户端…