原文:为自己搭建一个鹊桥 -- Native Page与Web View之间的JSBridge实现方式

说起JSBridge,大家最熟悉的应该就是微信的WeixinJSBridge,通过它各个公众页面可以调用后台方法和微信进行交互,为用户提供相关功能。我们就来说说UWP下怎么样实现我们自己的JSBridge。

在win10之前,如果需要实现JSBridge,我们大概有两种方法:

1. window.external.notify

做过webview的小伙伴肯定都熟悉,html页面可以通过window.external.notify将消息发送出去,然后客户端使用WebView.ScriptNotify事件接收,但是两边都只能用字符串来交流,所以通常我们都会定义好消息格式(比如json)。现在在UWP中使用这种方法有个限制,就是你需要在.appxmanifest里把站点加到Content URIs中,告诉系统那些域名的js脚本是可以调用windows.external.notify方法的,当然如果是本地js就没有这个限制的,添加方法如下图。

但是我们总会有些特殊需求,比如微信/淘宝应用怎么办?域名随时可能增加,总不能每次都更新manifest,然后更新商店吧!在8.1的时候我们还可以使用WebView.AllowedScriptNotifyUris在应用中动态添加信任站点,但是win10中这个接口已经废弃了,如果你的应用并不需要频繁/动态更改信任站点,这个方法还是可用的。

后台处理完结果之后,可以通过WebView.InvokeScript/InvokeScriptAsync方法调用当前页面中的js方法:

第一个参数是js方法名,第二个参数是调用这个方法需要的参数。

需要注意的是这个方法很容易出错,一定要注意异常捕获:(, 而且生成的异常基本都是一些0xXXXXX的code。

 1     public sealed partial class MainPage : Page
 2     {
 3         BridgeObject.Bridge _bridge = new BridgeObject.Bridge();
 4
 5         public MainPage()
 6         {
 7             this.InitializeComponent();
 8
 9             this.wv.ScriptNotify += Wv_ScriptNotify;
10
11             this.Loaded += MainPage_Loaded;
12         }
13
14         private async void Wv_ScriptNotify(object sender, NotifyEventArgs e)
15         {
16             await (new MessageDialog(e.Value)).ShowAsync();
17
18             //返回结果给html页面
19             await this.wv.InvokeScriptAsync("recieve", new[] { "hehe, 我是个结果"});
20         }
21
22         private void MainPage_Loaded(object sender, RoutedEventArgs e)
23         {
24             //我们事先写好了一个本地html页面用来做测试
25             this.wv.Navigate(new Uri("ms-appx-web:///assets/html/index.html", UriKind.RelativeOrAbsolute));
26         }
27     }

View Code

html代码:

 1 <!DOCTYPE html>
 2
 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
 4 <head>
 5     <meta charset="utf-8" />
 6     <title></title>
 7
 8     <script>
 9
10         //通知后台
11         function func1()
12         {
13
14                 window.external.notify("this is a message");
15
16         }
17
18         //这个方法用来接收后台的结果
19         function recieve(value)
20         {
21             output.textContent = value;
22         }
23
24     </script>
25 </head>
26 <body>
27     <div style="margin-top:100px">
28         <button id="fun1Btn" onclick="func1();">Call method 1</button>
29         <div id="output"></div>
30     </div>
31 </body>
32 </html>

View Code

2. Url

是的,你没有看错,我们也可以通过url实现JSBridge,这也是我们在放弃上一种方法之后的一个备选方案,因为手淘就有之前说到的问题,站点可能不是固定的,而更新应用明显不是个明智的选择。具体就是每次html页面需要调用后台code的时候,都发起一次页面跳转,当然跳转的url符合一定的规则,并可以加上参数,然后我们用WebView.NavigationStarting事件截获这次跳转,并Cancel调这次跳转,这样一个看似可行的方案出炉啦,还是热乎的呢!!

代码其实很简单,就是解析url参数,然后再通过WebView.InvokeScript/InvokeScriptAsync方法返回结果给页面(这个方法不针对站点)。

 1         private void Wv_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
 2         {
 3             if(args.Uri.OriginalString.StartsWith("http://our/jsbridge/url/pattern"))
 4             {
 5                 //是一次jsbridge调用,取消本次跳转
 6                 args.Cancel = true;
 7
 8                 //这里具体解析url的参数
 9             }
10         }

View Code

仔细想想。。好像也没什么不对,够动态,够简单。。。但现实总是残酷的,实际使用过程中突然发现,WebView的Url有最大长度限制,而且这个值比Android和IOS都要小很多,导致很多参数被截断了,最后只好放弃了。

就在上面两种方案都不能完美适应所有需求的时候,另外一种bulingbuling的方法出现在我们眼前:WebView.AddWebAllowedObject,这个方法是win10中新添加的方法,允许我们把Windows Runtime对象直接传递给JS调用!

下面是这个方法的定义:

public void AddWebAllowedObject(string name, object pObject)

name是对象在js中对应的全局变量名,通过这个方法传入到html页面中的对象都是挂在js的window对象上的,pObject就是要传入的对象。

首先新建一个Windows Runtime Component工程,添加一个新的类Bridge,我们之后就把这个传给也main,看看这个类有什么特殊的。

 1     //这个attribute是必须的,有了他我们的对象才能传递给WebView
 2     [AllowForWeb]
 3     public sealed class Bridge
 4     {
 5         /// <summary>
 6         /// 提示一条消息
 7         /// </summary>
 8         /// <param name="msg"></param>
 9         public void showMessage(string msg)
10         {
11             new MessageDialog(msg).ShowAsync();
12         }
13
14
15     }

View Code

一切的魔法都在AllowForWebAttribute这个特性上,有了它,我们的对象就可以传递给webview,但是这里有一点一定要万分小心,必须在NavigationStarting调用AddWebAllowedObject方法才可以!(我不会告诉你,我在DomLoaded事件里折腾了好久。。。)

 1     public sealed partial class MainPage : Page
 2     {
 3         BridgeObject.Bridge _bridge = new BridgeObject.Bridge();
 4
 5         public MainPage()
 6         {
 7             this.InitializeComponent();
 8
 9             this.wv.NavigationStarting += Wv_NavigationStarting;
10
11             this.Loaded += MainPage_Loaded;
12         }
13
14         private void MainPage_Loaded(object sender, RoutedEventArgs e)
15         {
16             //我们事先写好了一个本地html页面用来做测试
17             this.wv.Navigate(new Uri("ms-appx-web:///assets/html/index.html", UriKind.RelativeOrAbsolute));
18         }
19
20         private void Wv_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
21         {
22             //OURBRIDGEOBJ这个是我们的对象插入到页面之后对象的变量名,这是一个全局变量,也就是window.OURBRIDGEOBJ
23             this.wv.AddWebAllowedObject("OURBRIDGEOBJ", _bridge);
24         }
25     }

View Code

现在是见证奇迹的时候了,来看看在js中怎么调用这个对象?(请忽略我这水平不怎么样的html code。。。)

 1 <!DOCTYPE html>
 2
 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
 4 <head>
 5     <meta charset="utf-8" />
 6     <title></title>
 7
 8     <script>
 9
10         function func1() {
11             // 首先判断我们对象是否正确插入
12             if (window.OURBRIDGEOBJ) {
13                 //调用的我们消息函数
14                 window.OURBRIDGEOBJ.showMessage("呵呵呵,我是个message");
15             }
16         }
17     </script>
18 </head>
19 <body>
20     <div style="margin-top:100px">
21         <button id="fun1Btn" onclick="func1();">Call method 1</button>
22     </div>
23 </body>
24 </html>

View Code

代码都很直接,唯一需要说明的就是一定要注意js中调用方法时首字母都是小写(即使你在后台定义的首字母大写!当然这应该也是为了符合js的使用习惯),来看看结果。

当然如果它只有这点本事的话,并不会让人很激动,毕竟我们以前也可以做到。

继续之前,想想win10之前如果要通过jsbridge调用后台代码实现一个异步操作会怎么实现呢?

1). 首先我们的js调用和WebView.InvokeScript是分开,所以通常我们要为每一次js调用生成一个id

2). 后台完成操作之后,通过InvokeScript方法返回结果时,需要把本次调用id传回去,告诉页面这个哪次调用的结果

3). 然后js再根据这个id回调继续之前的操作。

但是现在我们可以抛弃那些繁琐的步骤了,我们的Windows Runtime Component支持异步(IAsyncAction/IAsyncOperation<T>),而js又支持Promise,结合在一起,你懂的!

先给我们的类添加一个简单的异步方法。

 1     //这个attribute是必须的,有了他我们的对象才能传递给WebView
 2     [AllowForWeb]
 3     public sealed class Bridge
 4     {
 5         /// <summary>
 6         /// 提示一条消息
 7         /// </summary>
 8         /// <param name="msg"></param>
 9         public void showMessage(string msg)
10         {
11             new MessageDialog(msg).ShowAsync();
12         }
13
14         public Windows.Foundation.IAsyncOperation<int> giveMeAnObject(int num)
15         {
16             return Task.Run(async () =>
17             {
18                 //延迟3秒钟,模拟异步任务:)
19                 await Task.Delay(3000);
20
21                 return ++num;
22             }).AsAsyncOperation();
23         }
24     }

View Code

接下来我们在js端,用promise.then来等待结果。

 1 <!DOCTYPE html>
 2
 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
 4 <head>
 5     <meta charset="utf-8" />
 6     <title></title>
 7
 8     <script>
 9
10         function func1() {
11             // 首先判断我们对象是否正确插入
12             if (window.OURBRIDGEOBJ) {
13                 //调用的我们消息函数
14                 window.OURBRIDGEOBJ.showMessage("呵呵呵,我是个message");
15             }
16         }
17
18         function func2() {
19             if (window.OURBRIDGEOBJ) {
20
21                 //对于js来说winrt的异步操作都会对应到promise上
22                 var result = window.OURBRIDGEOBJ.giveMeAnObject(12);
23
24                 // 等待结果
25                 result.then(function (nextNum) {
26                     // nextNum就是IAsyncOperation<int>的真正返回值
27                     output.textContent = nextNum;
28                 });
29
30             }
31         }
32     </script>
33 </head>
34 <body>
35     <div style="margin-top:100px">
36         <button id="fun1Btn" onclick="func1();">Call method 1</button>
37         <button id="fun2Btn" onclick="func2();">Call method 2</button>
38         <div id="output" />
39     </div>
40 </body>
41 </html>

View Code

运行起来,等待3秒之后,结果出来了!

另外这里再补充下评论中小伙伴关于事件的调用方法,其实事件的使用很简单,唯一需要注意的是c#的事件名称,到js里全都变成了小写的,下面是代码。

首先为我们的Bridge类添加一个事件和触发事件的公开方法(方便调试)。

 1     //这个attribute是必须的,有了他我们的对象才能传递给WebView
 2     [AllowForWeb]
 3     public sealed class Bridge
 4     {
 5         private IBridgeMethods _methods = null;
 6
 7         public event EventHandler<int> SomethingChanged;
 8
 9         public void FireEvent()
10         {
11             SomethingChanged?.Invoke(this, 1234);
12         }
13
14         /// <summary>
15         /// 提示一条消息
16         /// </summary>
17         /// <param name="msg"></param>
18         public void ShowMessage(string msg)
19         {
20             _methods?.ShowMessage(msg);
21         }
22
23         public IAsyncOperation<int> giveMeAnObject(int num)
24         {
25             return _methods?.GiveMmeAnObject(num);
26         }
27
28         /// <summary>
29         /// 初始化个方法的实现
30         /// </summary>
31         /// <param name="obj"></param>
32         public void Init(IBridgeMethods obj)
33         {
34             _methods = obj;
35         }
36     }

View Code

然后在js中添加listener,这里是要用js的标准方法!

 1 <!DOCTYPE html>
 2
 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
 4 <head>
 5     <meta charset="utf-8" />
 6     <title></title>
 7
 8     <script>
 9
10         function func1() {
11             // 首先判断我们对象是否正确插入
12             if (window.OURBRIDGEOBJ) {
13                 //调用的我们消息函数
14                 window.OURBRIDGEOBJ.showMessage("呵呵呵,我是个message");
15             }
16         }
17
18         function func2() {
19             if (window.OURBRIDGEOBJ) {
20
21                 //对于js来说winrt的异步操作都会对应到promise上
22                 var result = window.OURBRIDGEOBJ.giveMeAnObject(12);
23
24                 // 等待结果
25                 result.then(function (nextNum) {
26                     // nextNum就是IAsyncOperation<int>的真正返回值
27                     output.textContent = nextNum;
28                 });
29
30             }
31         }
32
33         function bindEvent() {
34             if (window.OURBRIDGEOBJ) {
35                 //注意事件名称!!!
36                 window.OURBRIDGEOBJ.addEventListener("somethingchanged", function (value) {
37                     output.textContent = "我是个事件回调: value="+value;
38                 });
39             }
40         }
41     </script>
42 </head>
43 <body>
44     <div style="margin-top:100px">
45         <button id="fun1Btn" onclick="func1();">Call method 1</button>
46         <button id="fun2Btn" onclick="func2();">Call method 2</button>
47         <button id="bindBtn" onclick="bindEvent();">Bind event</button>
48         <div id="output" />
49     </div>
50
51
52 </body>
53 </html>

View Code

最后在窗口上添加一个触发按钮。

 1     public sealed partial class MainPage : Page
 2     {
 3         BridgeObject.Bridge _bridge = new BridgeObject.Bridge();
 4
 5         public MainPage()
 6         {
 7             this.InitializeComponent();
 8
 9             this.wv.NavigationStarting += Wv_NavigationStarting;
10
11             this.Loaded += MainPage_Loaded;
12         }
13
14         private void MainPage_Loaded(object sender, RoutedEventArgs e)
15         {
16             //我们事先写好了一个本地html页面用来做测试
17             this.wv.Navigate(new Uri("ms-appx-web:///assets/html/index.html", UriKind.RelativeOrAbsolute));
18         }
19
20         private void Wv_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
21         {
22             //OURBRIDGEOBJ这个是我们的对象插入到页面之后对象的变量名,这是一个全局变量,也就是window.OURBRIDGEOBJ
23             this.wv.AddWebAllowedObject("OURBRIDGEOBJ", _bridge);
24         }
25
26         private void Button_Click(object sender, RoutedEventArgs e)
27         {
28             // 触发自定义事件
29             _bridge.FireEvent();
30         }
31     }

View Code

结果如下。

最后如果你觉得写component限制太多的话(继承都不让用。。),可以使用接口定义方法,然后在类库中实现这些方法也是一个不错的方案,下面是一个比较简单的实现供参考。

我们的jsbridge接口,包含我们准备提供的方法。

1     /// <summary>
2     /// 用来定义JSBridge中实现的方法
3     /// </summary>
4     public interface IBridgeMethods
5     {
6         IAsyncOperation<int> GiveMmeAnObject(int num);
7         void ShowMessage(string message);
8     }

View Code

修改我们的Bridge类,所有的方法都通过上面的接口来提供。

 1    //这个attribute是必须的,有了他我们的对象才能传递给WebView
 2     [AllowForWeb]
 3     public sealed class Bridge
 4     {
 5         private IBridgeMethods _methods = null;
 6
 7
 8         /// <summary>
 9         /// 提示一条消息
10         /// </summary>
11         /// <param name="msg"></param>
12         public void ShowMessage(string msg)
13         {
14             _methods?.ShowMessage(msg);
15         }
16
17         public IAsyncOperation<int> giveMeAnObject(int num)
18         {
19             return _methods?.GiveMmeAnObject(num);
20         }
21
22         /// <summary>
23         /// 初始化个方法的实现
24         /// </summary>
25         /// <param name="obj"></param>
26         public void Init(IBridgeMethods obj)
27         {
28             _methods = obj;
29         }
30     }

View Code

为自己搭建一个鹊桥 -- Native Page与Web View之间的JSBridge实现方式相关推荐

  1. linux box 信息发布,使用Instantbox快速搭建一个开箱即用的Web端临时Linux系统

    说明:我们很多时候想学习下Linux或者程序搭建,然后出错了,不会解决的可能会直接重装系统,很麻烦,然后这里博主就找到了个可以解决该问题的工具instantbox,使用该工具可以让你仅通过浏览器的情况 ...

  2. IDEA搭建一个SpringBoot项目——十分详细(web+mysql)

    前排提示: IDEA版本:IntelliJ IDEA 2021.1.1 专业版(是否为专业版影响不大) 搭建目的:前端web页面能sql教程够python基础教程获取到MySQL数据库中的数据 详细步 ...

  3. Django+vue搭建一个前后端分离的web 一

    项目用到的技术: Vue.bootstrap.Django.python: 前后端分离项目的搭建和目录结构介绍: 创建Django项目: jango-admin startproject 项目名称 目 ...

  4. 用vs完整的搭建一个项目流程(包括多个项目之间的依赖) 方法一

    一个完整的项目应该是这样的图,有一个主程序,依赖其他的项目 这边以控制台主程序输出.以Debug x64为例,如果是release的话需要重新配置一下  第一步: 将项目配置改成Debugx64,这边 ...

  5. 如何优雅的用 Nginx 在公网上快速搭建一个加密数据通道

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 最近在跨机房做一个部署,因为机房之间暂时没有专线,所以流量需要经过公网.对于经过公网的流量,我们一般需要做以下的安 ...

  6. 搭建一个免费的,无限流量的Blog----github Pages和Jekyll入门

    喜欢写Blog的人,会经历三个阶段. 第一阶段,刚接触Blog,觉得很新鲜,试着选择一个免费空间来写. 第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客. 第三阶段,觉得独立博客的管 ...

  7. go html vue,用Go+Vue.js快速搭建一个Web应用(初级demo)

    Vue.js做为目前前端最热门的库之一,为快速构建并开发前端项目多了一种思维模式.本文给大家介绍用Go+Vue.js快速搭建一个Web应用(初级demo). 环境准备: 1. 安装go语言,配置go开 ...

  8. 从入门到进阶|如何基于WebRTC搭建一个视频会议

    文|网易智慧企业流媒体服务器天团 导读:疫情期间,视频会议等远程办公产品备受青睐,众多互联网玩家切入视频会议市场,加剧市场竞争.但是,产品虽多,能够带来稳定可靠体验的产品却凤毛麟角,它的难点在哪里?视 ...

  9. 在Github上面搭建一个自己域名的Hexo博客

    前言 在一次看到别人的博客主页,觉得设计很漂亮.但是由于自己对于前台这块没什么办法,煞是羡慕.偶然中发现这种样式是在Github上面搭建的,使用的是Next主题.于是便想自己也搭建一个,于是便去就去查 ...

最新文章

  1. oracle valueerror,Oracle VALUE_ERROR异常(挑战题编号000005)
  2. SAP MM UB类型STO不能转供应商寄售库存?
  3. 永远的Macromedia, Macromedia Forever
  4. 关于Oracle数据库19c中的关键字和保留字的说明
  5. python 廖雪峰数据分析统计服_廖雪峰Python总结1
  6. java sqlserver ssl_拦截SQLSERVER的SSL加密通道替换传输过程中的用户名密码实现运维审计(一)...
  7. 【WCF】WCF服务库和WCF服务应用程序的区别
  8. 是谁榨干了 Android 设备的电量和流量?!| 极客头条
  9. java---键盘输入,写入到本地硬盘的数据
  10. php 判断是否在线,关于判断用户是否在线的问题!!!
  11. c语言stdio函数大全,初学者常用的stdio库,原来还有这么多知识点
  12. Unity-粒子特效
  13. SPSS基础操作(一):用幂指数型的权函数建立加权最小二乘回归方程
  14. 那个单位用计算机系统冷却,消防知识100题
  15. java 解析json字符串
  16. order by(排序查询结果)和LIMT
  17. 腾讯云服务器从购买到搭建tomcat
  18. mysql 字符串的hash函数_经典字符串Hash函数介绍 - yanjun_1982的专栏 - CSDNBlog
  19. JAVA 初步学习一
  20. 2020.9.30 PYTHON 自复习笔记

热门文章

  1. html5 canvas 加载图片
  2. python sys.path.append(),sys.path.insert()用法
  3. 【英文文本分类实战】之四——词典提取与词向量提取
  4. 【Vue】宝塔面板服务器配置Vue项目
  5. 人群密度估计--Spatiotemporal Modeling for Crowd Counting in Videos
  6. 行人检测--What Can Help Pedestrian Detection?
  7. rtk服务器协议,rtk的服务器ip地址
  8. 如何将文件放到服务器,如何将服务器文件放到云服务器
  9. fusioncompute中cpu可以设置的qos参数有哪些?_kubernetes 中 Qos 的设计与实现
  10. php中的核心函数有哪些,PHP内核探索:函数的分类