android phonegap源码详解(二)

传送门:

PhoneGap源码详解一

PhoneGap源码详解二

PhoneGap源码详解三

 

 

一、 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.jsplatform.js 这两个是固定的。但是不同平台版本其实现是不一样的。除 lib 包外,其余所有的 js 文件都是跨平台通用的。 ( 即使有差异应该也不大 )


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

 

cordova.js:拦截DOM,Window事件,加入自定义cordova事件,管理回调Javascript。

scripts/require.js:PhoneGap中模块化机制的基础框架。简单但是也不简单!

scripts/bootstrap.js:负责cordova的启动。

common/channel.js:PhoneGap中实现事件监听的基础。

common/builder.js:具备定制化构造模块的能力。

common/plugin:如名字所示。这里放置所有平台通用的插件接口。

lib/exec.js:于上一篇解析中提到。是Javascript调用Native的入口。

lib/platform.js:与平台实现有关的初始化。

lib/android/plugin:与android平台紧密相关的插件。

 

 

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

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

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

 

var require,

    define;

 

(function () {

    var modules = {};

 

    function build(module) {

        var factory = module.factory;

        module.exports = {};

        delete module.factory;

        factory(require, module.exports, module);

        return module.exports;

    }

 

    require = function (id) {

        if (!modules[id]) {

            throw "module " + id + " not found";

        }

        return modules[id].factory ? build(modules[id]) : modules[id].exports;

    };

 

    define = function (id, factory) {

        if (modules[id]) {

            throw "module " + id + " already defined";

        }

 

        modules[id] = {

            id: id,

            factory: factory

        };

    };

 

    define.remove = function (id) {

        delete modules[id];

    };

 

})();

 

//Export for use in node

if (typeof module === "object" && typeof require === "function") {

    module.exports.require = require;

    module.exports.define = define;

}

 

 

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

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

 

define("cordova/plugin/android/app", function(require, exports, module) {

var exec = require('cordova/exec');

 

module.exports = {

  /**

   * Clear the resource cache.

   */

  clearCache:function() {

    exec(null, null, "App", "clearCache", []);

  },

 

  /**

   * Load the url into the webview or into new browser instance.

   *

   * @param url           The URL to load

   * @param props         Properties that can be passed in to the activity:

   *      wait: int                           => wait msec before loading URL

   *      loadingDialog: "Title,Message"      => display a native loading dialog

   *      loadUrlTimeoutValue: int            => time in msec to wait before triggering a timeout error

   *      clearHistory: boolean              => clear webview history (default=false)

   *      openExternal: boolean              => open in a new browser (default=false)

   *

   * Example:

   *      navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});

   */

  loadUrl:function(url, props) {

    exec(null, null, "App", "loadUrl", [url, props]);

  },

 

  /**

   * Cancel loadUrl that is waiting to be loaded.

   */

  cancelLoadUrl:function() {

    exec(null, null, "App", "cancelLoadUrl", []);

  },

 

  /**

   * Clear web history in this web view.

   * Instead of BACK button loading the previous web page, it will exit the app.

   */

  clearHistory:function() {

    exec(null, null, "App", "clearHistory", []);

  },

 

  /**

   * Go to previous page displayed.

   * This is the same as pressing the backbutton on Android device.

   */

  backHistory:function() {

    exec(null, null, "App", "backHistory", []);

  },

 

  /**

   * Override the default behavior of the Android back button.

   * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.

   *

   * Note: The user should not have to call this method.  Instead, when the user

   *       registers for the "backbutton" event, this is automatically done.

   *

   * @param override        T=override, F=cancel override

   */

  overrideBackbutton:function(override) {

    exec(null, null, "App", "overrideBackbutton", [override]);

  },

 

  /**

   * Exit and terminate the application.

   */

  exitApp:function() {

    return exec(null, null, "App", "exitApp", []);

  }

};

});

 

 

这是 phonegap 模块化编程的典型写法。首先在头部定义依赖的模块组件 , 再次通过设置 module.exports 向外部暴露出对应的方法。

         由于 Javascript 中在语法级别没有私有访问符。因此往往解决之道是: a. 模仿 C 风格,命名使用 _ 开头的一律表示是私有 ;b. 新建一个 private 对象 , 将其私有方法放置其中 , 起到命名空间的作用 ;c. 将私有部分用 function{} 套住 ,return 返回公开部分。这种做法充分发挥了 javascript 闭包的优势,但是可读性比较差。

         phoneGapmodule 机制使用了第三种做法。但是通过将构造和依赖分离开来,使得可读性大大增加。代码清晰好懂,依赖性也一目了然。

         也许有朋友要问。这种模块机制碰到循环依赖的情况怎么办?例如 Adefine 阶段 requireBBdefine 阶段又 requireA 。很遗憾,这种循环依赖的情况 依照 phoneGap 的模块化思路是无法实现的。因此对于关键组件的编制 ,phoneGap 总会小心翼翼地处理其依赖顺序。

         通过阅读源码,发现大致的模块依赖顺序是这样子的 ( 被依赖模块到依赖模块 ):utils.js->channel.js|builder.js->cordova.js->exec.js|polling.js->callback.js->platform.js->bootstrap.js

三、 PhoneGap 中的事件处理机制

common/channel.jsPhoneGap 事件处理机制的基础。每个事件类型,都会包装成一个 Channel 对象。

既然是事件,那么就得支持基础的观察者模式吧? Channelprototype 定义了事件的一些关键方法。 subscribe 用于注册一个监听器,并给监听器一个 guidguid 类似 cordova.js 中的 callbackId ,只是一个流水标识。但 Channel 中的 guid 稍有些不同,指定确切的 guid 可以对监听器做覆盖操作。

utils.close 是个很有趣的方法。 Javascript 中的调用不当引起 this 不对,这是新手常见的错误。常见的做法会通过封装 applydelegate 。而 close 这个方法是绝了 , 它通过闭包包装了一个指向确定 this ,调用确定 function, 使用确定实参的 final 函数。不管在什么样的环境下调用 , 这个方法总能正确执行。

subscribeOnce 类似于 YUI 或者 jquery 中的 one 。只会收到一次监听。 ( 若事件已经触发过,则在注册阶段立即回调监听 )

unsubscribefire 分别用来注销监听器和触发事件。触发事件将会引起监听器的广播操作。可选的 fireArgs 用于保证 subscribeOnce 在事件已触发的情况下能获得正确的广播参数。

Channel 本身还有一个监听注册 / 注销的事件拦截。分别是 onSubscribeonUnSubscribe 。在 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 中我们看到下面一段代码。

 

channel.join(function() {

channel.onDeviceReady.fire();

}, channel.deviceReadyChannelsArray);

 

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

四、 启动与 PhoneGap 自定义事件

首先上图。


 

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

待续。。。。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值