博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
为自己搭建一个鹊桥 -- Native Page与Web View之间的JSBridge实现方式
阅读量:6367 次
发布时间:2019-06-23

本文共 9115 字,大约阅读时间需要 30 分钟。

说起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  2  3  4  5     
6 7 8 25 26 27
28
29
30
31 32
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的方法出现在我们眼前:,这个方法是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         ///  6         /// 提示一条消息 7         ///  8         ///  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.OURBRIDGEOBJ23             this.wv.AddWebAllowedObject("OURBRIDGEOBJ", _bridge);24         }25     }
View Code

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

1  2  3  4  5     
6 7 8 18 19 20
21
22
23 24
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又支持,结合在一起,你懂的!

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

1     //这个attribute是必须的,有了他我们的对象才能传递给WebView 2     [AllowForWeb] 3     public sealed class Bridge 4     { 5         ///  6         /// 提示一条消息 7         ///  8         ///  9         public void showMessage(string msg)10         {11             new MessageDialog(msg).ShowAsync();12         }13 14         public Windows.Foundation.IAsyncOperation
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端,用来等待结果。

1  2  3  4  5     
6 7 8 33 34 35
36
37
38
39
40 41
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
SomethingChanged; 8 9 public void FireEvent()10 {11 SomethingChanged?.Invoke(this, 1234);12 }13 14 ///
15 /// 提示一条消息16 /// 17 ///
18 public void ShowMessage(string msg)19 {20 _methods?.ShowMessage(msg);21 }22 23 public IAsyncOperation
giveMeAnObject(int num)24 {25 return _methods?.GiveMmeAnObject(num);26 }27 28 ///
29 /// 初始化个方法的实现30 /// 31 ///
32 public void Init(IBridgeMethods obj)33 {34 _methods = obj;35 }36 }
View Code

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

1  2  3  4  5     
6 7 8 42 43 44
45
46
47
48
49
50 51 52 53
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.OURBRIDGEOBJ23             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     /// 2     /// 用来定义JSBridge中实现的方法3     /// 4     public interface IBridgeMethods5     {6         IAsyncOperation
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         ///  9         /// 提示一条消息10         /// 11         /// 12         public void ShowMessage(string msg)13         {14             _methods?.ShowMessage(msg);15         }16 17         public IAsyncOperation
giveMeAnObject(int num)18 {19 return _methods?.GiveMmeAnObject(num);20 }21 22 ///
23 /// 初始化个方法的实现24 /// 25 ///
26 public void Init(IBridgeMethods obj)27 {28 _methods = obj;29 }30 }
View Code

 

转载地址:http://hgema.baihongyu.com/

你可能感兴趣的文章
物联网市场迅猛发展 “中国芯”如何把握机会?
查看>>
aws 上使用elb 的多域名问题
查看>>
环球花木网的目标就是致力于打造成为“园林相关行业的专业性门户网站
查看>>
《编写高质量代码:改善c程序代码的125个建议》—— 建议14-1:尽量避免对未知的有符号数执行位操作...
查看>>
《C语言编程魔法书:基于C11标准》——2.2 整数在计算机中的表示
查看>>
全球程序员编程水平排行榜TOP50,中国排名第一
查看>>
HDFS 进化,Hadoop 即将拥抱对象存储?
查看>>
Edge 浏览器奇葩 bug:“123456”打印成“114447”
查看>>
Sirius —— 开源版的 Siri ,由 Google 支持
查看>>
《OpenGL ES应用开发实践指南:Android卷》—— 2.7 小结
查看>>
《Windows Server 2012活动目录管理实践》——第 2 章 部署第一台域控制器2.1 案例任务...
查看>>
Java Date Time 教程-时间测量
查看>>
Selector.wakeup实现注记
查看>>
《Java EE 7精粹》—— 第1章 Java EE 1.1 简介
查看>>
《Exchange Server 2013 SP1管理实践》——导读
查看>>
syslog:类Unix系统常用的log服务
查看>>
使用Annotation设计持久层
查看>>
深入实践Spring Boot2.4.1 Neo4j依赖配置
查看>>
Zen Cart 如何添加地址栏上的小图标
查看>>
SecureCrt 连接Redhat linux
查看>>