最近正在做一个移动跨平台项目的应用开发,包括在iphone,ipad,android,windows phone等手机设备中运行混合式客户端应用程序,这里选择了PhoneGap的移动跨平台框架,这里我先简单介绍下PhoneGap到底是什么东东:
介绍
PhoneGap是一款HTML5平台,通过它,开发商可以使用HTML、CSS及JavaScript来开发本地移动应用程序。因此,目前开发商可以只 编写一次应用程序,然后在6个主要的移动平台和应用程序商店(app store)里进行发布,这些移动平台和应用程序商店包括:iOS、Android、BlackBerry、webOS、bada以及Symbian。
官方地址
英文官方:http://phonegap.com/
在上面你可以找到它的入门使用说明,这里我就不具体描述了。
关于PhoneGap开发的项目实践经验,我会在后面再另开文章来说明。
本篇文章的重点在于对于老赵的Wind.js的使用体验,老赵是谁,相信也不用多说了,具体可以见他的博客:http://blog.zhaojie.me/
这里就先介绍他的Wind.js:
官方链接:http://windjs.org/
开源链接:https://github.com/JeffreyZhao/wind
对于它的定义,我就直接引用官方的一段话:
Wind.js的前身为Jscex,即JavaScript Computation EXpressions的缩写,它为JavaScript语言提供了一个monadic扩展,能够显著提高一些常见场景下的编程体验(例如异步编程)。Wind.js完全使用JavaScript编写,能够在任意支持JavaScript的执行引擎里使用,包括各浏览器及服务器端JavaScript环境(例如Node.js)。
实际上,它就是原先的Jscex。
对于我来说,正在开始了解Wind.js的时候,是在今年的7月份,在阿里技术嘉年华(http://adc.taobao.com/)中听node.js专场时了解到的,当时现场火爆,大家对于Wind.js也是非常感兴趣。当然过程中也有一些人保持一种观望的态度。而对于我这种实战派的开发者来说,在一个有应用场景的情况下,Wind.js才更有说服力。
于是,在我当时的理解当中,它应该是属于可以改变异步体验的一种绝佳的方式,可以让你的代码可读性大大提升,而你从此不再为了setTimeout,callback之类的写法而烦恼!
好的,一些理论的东西我就先不多说了,Wind.js文档中已经写的很多了,今天就来用Wind.js来体验下如何应用到我的项目中去。
请先看下面的一段代码:
if (callback == null) {
return;
}
callIfNetworkAvailable(function() {
callNativeAPI(
native_refreshUrl,
{ key: 'Login', username: userName, password: password, type: type },
function (result) {
callback(result);
}
);
});
}
function callIfNetworkAvailable(fn) {
if (fn == null) {
return;
}
getNetworkStatus(function (result) {
if (result.status) {
if (result.data) {
fn();
}
else {
hideLoadingMsg();
alert(lang.networkUnAvailable);
}
}
else {
hideLoadingMsg();
alert(lang.getNetworkAvailableStatusFailed);
}
});
}
function getNetworkStatus(callback) {
if (callback == null) {
return;
}
callNativeAPI(
native_getUrl,
{ key: 'GetNetworkStatus' },
function (result) {
callback(result);
}
);
}
function callNativeAPI(url, data, callback) {
var items = url.split("/");
var serviceName = items[0];
var actionName = items[1].toLowerCase();
//因为参数必须是数组,所以把参数放在一个数组中
var params = [];
params.push(data);
log({ step: '调用Native接口前的参数信息', parameters: data });
//调用Native接口
Cordova.exec(
function (result) {
log({ step: '调用Native接口的返回值信息', returnValue: result });
if (callback != null) {
callback(result);
}
},
function () { },
serviceName,
actionName,
params
);
}
这里首先我先需要说的是,PhoneGap的javascript脚本与原生(iOS,android,wp等)的API的plugin交互,采用与浏览器webkit中的webview进行通信,而它的底层原理就是iframe的交互,它是以一种特定规范的通信协议来展开,而在传统的web上iframe的使用本身就是最原始的异步加载原理的使用。所以,没有办法异步方式在phonegap的开发中广泛使用。
再回过头看上面的代码,它实际上实现的是一个登录的功能,在登录的过程中,首先我必须先调用callIfNetworkAvailable方法,而它的参数本身作为一个回调函数,getNetworkStatus会调用callNativeAPI,callNativeAPI函数里面的Cordova.exec方法实际上就是一个跟原生交互的一个方法入口,通过传递serviceName,actionName以及对应需要传递的数据参数来决定,而它总是需要一个回调方法(successCallback,failCallback)来接收返回的数据。
最后的执行:
var userName = $("#username").val();
var password = $("#password").val();
if (isNullOrEmpty(userName)) {
alert(lang.usernameCannotEmpty);
return;
}
if (isNullOrEmpty(password)) {
alert(lang.passwordCannotEmpty);
return;
}
showLoading("登录中...");
login(userName, password, "normal", function (result) {
if (!result.status) {
alert(result.message);
}
else {
showPage("taskListPage");
hideLoading();
}
});
});
我们得到的就是需要不断地写嵌套函数,不断地callback,这样的写法看起来还是很纠结的!
于是,萌生了采用Wind.js的异步调用的独特方式:
先来看看我是如何改造这段代码的:
var loginAsync = eval(Wind.compile('async', function (userName, password, type) {
var result = $await(callIfNetworkAvailableAsync());
if(result) {
var result = $await(callNativeAPIAsync(native_refreshUrl,
{ key: 'Login', username: userName, password: password, type: type }));
return result;
}
return null;
}));
//在当前网络可用的情况下调用指定函数
var callIfNetworkAvailableAsync = eval(Wind.compile('async', function() {
var result = $await(getNetworkStatusAsync());
if (result.status) {
if (result.data) {
return true;
}
else {
hideLoadingMsg();
alert(lang.networkUnAvailable);
}
}
else {
hideLoadingMsg();
alert(lang.getNetworkAvailableStatusFailed);
}
return false;
}));
//获取当前网络状态
var getNetworkStatusAsync = eval(Wind.compile('async', function() {
var result = $await(callNativeAPIAsync(native_getUrl, { key: 'GetNetworkStatus' }));
return result;
}));
//JS端与PhoneGap Native API进行交互
var callNativeAPIAsync = eval(Wind.compile('async', function(url, data) {
var items = url.split("/");
var serviceName = items[0];
var actionName = items[1].toLowerCase();
//因为参数必须是数组,所以把参数放在一个数组中
var params = [];
params.push(data);
$await(logAsync({ step: '调用Native接口前的参数信息', parameters: data }));
//调用Native接口
var result = $await($.cordovaAsync(serviceName, actionName, params));
if(result) {
$await(logAsync({ step: '调用Native接口的返回值信息', returnValue: result }));
}
return result;
}))
从代码中,你不难发现,它采用一种顺序执行代替异步的方式很巧妙地绕开了一连串callback的写法,在感官上似乎更符合了一个开发者顺序执行模型的思想。
而return result;就是一个回调的结果。
到这里你可能还有个疑问,Cordova.exec是如何做到的,这里我定义了一个叫做$.cordovaAsync的函数:
$.cordovaAsync = function (serviceName, actionName, params) {
return Task.create(function (t) {
var success = function (result) {
t.complete("success", result);
};
var fail = function(result) {
t.complete("failure", result);
}
Cordova.exec(success, fail, serviceName, actionName, params);
});
}
这里是Wind.js提供的一种任务式的插件绑定,例如它可以把一个jQuery中的$.ajax改装成一个$.ajaxAsync的Wind.js调用方式,这里我把它改装成$.cordovaAsync来调用Cordova.exec,而最终var result = $await($.cordovaAsync(serviceName, actionName, params));的返回值实际上就是一个回调函数的success,这样我就实现了JS与原生客户端的交互。
最后的执行:
var userName = $("#username").val();
var password = $("#password").val();
if (isNullOrEmpty(userName)) {
alert(lang.usernameCannotEmpty);
return;
}
if (isNullOrEmpty(password)) {
alert(lang.passwordCannotEmpty);
return;
}
showLoading("登录中...");
startLoginAsync(userName, password, 'normal').start();
});
var startLoginAsync = eval(Wind.compile('async', function (userName, password, type) {
var result = $await(loginAsync(userName, password, type));
if(result) {
if (!result.status) {
alert(result.message);
}
else {
hideLoading();
showPage("taskListPage");
}
}
}));
理解上就相当简单了。
实例图:
总结
会持续关注Wind.js的发展,并且接下来也会了解下它的内部原理。另外,提出一个建议,eval(Wind.compile('async',…这样的写法还能够更加简易些吗,比如我觉得使用$.await(function(…){ … });就是挺好的方式。
开源地址:https://github.com/sunleepy/cooper-mobi