maq
maq
  • 发布:2015-11-28 21:32
  • 更新:2015-11-28 21:32
  • 阅读:10147

【分享】跨 webview 的 js 函数调用

分类:HBuilder

webview 是 5+ SDK 的重要组成部分,也是 HBuilder 区别于其它“H5 混合模式”移动端开发方案的一个重要特色和利器。

在实际使用中,webview 也会给程序设计带来一些麻烦。因为一个 webview 实例就相当于桌面浏览器里的一个窗口页签,每个 webview 的 js 上下文都是相互独立的,并不能互相访问。

幸好 HTML5+ 规范中定义了 WebviewObject,evalJS() 这个接口,为两个平行宇宙之间的互相通信提供了可能。MUI 就利用这个接口实现了“自定义事件”功能,可以用来跨 webview 传递消息。

不过,evalJS() 只是底层实现的一个单向通信机制,MUI 的自定义事件也是单向消息,没有返回值。虽然这已经解决了很多问题,但还是有不少应用场景更适合用接近“函数调用”的方式来实现。

有鉴于此,我做了这个 hbuilder-rpc。分享在这里,希望能有用。如果这段代码将来有机会成为 MUI 的一部分,我将尤感欣慰。

演示界面截图:

相关程序代码:

/**  
 * 以当前的 WebView 为媒介,向其它 WebView 中的 js 提供服务接口。  
 */  
window.RpcServer = {  
	/**  
	 * 服务提供者调用此函数注册一个服务接口。  
	 * @param {String} service_name 接口名称  
	 * @param {Function} fnService 注册的服务函数,具有如下形式:  
	 * 		function(params, finish) {  
	 * 			// params 是调用参数。  
	 * 			// finish 是回调函数,应该在服务完成后调用,并传入唯一参数表示服务执行结果。  
	 * 			// 		即使服务出错,也要确保回调函数被调用,并用传入参数来表示错误状态。  
	 * 		}  
	 */  
	expose: function(service_name, fnService) {  
		var me = this;  
		if (me.exposed[service_name] != undefined) {  
			throw new Error('RpcServer.expose: service already exists: ' + service_name);  
		}  
		me.exposed[service_name] = fnService;  
	},  
  
	exposed: {}, // 注册的服务接口  
  
	/**  
	 * RpcClient 通过 evalJS() 调用此函数,访问服务接口。  
	 * @param {String} service_name 服务接口名称  
	 * @param {Mixed} params 入口参数  
	 * @param {String} vw_id 调用源的 webview id  
	 * @param {String} cb_id 调用源的 callback id  
	 */  
	invoke: function(service_name, params, vw_id, cb_id) {  
		var fn = this.exposed[service_name];  
		if (typeof fn != 'function') {  
			throw new Error('RpcServer.invoke: service not found: ' + service_name);  
		}  
		fn(params, function(ret) {  
			var vw = plus.webview.getWebviewById(vw_id);  
			if (!vw) return;  
			var js = 'RpcClient.callback(' + JSON.stringify(cb_id);  
			js += ',' + JSON.stringify(ret);  
			js += ')';  
			vw.evalJS(js);  
		});  
	}  
};
/**  
 * 远程访问 RpcServer 提供的服务接口。  
 */  
window.RpcClient = {  
	/**  
	 * 调用一个远程服务接口。  
	 * @param {String} server_id rpc server 的 webview id。  
	 * @param {String} service_name 服务接口名称。  
	 * @param {Mixed} params 服务入口参数。  
	 * @param {Function} callback 回调函数,用于回传服务执行结果。  
	 */  
	invoke: function(server_id, service_name, params, callback) {  
		var me = this;  
		var cs = plus.webview.getWebviewById(server_id);  
		if (!cs) throw new Error('RpcServer view not found: ' + server_id);  
		var js = 'RpcServer.invoke(' + JSON.stringify(service_name);  
		js += ',' + JSON.stringify(params);  
		if (typeof callback == 'function') {  
			js += ',' + JSON.stringify(plus.webview.currentWebview().id);  
			js += ',' + me.next_callback_id;  
			me.callbacks[me.next_callback_id] = callback;  
			me.next_callback_id ++;  
		}  
		js += ')';  
		cs.evalJS(js);  
	},  
  
	next_callback_id: 1,  
	callbacks: {},  
	callback: function(cb_id, ret) {  
		var me = this;  
		var cb = me.callbacks[cb_id];  
		if (typeof cb != 'function') return;  
		cb.call(undefined, ret);  
		delete me.callbacks[cb_id];  
	}  
};
// 通过 RpcServer.expose() 暴露一个服务函数供其它 WebView 中的 js 调用  
RpcServer.expose('demo-rpc-service', function(params, finish) {  
	// 入口参数  
	mui('#rpc-call-params')[0].innerText = JSON.stringify(params, undefined, '    ');  
  
	// 服务功能完成后,调用 finish() 把结果发回给调用者  
	finish({  
		success: true,  
		result: {  
			reply: 'hi, ' + params.from + '.',  
			num: window._call_num = (window._call_num || 0) + 1  
		}  
	});  
});
// 通过 RpcClient.invoke() 调用另一个 WebView 中的服务函数  
RpcClient.invoke('demo-rpc-server', 'demo-rpc-service', {  
	greeting: 'hi !',  
	from: plus.webview.currentWebview().id,  
	num: window._call_num = (window._call_num || 0) + 1  
}, function(resp) {  
	// resp 是服务执行结果  
	mui('#rpc-call-resp')[0].innerText = JSON.stringify(resp, undefined, '    ');  
});

演示项目源代码可通过附件下载。

7 关注 分享
蔡繁荣 BoredApe Float bzliukai ming300 ruogu 咬了一口的苹果

要回复文章请先登录注册

咬了一口的苹果

咬了一口的苹果

mark
2017-10-18 10:51
1***@qq.com

1***@qq.com

mark
2017-03-20 00:18
闪闪

闪闪

回复 maq :
哦,你的这个“通信中心”想法挺好的。
2015-11-30 14:42
maq

maq (作者)

多谢 @闪闪 的回复。

1.webview是所有Hybrid模式都需要的,不是5+区别于其他的特色。

这一点我的确了解得不多,结论有些草率。不过,就我所知道的 Cordova(PhoneGap),里面就没有找到跟 webview 对等的东西,有一个 InAppBrowser 比较接近,但其典型用法还是有很大区别的。

2.一个webview可能既是“server”也是“client",webview间的方法调用是互相的,建议封装成一个就好了。

这段代码我是从实际项目里面抽出来的,在这个项目里,需要以 websocket 方式来跟服务器进行实时通信,为了避免建立过多的 websocket 连接,我专门创建了一个 webview 作为“通信中心”,并通过 RpcServer 把服务和数据发布给其它 webview,而其它的 webview 则通过 RpcClient 来访问。所以,分开来使用还是有一定灵活性的。
2015-11-30 11:25
闪闪

闪闪

1.webview是所有Hybrid模式都需要的,不是5+区别于其他的特色。
2.一个webview可能既是“server”也是“client",webview间的方法调用是互相的,建议封装成一个就好了。
2015-11-30 10:51
maq

maq (作者)

发完这个帖子才看到,原来已经有人写过类似的内容了:

分享一个js,如何更方便地进行跨webview的调用
http://ask.dcloud.net.cn/question/7493

看来这方面的需求还是挺旺盛的啊,呵呵
2015-11-28 21:54