cordova/channel模块事件拦截机制
本章节重点讲解channel模块是,它是如何订阅事件,触发事件,取消订阅。
一.基本使用
var channel = require('cordova/channel');
var funName = function(){console.log("trigger test")}
channel.createSticky('test');//建立个叫test的隧道
channel['test'].subscribe(funName)//test隧道订阅事件,可以连续订阅
channel['test'].subscribe(funName2)//test隧道订阅事件,可以连续订阅
channel['test'].onHasSubscribersChange(function(){console.log("已经有被订阅了")})//当我们订阅事件数目大于0的时候被触发,订阅事件减少到0时候被触发
channel['test'].fire()//触发订阅的事件
channel['test'].unsubscribe(funName)//取消已经订阅的事件
channel['test'].unsubscribe(funName2)//取消已经订阅的事件
二.跟window事件的关系
document.addEventListener('deviceready', function(){}, false);
1.创建deviceready隧道
//存放我们自定义的channel对象
var documentEventHandlers = {},windowEventHandlers = {};
//创建channel
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
//cordova模块
addStickyDocumentEventHandler:function(event) {
return (documentEventHandlers[event] = channel.createSticky(event));
},
2.替换 document.addEventListener事件(在cordova模块定义)
//替换document.addEventListener方法
document.addEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof documentEventHandlers[e] != 'undefined') {
documentEventHandlers[e].subscribe(handler);
} else {
//原来的document.addEventListener
m_document_addEventListener.call(document, evt, handler, capture);
}
};
由于已经预先创建了deviceready 隧道,所以我们在执行document.addEventListener('deviceready', function(){}, false);
时候监听器将添加在channel['onDeviceReady']上
3.触发事件
//见“cordova/init”模块
channel.join(function() {
require('cordova').fireDocumentEvent('deviceready');
}, channel.deviceReadyChannelsArray);
//cordova模块
fireDocumentEvent: function(type, data, bNoDetach) {
var evt = createEvent(type, data);
if (typeof documentEventHandlers[type] != 'undefined') {
if( bNoDetach ) {
documentEventHandlers[type].fire(evt);
}
else {
setTimeout(function() {
// Fire deviceready on listeners that were registered before cordova.js was loaded.
if (type == 'deviceready') {
document.dispatchEvent(evt);
}
documentEventHandlers[type].fire(evt);
}, 0);
}
} else {
document.dispatchEvent(evt);
}
},
channel模块源码注解
/**
* Custom pub-sub "channel" that can have functions subscribed to it
* This object is used to define and control firing of events for
* cordova initialization, as well as for custom events thereafter.
*
* The order of events during page load and Cordova startup is as follows:
*
* onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed.
* onNativeReady* Internal event that indicates the Cordova native side is ready.
* onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created.
* onDeviceReady* User event fired to indicate that Cordova is ready
* onResume User event fired to indicate a start/resume lifecycle event
* onPause User event fired to indicate a pause lifecycle event
*
* The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
* All listeners that subscribe after the event is fired will be executed right away.
*
* The only Cordova events that user code should register for are:
* deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript
* pause App has moved to background
* resume App has returned to foreground
*
* Listeners can be registered as:
* document.addEventListener("deviceready", myDeviceReadyListener, false);
* document.addEventListener("resume", myResumeListener, false);
* document.addEventListener("pause", myPauseListener, false);
*
* The DOM lifecycle events should be used for saving and restoring state
* window.onload
* window.onunload
*
*/
/**
* Channel
* @constructor
* @param type String the channel name
dispatchKeyEvent */
var Channel = function(type, sticky) {
this.type = type;
// //订阅的事件channel.subscribe方法添加的事件都将丢在该对象内{guid:func,guid2:func}
this.handlers = {};
// 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
this.state = sticky ? 1 : 0;
// Used in sticky mode to remember args passed to fire().
this.fireArgs = null;
// subscirbe数目
this.numHandlers = 0;//
//当有第一个subscribe的事件的时候,或者 最后一个unsubscribe事件被移除的时候将会触发该方法
this.onHasSubscribersChange = null;
},
channel = {
/**
* 参数2为channel数组,给数组中的所有channel都订阅f方法,当数组中所有的channel 都被fire的时候,fire的最后一个channel将会执行h方法
*/
join: function(h, c) {
var len = c.length,
i = len,
f = function() {
if (!(--i)) h();
};
for (var j=0; j<len; j++) {
if (c[j].state === 0) {
throw Error('Can only use join with sticky channels.');
}
c[j].subscribe(f);
}
if (!len) h();
},
create: function(type) {
return channel[type] = new Channel(type, false);
},
createSticky: function(type) {
return channel[type] = new Channel(type, true);
},
/**
* cordova Channels that must fire before "deviceready" is fired.
*/
deviceReadyChannelsArray: [],
deviceReadyChannelsMap: {},
/**
* Indicate that a feature needs to be initialized before it is ready to be used.
* This holds up Cordova's "deviceready" event until the feature has been initialized
* and Cordova.initComplete(feature) is called.
*
* @param feature {String} The unique feature name
*/
waitForInitialization: function(feature) {
if (feature) {
var c = channel[feature] || this.createSticky(feature);
this.deviceReadyChannelsMap[feature] = c;
this.deviceReadyChannelsArray.push(c);
}
},
/**
* Indicate that initialization code has completed and the feature is ready to be used.
*
* @param feature {String} The unique feature name
*/
initializationComplete: function(feature) {
var c = this.deviceReadyChannelsMap[feature];
if (c) {
c.fire();
}
}
};
function checkSubscriptionArgument(argument) {
if (typeof argument !== "function" && typeof argument.handleEvent !== "function") {
throw new Error(
"Must provide a function or an EventListener object " +
"implementing the handleEvent interface."
);
}
}
/**
* 订阅事件 最终结果handlers {guid:listener,guid2:listener2}
*/
Channel.prototype.subscribe = function(eventListenerOrFunction, eventListener) {
checkSubscriptionArgument(eventListenerOrFunction);
var handleEvent, guid;
if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
// Received an EventListener object implementing the handleEvent interface
handleEvent = eventListenerOrFunction.handleEvent;
eventListener = eventListenerOrFunction;
} else {
// Received a function to handle event
handleEvent = eventListenerOrFunction;
}
if (this.state == 2) {
handleEvent.apply(eventListener || this, this.fireArgs);
return;
}
guid = eventListenerOrFunction.observer_guid;
if (typeof eventListener === "object") {
handleEvent = utils.close(eventListener, handleEvent);
}
if (!guid) {
// First time any channel has seen this subscriber
guid = '' + nextGuid++;
}
handleEvent.observer_guid = guid;
eventListenerOrFunction.observer_guid = guid;
// Don't add the same handler more than once.
if (!this.handlers[guid]) {
this.handlers[guid] = handleEvent;
this.numHandlers++;
if (this.numHandlers == 1) {
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
/**
* 根据给定的函数名从handler对象中删除该函数
*/
Channel.prototype.unsubscribe = function(eventListenerOrFunction) {
checkSubscriptionArgument(eventListenerOrFunction);
var handleEvent, guid, handler;
if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
// Received an EventListener object implementing the handleEvent interface
handleEvent = eventListenerOrFunction.handleEvent;
} else {
// Received a function to handle event
handleEvent = eventListenerOrFunction;
}
guid = handleEvent.observer_guid;
handler = this.handlers[guid];
if (handler) {
delete this.handlers[guid];
this.numHandlers--;
if (this.numHandlers === 0) {
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
/**
* 触发handlers中的所有方法
*/
Channel.prototype.fire = function(e) {
var fail = false,
fireArgs = Array.prototype.slice.call(arguments);
// Apply stickiness.
if (this.state == 1) {
this.state = 2;
this.fireArgs = fireArgs;
}
if (this.numHandlers) {
// Copy the values first so that it is safe to modify it from within
// callbacks.
var toCall = [];
for (var item in this.handlers) {
toCall.push(this.handlers[item]);
}
for (var i = 0; i < toCall.length; ++i) {
toCall[i].apply(this, fireArgs);
}
if (this.state == 2 && this.numHandlers) {
this.numHandlers = 0;
this.handlers = {};
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
}
};
// defining them here so they are ready super fast!
// DOM event that is received when the web page is loaded and parsed.
channel.createSticky('onDOMContentLoaded');
// Event to indicate the Cordova native side is ready.
channel.createSticky('onNativeReady');
// Event to indicate that all Cordova JavaScript objects have been created
// and it's time to run plugin constructors.
channel.createSticky('onCordovaReady');
// Event to indicate that all automatically loaded JS plugins are loaded and ready.
// FIXME remove this
channel.createSticky('onPluginsReady');
// Event to indicate that Cordova is ready
channel.createSticky('onDeviceReady');
// Event to indicate a resume lifecycle event
channel.create('onResume');
// Event to indicate a pause lifecycle event
channel.create('onPause');
// Channels that must fire before "deviceready" is fired.
channel.waitForInitialization('onCordovaReady');
channel.waitForInitialization('onDOMContentLoaded');
module.exports = channel;
});