为自己搭建一个鹊桥 -- 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。
public sealed partial class MainPage : Page
{
BridgeObject.Bridge _bridge = new BridgeObject.Bridge(); public MainPage()
{
this.InitializeComponent(); this.wv.ScriptNotify += Wv_ScriptNotify; this.Loaded += MainPage_Loaded;
} private async void Wv_ScriptNotify(object sender, NotifyEventArgs e)
{
await (new MessageDialog(e.Value)).ShowAsync(); //返回结果给html页面
await this.wv.InvokeScriptAsync("recieve", new[] { "hehe, 我是个结果"});
} private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
//我们事先写好了一个本地html页面用来做测试
this.wv.Navigate(new Uri("ms-appx-web:///assets/html/index.html", UriKind.RelativeOrAbsolute));
}
}
html代码:
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title> <script> //通知后台
function func1()
{ window.external.notify("this is a message"); } //这个方法用来接收后台的结果
function recieve(value)
{
output.textContent = value;
} </script>
</head>
<body>
<div style="margin-top:100px">
<button id="fun1Btn" onclick="func1();">Call method 1</button>
<div id="output"></div>
</div>
</body>
</html>
2. Url
是的,你没有看错,我们也可以通过url实现JSBridge,这也是我们在放弃上一种方法之后的一个备选方案,因为手淘就有之前说到的问题,站点可能不是固定的,而更新应用明显不是个明智的选择。具体就是每次html页面需要调用后台code的时候,都发起一次页面跳转,当然跳转的url符合一定的规则,并可以加上参数,然后我们用WebView.NavigationStarting事件截获这次跳转,并Cancel调这次跳转,这样一个看似可行的方案出炉啦,还是热乎的呢!!
代码其实很简单,就是解析url参数,然后再通过WebView.InvokeScript/InvokeScriptAsync方法返回结果给页面(这个方法不针对站点)。
private void Wv_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if(args.Uri.OriginalString.StartsWith("http://our/jsbridge/url/pattern"))
{
//是一次jsbridge调用,取消本次跳转
args.Cancel = true; //这里具体解析url的参数
}
}
仔细想想。。好像也没什么不对,够动态,够简单。。。但现实总是残酷的,实际使用过程中突然发现,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,看看这个类有什么特殊的。
//这个attribute是必须的,有了他我们的对象才能传递给WebView
[AllowForWeb]
public sealed class Bridge
{
/// <summary>
/// 提示一条消息
/// </summary>
/// <param name="msg"></param>
public void showMessage(string msg)
{
new MessageDialog(msg).ShowAsync();
} }
一切的魔法都在AllowForWebAttribute这个特性上,有了它,我们的对象就可以传递给webview,但是这里有一点一定要万分小心,必须在NavigationStarting调用AddWebAllowedObject方法才可以!(我不会告诉你,我在DomLoaded事件里折腾了好久。。。)
public sealed partial class MainPage : Page
{
BridgeObject.Bridge _bridge = new BridgeObject.Bridge(); public MainPage()
{
this.InitializeComponent(); this.wv.NavigationStarting += Wv_NavigationStarting; this.Loaded += MainPage_Loaded;
} private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
//我们事先写好了一个本地html页面用来做测试
this.wv.Navigate(new Uri("ms-appx-web:///assets/html/index.html", UriKind.RelativeOrAbsolute));
} private void Wv_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
//OURBRIDGEOBJ这个是我们的对象插入到页面之后对象的变量名,这是一个全局变量,也就是window.OURBRIDGEOBJ
this.wv.AddWebAllowedObject("OURBRIDGEOBJ", _bridge);
}
}
现在是见证奇迹的时候了,来看看在js中怎么调用这个对象?(请忽略我这水平不怎么样的html code。。。)
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title> <script> function func1() {
// 首先判断我们对象是否正确插入
if (window.OURBRIDGEOBJ) {
//调用的我们消息函数
window.OURBRIDGEOBJ.showMessage("呵呵呵,我是个message");
}
}
</script>
</head>
<body>
<div style="margin-top:100px">
<button id="fun1Btn" onclick="func1();">Call method 1</button>
</div>
</body>
</html>
代码都很直接,唯一需要说明的就是一定要注意js中调用方法时首字母都是小写(即使你在后台定义的首字母大写!当然这应该也是为了符合js的使用习惯),来看看结果。
当然如果它只有这点本事的话,并不会让人很激动,毕竟我们以前也可以做到。
继续之前,想想win10之前如果要通过jsbridge调用后台代码实现一个异步操作会怎么实现呢?
1). 首先我们的js调用和WebView.InvokeScript是分开,所以通常我们要为每一次js调用生成一个id
2). 后台完成操作之后,通过InvokeScript方法返回结果时,需要把本次调用id传回去,告诉页面这个哪次调用的结果
3). 然后js再根据这个id回调继续之前的操作。
但是现在我们可以抛弃那些繁琐的步骤了,我们的Windows Runtime Component支持异步(IAsyncAction/IAsyncOperation<T>),而js又支持Promise,结合在一起,你懂的!
先给我们的类添加一个简单的异步方法。
//这个attribute是必须的,有了他我们的对象才能传递给WebView
[AllowForWeb]
public sealed class Bridge
{
/// <summary>
/// 提示一条消息
/// </summary>
/// <param name="msg"></param>
public void showMessage(string msg)
{
new MessageDialog(msg).ShowAsync();
} public Windows.Foundation.IAsyncOperation<int> giveMeAnObject(int num)
{
return Task.Run(async () =>
{
//延迟3秒钟,模拟异步任务:)
await Task.Delay(); return ++num;
}).AsAsyncOperation();
}
}
接下来我们在js端,用promise.then来等待结果。
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title> <script> function func1() {
// 首先判断我们对象是否正确插入
if (window.OURBRIDGEOBJ) {
//调用的我们消息函数
window.OURBRIDGEOBJ.showMessage("呵呵呵,我是个message");
}
} function func2() {
if (window.OURBRIDGEOBJ) { //对于js来说winrt的异步操作都会对应到promise上
var result = window.OURBRIDGEOBJ.giveMeAnObject(12); // 等待结果
result.then(function (nextNum) {
// nextNum就是IAsyncOperation<int>的真正返回值
output.textContent = nextNum;
}); }
}
</script>
</head>
<body>
<div style="margin-top:100px">
<button id="fun1Btn" onclick="func1();">Call method 1</button>
<button id="fun2Btn" onclick="func2();">Call method 2</button>
<div id="output" />
</div>
</body>
</html>
运行起来,等待3秒之后,结果出来了!
另外这里再补充下评论中小伙伴关于事件的调用方法,其实事件的使用很简单,唯一需要注意的是c#的事件名称,到js里全都变成了小写的,下面是代码。
首先为我们的Bridge类添加一个事件和触发事件的公开方法(方便调试)。
//这个attribute是必须的,有了他我们的对象才能传递给WebView
[AllowForWeb]
public sealed class Bridge
{
private IBridgeMethods _methods = null; public event EventHandler<int> SomethingChanged; public void FireEvent()
{
SomethingChanged?.Invoke(this, );
} /// <summary>
/// 提示一条消息
/// </summary>
/// <param name="msg"></param>
public void ShowMessage(string msg)
{
_methods?.ShowMessage(msg);
} public IAsyncOperation<int> giveMeAnObject(int num)
{
return _methods?.GiveMmeAnObject(num);
} /// <summary>
/// 初始化个方法的实现
/// </summary>
/// <param name="obj"></param>
public void Init(IBridgeMethods obj)
{
_methods = obj;
}
}
然后在js中添加listener,这里是要用js的标准方法!
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title> <script> function func1() {
// 首先判断我们对象是否正确插入
if (window.OURBRIDGEOBJ) {
//调用的我们消息函数
window.OURBRIDGEOBJ.showMessage("呵呵呵,我是个message");
}
} function func2() {
if (window.OURBRIDGEOBJ) { //对于js来说winrt的异步操作都会对应到promise上
var result = window.OURBRIDGEOBJ.giveMeAnObject(12); // 等待结果
result.then(function (nextNum) {
// nextNum就是IAsyncOperation<int>的真正返回值
output.textContent = nextNum;
}); }
} function bindEvent() {
if (window.OURBRIDGEOBJ) {
//注意事件名称!!!
window.OURBRIDGEOBJ.addEventListener("somethingchanged", function (value) {
output.textContent = "我是个事件回调: value="+value;
});
}
}
</script>
</head>
<body>
<div style="margin-top:100px">
<button id="fun1Btn" onclick="func1();">Call method 1</button>
<button id="fun2Btn" onclick="func2();">Call method 2</button>
<button id="bindBtn" onclick="bindEvent();">Bind event</button>
<div id="output" />
</div> </body>
</html>
最后在窗口上添加一个触发按钮。
public sealed partial class MainPage : Page
{
BridgeObject.Bridge _bridge = new BridgeObject.Bridge(); public MainPage()
{
this.InitializeComponent(); this.wv.NavigationStarting += Wv_NavigationStarting; this.Loaded += MainPage_Loaded;
} private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
//我们事先写好了一个本地html页面用来做测试
this.wv.Navigate(new Uri("ms-appx-web:///assets/html/index.html", UriKind.RelativeOrAbsolute));
} private void Wv_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
//OURBRIDGEOBJ这个是我们的对象插入到页面之后对象的变量名,这是一个全局变量,也就是window.OURBRIDGEOBJ
this.wv.AddWebAllowedObject("OURBRIDGEOBJ", _bridge);
} private void Button_Click(object sender, RoutedEventArgs e)
{
// 触发自定义事件
_bridge.FireEvent();
}
}
结果如下。
最后如果你觉得写component限制太多的话(继承都不让用。。),可以使用接口定义方法,然后在类库中实现这些方法也是一个不错的方案,下面是一个比较简单的实现供参考。
我们的jsbridge接口,包含我们准备提供的方法。
/// <summary>
/// 用来定义JSBridge中实现的方法
/// </summary>
public interface IBridgeMethods
{
IAsyncOperation<int> GiveMmeAnObject(int num);
void ShowMessage(string message);
}
修改我们的Bridge类,所有的方法都通过上面的接口来提供。
//这个attribute是必须的,有了他我们的对象才能传递给WebView
[AllowForWeb]
public sealed class Bridge
{
private IBridgeMethods _methods = null; /// <summary>
/// 提示一条消息
/// </summary>
/// <param name="msg"></param>
public void ShowMessage(string msg)
{
_methods?.ShowMessage(msg);
} public IAsyncOperation<int> giveMeAnObject(int num)
{
return _methods?.GiveMmeAnObject(num);
} /// <summary>
/// 初始化个方法的实现
/// </summary>
/// <param name="obj"></param>
public void Init(IBridgeMethods obj)
{
_methods = obj;
}
}
为自己搭建一个鹊桥 -- Native Page与Web View之间的JSBridge实现方式的更多相关文章
- Nginx系列2:用Nginx搭建一个可用的静态资源Web服务器
上一节中编译好自己的nginx服务器后, 现在要对nginx.conf文件进行配置,搭建一个可用的静态资源Web服务器 1.放入可访问的html文件到nginx文件夹下,如图1所示: 这里我放入的是一 ...
- Node.js基于Express框架搭建一个简单的注册登录Web功能
这个小应用使用到了node.js bootstrap express 以及数据库的操作 :使用mongoose对象模型来操作 mongodb 如果没了解过的可以先去基本了解一下相关概念~ 首先注 ...
- 如何快速搭建一个基于ServiceStack框架的web服务
ServiceStack是一个高性能的.NET Web Service 平台,能够简化开发高性能的REST (支持JSON,XML,JSV,HTML,MsgPack,ProtoBuf,CSV等消息格式 ...
- IDEA搭建一个SpringBoot项目——十分详细(web+mysql)
前排提示: IDEA版本:IntelliJ IDEA 2021.1.1 专业版(是否为专业版影响不大) 搭建目的:前端web页面能够获取到MySQL数据库中的数据 详细步骤: 1. 创建一个新项目 ...
- 使用IDEA搭建一个 Spring + Spring MVC 的Web项目(零配置文件)
注解是Spring的一个构建的一个重要手段,减少写配置文件,下面解释一下一些要用到的注解: @Configuration 作用于类上面,声明当前类是一个配置类(相当于一个Spring的xml文件)@C ...
- 使用IDEA搭建一个Spring + Spring MVC 的Web项目(零配置文件)
话不多说,直接上代码: 注解是Spring的一个构建的一个重要手段,减少写配置文件,下面解释一下一些要用到的注解: @Configuration 作用于类上面,声明当前类是一个配置类(相当于一个Spr ...
- 使用nginx搭建一个可用的静态资源web服务器
新建dlib目录,dlib里面放着很多index.html文件 修改conf文件 配置location,/所有的请求,这里一般使用alias,这样url后面的路径和dlib/下面的路径是一一对应的,如 ...
- 使用IDEA搭建一个 Spring + Spring MVC + Mybatis 的Web项目 ( 零配置文件 )
前言: 除了mybatis 不是零配置,有些还是有xml的配置文件在里面的. 注解是Spring的一个构建的一个重要手段,减少写配置文件,下面解释一下一些要用到的注解: @Configuration ...
- Native App、Web App 还是Hybrid App?
一.什么是Native App? Native App即原生应用,即我们一般所称的客户端,是针对不同手机系统单独开发的本地应用,如需使用需要先下载到手机并安装,下载Native App的最常见方法是访 ...
随机推荐
- 20145226夏艺华 《Java程序设计》第0周学习总结
关于师生关系: 学生和老师之间我觉得关系时多元化的,不能拘泥于单独的一种关系:灌输与被灌输,教授与被教授--我认为,在不同的课程阶段,师生之间的关系都可以发生变化.前期的老师更像是一个指路的人,而在入 ...
- wifi,网关相关标识的获取
获取WIFI的相关信息 - (void)getWifiInfo { NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfa ...
- HTML5中判断横屏竖屏
在移动端中我们经常碰到横屏竖屏的问题,那么我们应该如何去判断或者针对横屏.竖屏来写不同的代码呢. 这里有两种方法: 一:CSS判断横屏竖屏 写在同一个CSS中 1 2 3 4 5 6 @media s ...
- .net 常用的命名空间和类
一.基础命名空间 l System.Collections 包含了一些与集合相关的类型,比如列表,队列,位数组,哈希表和字典等. l System.IO 包含了一些数据流类型并提供了文件和目录同步 ...
- sikuli实战记录
最近,为了解放运营人力,需要对某国企的系统进行自动化操作.该系统使用的是https,需要加载证书,而且非得用IE才行,firefox无法正常的加载证书.而selenium启动的IE是纯净的,不会加载任 ...
- liunx 字符编码问题
查询当前服务器的编码:echo $LANG 设置服务器的编码:LANG=en_US.UTF-8
- 如何搭建Java开发环境(包括下载、安装和配置JDK)和Eclipse的安装
JDK的下载 1.打开网址https://www.oracle.com/index.html 2.下载JDK JDK的安装 设置环境变量(以windows 7 为例) 1. 在“计算机”图标上单击鼠标 ...
- 关于Scrum团队的理解
<阅读完<构建之法>第6~7章>之读后感 阅读完<构建之法>第6~7章之后,不仅感觉获益匪浅,也甚感团队合作.分配.工作的不易与一个团队运营一个项目并推广的艰辛与 ...
- [fortify] open redirect漏洞
简介: 些通过请求(如查询字符串和表单数据)指定重定向URL的Web程序可能会被篡改,而把用户重定向到外部的恶意URL.这种篡改就被称为开发重定向攻击. 场景分析 假设有一个正规网站http://ne ...
- 安卓稳定性压测工具_monkey环境搭建(简易)
1.准备工具: sdk(64位操作系统):http://pan.baidu.com/s/1kV33pll sdk(32位操作系统):http://pan.baidu.com/s/1gfnww87 2. ...