PhoneGap研究之二

传送门:

PhoneGap源码详解一

PhoneGap源码详解二

PhoneGap源码详解三

讨论q群:248908795

一、 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 文件都是跨平台通用的。 ( 即使有差异应该也不大 )


简单介绍一下各个目录和一些关键组件 :

 

Java代码   收藏代码
  1. cordova.js:拦截DOM,Window事件,加入自定义cordova事件,管理回调Javascript。  
  2.   
  3. scripts/require.js:PhoneGap中模块化机制的基础框架。简单但是也不简单!  
  4.   
  5. scripts/bootstrap.js:负责cordova的启动。  
  6.   
  7. common/channel.js:PhoneGap中实现事件监听的基础。  
  8.   
  9. common/builder.js:具备定制化构造模块的能力。  
  10.   
  11. common/plugin:如名字所示。这里放置所有平台通用的插件接口。  
  12.   
  13. lib/exec.js:于上一篇解析中提到。是Javascript调用Native的入口。  
  14.   
  15. lib/platform.js:与平台实现有关的初始化。  
  16.   
  17. lib/android/plugin:与android平台紧密相关的插件。  

 

 

二、 浅析 PhoneGap 中的模块化机制

也许是因为本人见过的 Javascript 代码太少,见到 PhoneGap 的模块化机制后便觉得非常的有趣和前卫。Pascal 的作者沃斯曾写过一门叫做 module 的语言,其在语言级别做了模块化机制。我不知道 PhoneGap 模块化的思路是否也受 ; 此影响。

废话不多说了,从 require.js 开始看起吧。它是模块化的基础。

 

Js代码   收藏代码
  1. var require,  
  2.   
  3.     define;  
  4.   
  5.    
  6.   
  7. (function () {  
  8.   
  9.     var modules = {};  
  10.   
  11.    
  12.   
  13.     function build(module) {  
  14.   
  15.         var factory = module.factory;  
  16.   
  17.         module.exports = {};  
  18.   
  19.         delete module.factory;  
  20.   
  21.         factory(require, module.exports, module);  
  22.   
  23.         return module.exports;  
  24.   
  25.     }  
  26.   
  27.    
  28.   
  29.     require = function (id) {  
  30.   
  31.         if (!modules[id]) {  
  32.   
  33.             throw "module " + id + " not found";  
  34.   
  35.         }  
  36.   
  37.         return modules[id].factory ? build(modules[id]) : modules[id].exports;  
  38.   
  39.     };  
  40.   
  41.    
  42.   
  43.     define = function (id, factory) {  
  44.   
  45.         if (modules[id]) {  
  46.   
  47.             throw "module " + id + " already defined";  
  48.   
  49.         }  
  50.   
  51.    
  52.   
  53.         modules[id] = {  
  54.   
  55.             id: id,  
  56.   
  57.             factory: factory  
  58.   
  59.         };  
  60.   
  61.     };  
  62.   
  63.    
  64.   
  65.     define.remove = function (id) {  
  66.   
  67.         delete modules[id];  
  68.   
  69.     };  
  70.   
  71.    
  72.   
  73. })();  
  74.   
  75.    
  76.   
  77. //Export for use in node  
  78.   
  79. if (typeof module === "object" && typeof require === "function") {  
  80.   
  81.     module.exports.require = require;  
  82.   
  83.     module.exports.define = define;  
  84.   
  85. }  

 

 

代码行数的确非常短。其定义了 require 和 define 两个函数。首先 define 函数用于声明一个模块。其中 id 表示模块名称,这必须是唯一的,而 factory 便是构造模块的工厂方法。 require 函数使用懒加载的方式获得已define 过的对应 id 的模块。

来看一个使用其的简单示例吧 :

 

Java代码   收藏代码
  1. define("cordova/plugin/android/app", function(require, exports, module) {  
  2.   
  3. var exec = require('cordova/exec');  
  4.   
  5.    
  6.   
  7. module.exports = {  
  8.   
  9.   /** 
  10.  
  11.    * Clear the resource cache. 
  12.  
  13.    */  
  14.   
  15.   clearCache:function() {  
  16.   
  17.     exec(nullnull"App""clearCache", []);  
  18.   
  19.   },  
  20.   
  21.    
  22.   
  23.   /** 
  24.  
  25.    * Load the url into the webview or into new browser instance. 
  26.  
  27.    * 
  28.  
  29.    * @param url           The URL to load 
  30.  
  31.    * @param props         Properties that can be passed in to the activity: 
  32.  
  33.    *      wait: int                           => wait msec before loading URL 
  34.  
  35.    *      loadingDialog: "Title,Message"      => display a native loading dialog 
  36.  
  37.    *      loadUrlTimeoutValue: int            => time in msec to wait before triggering a timeout error 
  38.  
  39.    *      clearHistory: boolean              => clear webview history (default=false) 
  40.  
  41.    *      openExternal: boolean              => open in a new browser (default=false) 
  42.  
  43.    * 
  44.  
  45.    * Example: 
  46.  
  47.    *      navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); 
  48.  
  49.    */  
  50.   
  51.   loadUrl:function(url, props) {  
  52.   
  53.     exec(nullnull"App""loadUrl", [url, props]);  
  54.   
  55.   },  
  56.   
  57.    
  58.   
  59.   /** 
  60.  
  61.    * Cancel loadUrl that is waiting to be loaded. 
  62.  
  63.    */  
  64.   
  65.   cancelLoadUrl:function() {  
  66.   
  67.     exec(nullnull"App""cancelLoadUrl", []);  
  68.   
  69.   },  
  70.   
  71.    
  72.   
  73.   /** 
  74.  
  75.    * Clear web history in this web view. 
  76.  
  77.    * Instead of BACK button loading the previous web page, it will exit the app. 
  78.  
  79.    */  
  80.   
  81.   clearHistory:function() {  
  82.   
  83.     exec(nullnull"App""clearHistory", []);  
  84.   
  85.   },  
  86.   
  87.    
  88.   
  89.   /** 
  90.  
  91.    * Go to previous page displayed. 
  92.  
  93.    * This is the same as pressing the backbutton on Android device. 
  94.  
  95.    */  
  96.   
  97.   backHistory:function() {  
  98.   
  99.     exec(nullnull"App""backHistory", []);  
  100.   
  101.   },  
  102.   
  103.    
  104.   
  105.   /** 
  106.  
  107.    * Override the default behavior of the Android back button. 
  108.  
  109.    * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. 
  110.  
  111.    * 
  112.  
  113.    * Note: The user should not have to call this method.  Instead, when the user 
  114.  
  115.    *       registers for the "backbutton" event, this is automatically done. 
  116.  
  117.    * 
  118.  
  119.    * @param override        T=override, F=cancel override 
  120.  
  121.    */  
  122.   
  123.   overrideBackbutton:function(override) {  
  124.   
  125.     exec(nullnull"App""overrideBackbutton", [override]);  
  126.   
  127.   },  
  128.   
  129.    
  130.   
  131.   /** 
  132.  
  133.    * Exit and terminate the application. 
  134.  
  135.    */  
  136.   
  137.   exitApp:function() {  
  138.   
  139.     return exec(nullnull"App""exitApp", []);  
  140.   
  141.   }  
  142.   
  143. };  
  144.   
  145. });  

 

 

这是 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 中我们看到下面一段代码。

 

Java代码   收藏代码
  1. channel.join(function() {  
  2.   
  3. channel.onDeviceReady.fire();  
  4.   
  5. }, channel.deviceReadyChannelsArray);  

 

         waitForInitialization 用于添加 onDeviceReady 的等待 Channel 事件。 initializeComplete 用于触发指定的等待 Channel 事件。如果想要增加 onDeviceReady 的条件,我们只需要在 onCordovaReady 之前添加waitForInitialization 即可。事实上,在 lib/android/plugin/storage.js 中我们便可以看到一个绝佳的例子。cupcakeStorage 利用本地 Plugin 为不支持 localStorage API 的 WebView 提供了一个备选方案。在本地建立好备用的 sqlite 数据库后 ,cupcakeStorage 的等待时间便结束完毕。

四、 启动与 PhoneGap 自定义事件

首先上图。


上图为本人整理的启动事件序列,待会儿大家便能从源码中看到了。

待续。。。。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值