執(zhí)行 cordova.js 的入口就以下2行代碼:
Js 代碼
// 導(dǎo)入cordova
window.cordova = require('cordova');
// 啟動(dòng)處理
require('cordova/init');
src/cordova.js 事件的處理和回調(diào),外部訪問 cordova.js 的入口
其中第一步是加載整個(gè)模塊系統(tǒng)和外部訪問 cordova.js 的入口,基于事件通道提供了整體的事件攔截控制及回調(diào)。代碼不是很復(fù)雜。
源碼:
Js 代碼
// file: src/cordova.js
define("cordova", function(require, exports, module) {
// 調(diào)用通道和平臺(tái)模塊
var channel = require('cordova/channel');
var platform = require('cordova/platform');
// 備份document和window的事件監(jiān)聽器
var m_document_addEventListener = document.addEventListener;
var m_document_removeEventListener = document.removeEventListener;
var m_window_addEventListener = window.addEventListener;
var m_window_removeEventListener = window.removeEventListener;
// 保存自定義的document和window的事件監(jiān)聽器
var documentEventHandlers = {},
windowEventHandlers = {};
// 攔截document和window的事件監(jiān)聽器(addEventListener/removeEventListener)
// 存在自定義的事件監(jiān)聽器的話,使用自定義的;不存在的話調(diào)用備份document和window的事件監(jiān)聽器
document.addEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof documentEventHandlers[e] != 'undefined') {
documentEventHandlers[e].subscribe(handler);
} else {
m_document_addEventListener.call(document, evt, handler, capture);
}
};
window.addEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof windowEventHandlers[e] != 'undefined') {
windowEventHandlers[e].subscribe(handler);
} else {
m_window_addEventListener.call(window, evt, handler, capture);
}
};
document.removeEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof documentEventHandlers[e] != "undefined") {
documentEventHandlers[e].unsubscribe(handler);
} else {
m_document_removeEventListener.call(document, evt, handler, capture);
}
};
window.removeEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof windowEventHandlers[e] != "undefined") {
windowEventHandlers[e].unsubscribe(handler);
} else {
m_window_removeEventListener.call(window, evt, handler, capture);
}
};
// 創(chuàng)建一個(gè)指定type的事件。
// 參考:https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent#Notes
function createEvent(type, data) {
var event = document.createEvent('Events');
// 指定事件名、不可冒泡、不可取消
event.initEvent(type, false, false);
// 自定義數(shù)據(jù)
if (data) {
for (var i in data) {
if (data.hasOwnProperty(i)) {
event[i] = data[i];
}
}
}
return event;
}
// 外部訪問cordova.js的入口
var cordova = {
// 模塊系統(tǒng)
define:define,
require:require,
// 版本號(hào)和平臺(tái)名
version:CORDOVA_JS_BUILD_LABEL,
platformId:platform.id,
// 為了攔截document和window的事件監(jiān)聽器,添加或刪除自定義的事件監(jiān)聽器
addWindowEventHandler:function(event) {
return (windowEventHandlers[event] = channel.create(event));
},
// sticky 是指一旦被調(diào)用那么它以后都保持被調(diào)用的狀態(tài),所定義的監(jiān)聽器會(huì)被立即執(zhí)行。
// 比如: deviceready事件只觸發(fā)一次,以后的所有監(jiān)聽都是立即執(zhí)行的。
addStickyDocumentEventHandler:function(event) {
return (documentEventHandlers[event] = channel.createSticky(event));
},
addDocumentEventHandler:function(event) {
return (documentEventHandlers[event] = channel.create(event));
},
removeWindowEventHandler:function(event) {
delete windowEventHandlers[event];
},
removeDocumentEventHandler:function(event) {
delete documentEventHandlers[event];
},
// 獲取攔截前的document和window的事件監(jiān)聽器
getOriginalHandlers: function() {
return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener},
'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}};
},
// 調(diào)用document的事件
fireDocumentEvent: function(type, data, bNoDetach) {
var evt = createEvent(type, data);
if (typeof documentEventHandlers[type] != 'undefined') {
// 判斷是否需要拋出事件異常
if( bNoDetach ) {
// 通過Channel的fire方法來(lái)調(diào)用事件(apply)
documentEventHandlers[type].fire(evt);
}
else {
// setTimeout(callback, 0) 的意思是DOM構(gòu)成完畢、事件監(jiān)聽器執(zhí)行完后立即執(zhí)行
setTimeout(function() {
// 調(diào)用加載cordova.js之前定義的那些deviceready事件
if (type == 'deviceready') {
document.dispatchEvent(evt);
}
// 通過Channel的fire方法來(lái)調(diào)用事件(apply)
documentEventHandlers[type].fire(evt);
}, 0);
}
} else {
// 直接調(diào)用事件
document.dispatchEvent(evt);
}
},
// 調(diào)用window的事件
fireWindowEvent: function(type, data) {
var evt = createEvent(type,data);
if (typeof windowEventHandlers[type] != 'undefined') {
setTimeout(function() {
windowEventHandlers[type].fire(evt);
}, 0);
} else {
window.dispatchEvent(evt);
}
},
// 插件回調(diào)相關(guān)-------------------------------------
// 回調(diào)ID中間的一個(gè)隨機(jī)數(shù)(真正的ID:插件名+隨機(jī)數(shù))
callbackId: Math.floor(Math.random() * 2000000000),
// 回調(diào)函數(shù)對(duì)象,比如success,fail
callbacks: {},
// 回調(diào)狀態(tài)
callbackStatus: {
NO_RESULT: 0,
OK: 1,
CLASS_NOT_FOUND_EXCEPTION: 2,
ILLEGAL_ACCESS_EXCEPTION: 3,
INSTANTIATION_EXCEPTION: 4,
MALFORMED_URL_EXCEPTION: 5,
IO_EXCEPTION: 6,
INVALID_ACTION: 7,
JSON_EXCEPTION: 8,
ERROR: 9
},
// 以后使用callbackFromNative代替callbackSuccess和callbackError
callbackSuccess: function(callbackId, args) {
try {
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in error callback: " + callbackId + " = "+e);
}
},
callbackError: function(callbackId, args) {
try {
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in error callback: " + callbackId + " = "+e);
}
},
// 調(diào)用回調(diào)函數(shù)
callbackFromNative: function(callbackId, success, status, args, keepCallback) {
var callback = cordova.callbacks[callbackId];
// 判斷是否定義了回調(diào)函數(shù)
if (callback) {
if (success && status == cordova.callbackStatus.OK) {
// 調(diào)用success函數(shù)
callback.success && callback.success.apply(null, args);
} else if (!success) {
// 調(diào)用fail函數(shù)
callback.fail && callback.fail.apply(null, args);
}
// 如果設(shè)置成不再保持回調(diào),刪除回調(diào)函數(shù)對(duì)象
if (!keepCallback) {
delete cordova.callbacks[callbackId];
}
}
},
// 沒有地方用到!
// 目的是把你自己的函數(shù)在注入到Cordova的生命周期中。
addConstructor: function(func) {
channel.onCordovaReady.subscribe(function() {
try {
func();
} catch(e) {
console.log("Failed to run constructor: " + e);
}
});
}
};
module.exports = cordova;
});
src/common/init.js 初始化處理
第二步是就是執(zhí)行初始化處理
源碼:
Js 代碼
// file: src/common/init.js
define("cordova/init", function(require, exports, module) {
var channel = require('cordova/channel');
var cordova = require('cordova');
var modulemapper = require('cordova/modulemapper');
var platform = require('cordova/platform');
var pluginloader = require('cordova/pluginloader');
// 定義平臺(tái)初期化處理必須在onNativeReady和onPluginsReady之后進(jìn)行
var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady];
// 輸出事件通道名到日志
function logUnfiredChannels(arr) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i].state != 2) {
console.log('Channel not fired: ' + arr[i].type);
}
}
}
// 5秒之后deviceready事件還沒有被調(diào)用將輸出log提示
// 出現(xiàn)這個(gè)錯(cuò)誤的情況比較復(fù)雜,比如,加載的plugin太多等等
window.setTimeout(function() {
if (channel.onDeviceReady.state != 2) {
console.log('deviceready has not fired after 5 seconds.');
logUnfiredChannels(platformInitChannelsArray);
logUnfiredChannels(channel.deviceReadyChannelsArray);
}
}, 5000);
// 替換window.navigator
function replaceNavigator(origNavigator) {
// 定義新的navigator,把navigator的原型鏈賦給新的navigator的原型鏈
var CordovaNavigator = function() {};
CordovaNavigator.prototype = origNavigator;
var newNavigator = new CordovaNavigator();
// 判斷是否存在Function.bind函數(shù)
if (CordovaNavigator.bind) {
for (var key in origNavigator) {
if (typeof origNavigator[key] == 'function') {
// 通過bind創(chuàng)建一個(gè)新的函數(shù)(this指向navigator)后賦給新的navigator
// 參考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
newNavigator[key] = origNavigator[key].bind(origNavigator);
}
}
}
return newNavigator;
}
// 替換webview的BOM對(duì)象navigator
// Cordova提供的接口基本都是:navigator.<plugin_name>.<action_name>
if (window.navigator) {
window.navigator = replaceNavigator(window.navigator);
}
// 定義console.log()
if (!window.console) {
window.console = {
log: function(){}
};
}
// 定義console.warn()
if (!window.console.warn) {
window.console.warn = function(msg) {
this.log("warn: " + msg);
};
}
// 注冊(cè)pause,resume,deviceready事件通道,并應(yīng)用到Cordova自定義的事件攔截
// 這樣頁(yè)面定義的事件監(jiān)聽器就能訂閱到相應(yīng)的通道上了。
channel.onPause = cordova.addDocumentEventHandler('pause');
channel.onResume = cordova.addDocumentEventHandler('resume');
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
// 如果此時(shí)DOM加載完成,觸發(fā)onDOMContentLoaded事件通道中的事件處理
if (document.readyState == 'complete' || document.readyState == 'interactive') {
channel.onDOMContentLoaded.fire();
} else {
// 如果此時(shí)DOM沒有加載完成,定義一個(gè)監(jiān)聽器在DOM完成后觸發(fā)事件通道的處理
// 注意這里調(diào)用的webview的原生事件監(jiān)聽
document.addEventListener('DOMContentLoaded', function() {
channel.onDOMContentLoaded.fire();
}, false);
}
// 以前版本是在CordovaLib中反向執(zhí)行js把_nativeReady設(shè)置成true后觸發(fā)事件通道
// 現(xiàn)在已經(jīng)改成在平臺(tái)啟動(dòng)處理中立即觸發(fā)
// 參考:https://issues.apache.org/jira/browse/CB-3066
if (window._nativeReady) {
channel.onNativeReady.fire();
}
// 給常用的模塊起個(gè)別名
// 比如:就可以直接使用cordova.exec(...)來(lái)代替var exec = require('cordova/exec'); exec(...);
// 不過第一行第二個(gè)參數(shù)應(yīng)該是“Cordova”,c應(yīng)該大寫!??!
modulemapper.clobbers('cordova', 'cordova');
modulemapper.clobbers('cordova/exec', 'cordova.exec');
modulemapper.clobbers('cordova/exec', 'Cordova.exec');
// 調(diào)用平臺(tái)初始化啟動(dòng)處理
platform.bootstrap && platform.bootstrap();
// 所有插件加載完成后,觸發(fā)onPluginsReady事件通道中的事件處理
pluginloader.load(function() {
channel.onPluginsReady.fire();
});
// 一旦本地代碼準(zhǔn)備就緒,創(chuàng)建cordova所需的所有對(duì)象
channel.join(function() {
// 把所有模塊附加到window對(duì)象上
modulemapper.mapModules(window);
// 如果平臺(tái)有特殊的初始化處理,調(diào)用它(目前來(lái)看都沒有)
platform.initialize && platform.initialize();
// 觸發(fā)onCordovaReady事件通道,標(biāo)示cordova準(zhǔn)備完成
channel.onCordovaReady.fire();
// 一切準(zhǔn)備就緒后,執(zhí)行deviceready事件通道上的所有事件。
channel.join(function() {
require('cordova').fireDocumentEvent('deviceready');
}, channel.deviceReadyChannelsArray); // onCordovaReady、onDOMContentLoaded
}, platformInitChannelsArray); // onNativeReady、onPluginsReady
});
src/android/platform.js 平臺(tái)啟動(dòng)處理
源碼:
Js 代碼
// file: src/android/platform.js
define("cordova/platform", function(require, exports, module) {
module.exports = {
id: 'android',
// 平臺(tái)啟動(dòng)處理(各個(gè)平臺(tái)處理都不一樣,比如ios就只需要觸發(fā)onNativeReady)
bootstrap: function() {
var channel = require('cordova/channel'),
cordova = require('cordova'),
exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper');
// 把exec()的執(zhí)行從WebCore線程變到UI線程上來(lái)
// 后臺(tái)PluginManager初始化的時(shí)候默認(rèn)添加了一個(gè)'PluginManager的插件
exec(null, null, 'PluginManager', 'startup', []);
// 觸發(fā)onNativeReady事件通道,告訴JS本地代碼已經(jīng)完成
channel.onNativeReady.fire();
// app插件?,F(xiàn)在沒有被單獨(dú)抽出去,沒找到合適的地方。
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
// 給返回按鈕注意個(gè)監(jiān)聽器
var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
backButtonChannel.onHasSubscribersChange = function() {
// 如果只為返回按鈕定義了1個(gè)事件監(jiān)聽器的話,通知后臺(tái)覆蓋默認(rèn)行為
exec(null, null, "App", "overrideBackbutton", [this.numHandlers == 1]);
};
// 添加菜單和搜素的事件監(jiān)聽
cordova.addDocumentEventHandler('menubutton');
cordova.addDocumentEventHandler('searchbutton');
// 啟動(dòng)完成后,告訴本地代碼顯示W(wǎng)ebView
channel.onCordovaReady.subscribe(function() {
exec(null, null, "App", "show", []);
});
}
};
});
src/common/pluginloader.js 加載所有 cordova_plugins.js 中定義的模塊,執(zhí)行完成后會(huì)觸發(fā)onPluginsReady
Js 代碼
// file: src/common/pluginloader.js
define("cordova/pluginloader", function(require, exports, module) {
var modulemapper = require('cordova/modulemapper');
var urlutil = require('cordova/urlutil');
// 創(chuàng)建<script>tag,把js文件動(dòng)態(tài)添加到head中
function injectScript(url, onload, onerror) {
var script = document.createElement("script");
script.onload = onload;
script.onerror = onerror || onload; // 出錯(cuò)的時(shí)候也執(zhí)行onload處理
script.src = url;
document.head.appendChild(script);
}
// 加載到head中的插件js腳本定義如下:
// cordova.define("org.apache.cordova.xxx", function(require, exports, module) { ... });
// 模塊名稱是cordova_plugins.js中定義的id,所以要把該id指向定義好的clobbers
function onScriptLoadingComplete(moduleList, finishPluginLoading) {
for (var i = 0, module; module = moduleList[i]; i++) {
if (module) {
try {
// 把該模塊需要clobber的clobber到指定的clobbers里
if (module.clobbers && module.clobbers.length) {
for (var j = 0; j < module.clobbers.length; j++) {
modulemapper.clobbers(module.id, module.clobbers[j]);
}
}
// 把該模塊需要合并的部分合并到指定的模塊里
if (module.merges && module.merges.length) {
for (var k = 0; k < module.merges.length; k++) {
modulemapper.merges(module.id, module.merges[k]);
}
}
// 處理只希望require()的模塊
// <js-module src="www/xxx.js" name="Xxx">
// <runs />
// </js-module>
if (module.runs && !(module.clobbers && module.clobbers.length) && !(module.merges && module.merges.length)) {
modulemapper.runs(module.id);
}
}
catch(err) {
}
}
}
// 插件js腳本加載完成后,執(zhí)行回調(diào)!!!
finishPluginLoading();
}
// 加載所有cordova_plugins.js中定義的js-module
function handlePluginsObject(path, moduleList, finishPluginLoading) {
var scriptCounter = moduleList.length;
// 沒有插件,直接執(zhí)行回調(diào)后返回
if (!scriptCounter) {
finishPluginLoading();
return;
}
// 加載每個(gè)插件js的腳本的回調(diào)
function scriptLoadedCallback() {
// 加載完成一個(gè)就把計(jì)數(shù)器減1
if (!--scriptCounter) {
// 直到所有插件的js腳本都被加載完成后clobber
onScriptLoadingComplete(moduleList, finishPluginLoading);
}
}
// 依次把插件的js腳本添加到head中后加載
for (var i = 0; i < moduleList.length; i++) {
injectScript(path + moduleList[i].file, scriptLoadedCallback);
}
}
// 注入插件的js腳本
function injectPluginScript(pathPrefix, finishPluginLoading) {
var pluginPath = pathPrefix + 'cordova_plugins.js';
// 根據(jù)cordova.js文件的路徑首先把cordova_plugins.js添加到head中后加載
injectScript(pluginPath, function() {
try {
// 導(dǎo)入cordova_plugins.jsz中定義的'cordova/plugin_list'模塊
// 這個(gè)文件的內(nèi)容是根據(jù)所有插件的plugin.xml生成的。
var moduleList = require("cordova/plugin_list");
// 加載所有cordova_plugins.js中定義的js-module
handlePluginsObject(pathPrefix, moduleList, finishPluginLoading);
}
catch (e) {
// 忽略cordova_plugins.js記載失敗、或者文件不存在等錯(cuò)誤
finishPluginLoading();
}
}, finishPluginLoading);
}
// 獲取cordova.js文件的路徑
function findCordovaPath() {
var path = null;
var scripts = document.getElementsByTagName('script');
var term = 'cordova.js';
for (var n = scripts.length-1; n>-1; n--) {
var src = scripts[n].src;
if (src.indexOf(term) == (src.length - term.length)) {
path = src.substring(0, src.length - term.length);
break;
}
}
return path;
}
// 加載所有cordova_plugins.js中定義的js-module
// 執(zhí)行完成后會(huì)觸發(fā)onPluginsReady(異步執(zhí)行)
exports.load = function(callback) {
// 取cordova.js文件所在的路徑
var pathPrefix = findCordovaPath();
if (pathPrefix === null) {
console.log('Could not find cordova.js script tag. Plugin loading may fail.');
pathPrefix = '';
}
// 注入插件的js腳本,執(zhí)行完成后回調(diào)onPluginsReady
injectPluginScript(pathPrefix, callback);
};
});