https://github.com/zytc2009/BigTeam_learning
分类: Java
2012-07-24 18:02:18
一、Javascript的源码结构
提醒一下大家,PhoneGap的作者已经将PhoneGap的源码委托给了Apache基金会。PhoneGap的开源版本称为cordova。
PhoneGap之于Cordova,正如OpenJDK之于JDK。两者基本上是差不多的。
cordova.android.js是一个build版本。本人对其做了反build工作,cordova.android.js展开的源码目录结构便如上所示。
从命名上其实已经可以看出,lib/android属于android平台的专用库。其余平台基本上是lib/windowsphone或者lib/ios。而除lib下的exec.js和platform.js这两个是固定的。但是不同平台版本其实现是不一样的。除lib包外,其余所有的js文件都是跨平台通用的。(即使有差异应该也不大)
简单介绍一下各个目录和一些关键组件:
二、浅析PhoneGap中的模块化机制
也许是因为本人见过的Javascript代码太少,见到PhoneGap的模块化机制后便觉得非常的有趣和前卫。Pascal的作者沃斯曾写过一门叫做module的语言,其在语言级别做了模块化机制。我不知道PhoneGap模块化的思路是否也受;此影响。
废话不多说了,从require.js开始看起吧。它是模块化的基础。
代码行数的确非常短。其定义了require和define两个函数。首先define函数用于声明一个模块。其中id表示模块名称,这必须是唯一的,而factory便是构造模块的工厂方法。require函数使用懒加载的方式获得已define过的对应id的模块。
来看一个使用其的简单示例吧:
这是phonegap模块化编程的典型写法。首先在头部定义依赖的模块组件,再次通过设置module.exports 向外部暴露出对应的方法。
由于Javascript中在语法级别没有私有访问符。因此往往解决之道是:a.模仿C风格,命名使用_开头的一律表示是私有;b.新建一个private对象,将其私有方法放置其中,起到命名空间的作用;c.将私有部分用function{}套住,用return返回公开部分。这种做法充分发挥了javascript闭包的优势,但是可读性比较差。
phoneGap的module机制使用了第三种做法。但是通过将构造和依赖分离开来,使得可读性大大增加。代码清晰好懂,依赖性也一目了然。
也许有朋友要问。这种模块机制碰到循环依赖的情况怎么办?例如A在define阶段requireB,B在define阶段又requireA。很遗憾,这种循环依赖的情况 依照phoneGap的模块化思路是无法实现的。因此对于关键组件的编制,phoneGap总会小心翼翼地处理其依赖顺序。
通过阅读源码,发现大致的模块依赖顺序是这样子的(被依赖模块到依赖模块):utils.js->channel.js|builder.js->cordova.js->exec.js|polling.js->callback.js->platform.js->bootstrap.js。
三、PhoneGap中的事件处理机制
common/channel.js是PhoneGap事件处理机制的基础。每个事件类型,都会包装成一个Channel对象。
既然是事件,那么就得支持基础的观察者模式吧?Channel的prototype定义了事件的一些关键方法。subscribe用于注册一个监听器,并给监听器一个guid。guid类似cordova.js中的callbackId,只是一个流水标识。但Channel中的guid稍有些不同,指定确切的guid可以对监听器做覆盖操作。
utils.close是个很有趣的方法。Javascript中的调用不当引起this不对,这是新手常见的错误。常见的做法会通过封装apply做delegate。而close这个方法是绝了,它通过闭包包装了一个指向确定this,调用确定function,使用确定实参的final函数。不管在什么样的环境下调用,这个方法总能正确执行。
subscribeOnce类似于YUI或者jquery中的one。只会收到一次监听。(若事件已经触发过,则在注册阶段立即回调监听)
unsubscribe和fire分别用来注销监听器和触发事件。触发事件将会引起监听器的广播操作。可选的fireArgs用于保证subscribeOnce在事件已触发的情况下能获得正确的广播参数。
Channel本身还有一个监听注册/注销的事件拦截。分别是onSubscribe和onUnSubscribe。在common\plugin\battery.js中,我们可以看到。battery.js便是利用这个注册监听回调,来对Plugin服务做懒加载和卸载工作。
作为模块暴露公有部分的channel对象比较有意思。join这个工具方法类似subscribeOnce,它的第二个参数是个Channel数组。当且仅当所有的Channel事件都被fire后,join的监听才会被回调。这个方法还是挺有用的
create是个构造工厂方法。新构造的Channel事件会被放置在channel对象中。使用上会方便点。在channel.create('onCordovaReady');后,便可以便捷的通过channel[‘onCordovaReady’]来方便的访问对应类型的Channel对象了。
deviceReadyMap,deviceReadyArray,waitForInitialization,initializeComplete这四者紧密相关。它们决定了onDeviceReady事件在何时被触发。于common/bootstrap.js中我们看到下面一段代码。
waitForInitialization用于添加onDeviceReady的等待Channel事件。initializeComplete用于触发指定的等待Channel事件。如果想要增加onDeviceReady的条件,我们只需要在onCordovaReady之前添加waitForInitialization即可。事实上,在lib/android/plugin/storage.js中我们便可以看到一个绝佳的例子。cupcakeStorage利用本地Plugin为不支持localStorage API的WebView提供了一个备选方案。在本地建立好备用的sqlite数据库后,cupcakeStorage的等待时间便结束完毕。
四、启动与PhoneGap自定义事件
首先上图。
上图为本人整理的启动事件序列,待会儿大家便能从源码中看到了。
一、PhoneGap的启动
近期因为赶项目和犯懒,所以一直没有更新,希望朋友们见谅。
前面介绍过phoneGap的模块化机制。其通过require和define,巧妙的将模块的定义和依赖关系隔离开来。
如上图所示,在定义模块机制后。后面统统都是做define。那么,这些被定义的模块最终何时被实例化呢?
在cordova.js的末尾我们找到了这一句。第一个被请求实例化的模块是cordova(adobe版本是phonegap),接着在实例化cordova模块后,cordova依赖的模块也纷纷被实例化出来了。(channel、utils)
cordova模块仅是个最基础的顶层通信模块。除了cordova模块外,其余数十个phonegap模块都还在沉睡之中。事实上,这一步走完其实phonegap已经完成了。Phonegap用户只需要require(‘cordova/plugin/contacts’)这样,即可访问到phonegap的功能。
但是,这样做还不够好。第一,require对于phonegap使用者来说并不友好。第二,虽然可以通过require得到构建模块的实例,可这并不代表Native/Javascript的通信关系已经建立。phonegap用户还不知道何时可以安全正确的使用这些功能。
基于上面两点,phonegap构建了一个bootstrap函数。在上图的后一句,大家可以看到bootstrap的封闭调用。下面我来详解一下bootstrap
二、Bootstrap详解
首先贴上代码。
我们先来详细关注下,phonegap是如何提供回调通知,来告诉用户Native与Javascript之间成功建立联系的呢?
首先,bootstrap通过channel模块注册监听了onNativeReady事件。这个事件由Native层触发,用来表示Native层准备完毕(可以接受plugin调用)。在Android平台上面,onNativeReady是在WebView的onPageFinished回调中触发的。
为了安全的启动,boot等待onNativeReady和onDOMContentLoaded完毕后才执行。那么Phonegap的boot都做了些什么呢?
boot做了两件事情。首先是实例化和发布模块来给phonegap用户使用。其次是广播onCordovaReady来通知phonegap层boot完毕,用户可以放心使用phonegap的功能。
通过builder,模块的实例化和发布工作变得很艺术。builder模块提供三种发布方式,分别是intoButDontClobber(若发布目标中已存在同名模块,则不允许做覆盖),intoAndClobber(若发布目标中已存在同名模块,则强制覆盖),intoAndMerge(若发布目标中已存在实例模块,则尝试对两者进行合并,合并的优先级是欲发布模块比发布模块高)
解释一下“发布”这个词。其实就是根据定义id实例化(require)模块,然后把它作为某个object的属性。从boot的源码中,我们可以看到。在common.js和platform.js中,分别定义了objects对象。objects定义了各个模块的id和层次关系。父子关系通过children字段表明。
根据上面的定义,cordova模块下将挂一个exec模块。即通过builder后,用户可以直接通过cordova.exec来访问exec模块。
当然,发布的target都是window。common下的发布定义(objects),所使用的发布策略是不覆写原有属性。之所以这样做,是考虑到之后浏览器加强html5支持后,有些模块将会原生提供。
platform下的发布定义(objects)所使用的发布策略是覆写所有属性。其定义的是与平台密切相关的模块,因此会做强制覆写。
注意platform下的发布定义,除了objects外。还提供了merge定义。这里的merge作用在于“增强”。为原有的通用模块,增加一些平台相关的其它方法。在android/platform.js的merges定义便是一个例子。最终发布的notification将既包含common下notification.js的alert方法,也包含platform下notification.js的activityStart方法。
发布模块后会执行platform.initialize方法。这个方法用于做platform初始化工作,将与平台特性紧耦合,尤其会与Native与Javascript互相通信相关。在android平台版本中,initialize包含了polling/xhr的初始化,android的按键事件、可兼容web数据库api与localstorage。当这些工作完毕后,将会广播onCordovaReady事件。此后便可以安全的使用phonegap所提供的所有功能了。
onCordovaReady后会去准备onDeviceReady。关于onDeviceReady的详细描述,我在上篇解析的channel中提过了,在此也就不重复了。
三、总结
至此phonegap的Native与Javascript层源码解析完毕。有关如何扩展phonegap插件,各位可以参照github上面的这个开源项目:
需要注意的是。其中的addPlugin写法,phonegap已经不再推荐使用了。并表示该方法将在2.0版本中移除。