cordova/exec模块注解
define("cordova/exec", function(require, exports, module) {
/**
* Execute a cordova command. It is up to the native side whether this action
* is synchronous or asynchronous. The native side can return:
* Synchronous: PluginResult object as a JSON string
* Asynchronous: Empty string ""
* If async, the native side will cordova.callbackSuccess or cordova.callbackError,
* depending upon the result of the action.
*
* @param {Function} success The success callback
* @param {Function} fail The fail callback
* @param {String} service The name of the service to use
* @param {String} action Action to be run in cordova
* @param {String[]} [args] Zero or more arguments to pass to the method
*/
var cordova = require('cordova'),
nativeApiProvider = require('cordova/android/nativeapiprovider'),
utils = require('cordova/utils'),
base64 = require('cordova/base64'),
channel = require('cordova/channel'),//事件订阅模块
jsToNativeModes = {
PROMPT: 0,//使用弹出框prompt的方式来向native发送数据
JS_OBJECT: 1//使用原生暴露API的方式让js访问原生数据
},
nativeToJsModes = {
POLLING: 0,//轮寻的方式,前端定时器不间断的调用原生暴露API的方式来让js访问原生数据
LOAD_URL: 1,//native使用webview.loadUrl("javascript:code")方式来执行前端脚本达到通信目的
ONLINE_EVENT: 2,//native使用webview.setNetworkAvailable(true/false)方法,触发document online/offline 事件,再由前端主动访问原生变量来达到通信目的
EVAL_BRIDGE: 3 //native使用webview.loadUrl方式来执行前端脚本达到通信目的
},
jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
pollEnabled = false,//是否启用轮询
//我们在设置native到js的通信模式的时候会返回随机数字(字符),native端js端均保存这个变量值,
//在每次执行exec的时候会将js端保存的该值传递给native并做比较,如果两个值不相等则终止执行exec
bridgeSecret = -1;
var messagesFromNative = [];//存放来自native的执行结果队列
var isProcessing = false;
var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve();
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); };
function androidExec(success, fail, service, action, args) {
if (bridgeSecret < 0) {
//未初始化native到js的桥接的抛出异常
throw new Error('exec() called without bridgeSecret');
}
//设置js到native的桥接模式
if (jsToNativeBridgeMode === undefined) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
args = args || [];
//处理ArrayBuffer转base64字符串
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
args[i] = base64.fromArrayBuffer(args[i]);
}
}
//每次执行exec生成callbackId
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args);
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
//执行原生操作
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
androidExec(success, fail, service, action, args);
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
} else if (msgs) {
messagesFromNative.push(msgs);
//异步处理消息
nextTick(processMessages);
}
}
//初始化native to js的桥接方式保存bridgeSecret
androidExec.init = function() {
//CB-11828
//This failsafe checks the version of Android and if it's Jellybean, it switches it to
//using the Online Event bridge for communicating from Native to JS
//
//It's ugly, but it's necessary.
var check = navigator.userAgent.toLowerCase().match(/android\s[0-9].[0-9]/);
var version_code = check && check[0].match(/4.[0-3].*/);
if (version_code != null && nativeToJsBridgeMode == nativeToJsModes.EVAL_BRIDGE) {
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT;
}
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
function pollOnceFromOnlineEvent() {
pollOnce(true);
}
//js发起检索native变量操作
function pollOnce(opt_fromOnlineEvent) {
if (bridgeSecret < 0) {
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions.
// We know there's nothing to retrieve, so no need to poll.
return;
}
//js发起访问原生数据,由于原生将每次执行exec的结果保存在一个队列中(NativeToJsMessageQueue.java 中的queue数组)
//每次检索操作实际上将该数组的内容pop给js
var msgs = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent);
if (msgs) {
messagesFromNative.push(msgs);
// Process sync since we know we're already top-of-stack.
processMessages();
}
}
function pollingTimerFunc() {
if (pollEnabled) {
pollOnce();
setTimeout(pollingTimerFunc, 50);
}
}
//给文档添加online/offline事件,在native 调用webview.setNetworkAvailable()时候被触发,让js执行检索原生数据操作
function hookOnlineApis() {
function proxyEvent(e) {
cordova.fireWindowEvent(e.type);
}
window.addEventListener('online', pollOnceFromOnlineEvent, false);//给原生的window对象注册事件监听
window.addEventListener('offline', pollOnceFromOnlineEvent, false);
cordova.addWindowEventHandler('online');//创建一个交online类型的channle并丢在windowEventHandlers对象上,之后当调用fireWindowEvent时候会优先触发channel上注册的事件
cordova.addWindowEventHandler('offline');
document.addEventListener('online', proxyEvent, false);//给原生的document对象注册事件监听
document.addEventListener('offline', proxyEvent, false);
}
hookOnlineApis();
androidExec.jsToNativeModes = jsToNativeModes;
androidExec.nativeToJsModes = nativeToJsModes;
//设置js-》native的桥接模式
androidExec.setJsToNativeBridgeMode = function(mode) {
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
mode = jsToNativeModes.PROMPT;
}
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
jsToNativeBridgeMode = mode;
};
//设置 native -> js 的桥接模式
androidExec.setNativeToJsBridgeMode = function(mode) {
if (mode == nativeToJsBridgeMode) {
return;
}
if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
pollEnabled = false;
}
nativeToJsBridgeMode = mode;
if (bridgeSecret >= 0) {
//通知原生执行切换桥接模式操作
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
}
//如果是轮寻模式则启动定时器不端的访问原生变量
if (mode == nativeToJsModes.POLLING) {
pollEnabled = true;
setTimeout(pollingTimerFunc, 1);
}
};
//以下为具体处理消息过程不解释
function buildPayload(payload, message) {
var payloadKind = message.charAt(0);
if (payloadKind == 's') {
payload.push(message.slice(1));
} else if (payloadKind == 't') {
payload.push(true);
} else if (payloadKind == 'f') {
payload.push(false);
} else if (payloadKind == 'N') {
payload.push(null);
} else if (payloadKind == 'n') {
payload.push(+message.slice(1));
} else if (payloadKind == 'A') {
var data = message.slice(1);
payload.push(base64.toArrayBuffer(data));
} else if (payloadKind == 'S') {
payload.push(window.atob(message.slice(1)));
} else if (payloadKind == 'M') {
var multipartMessages = message.slice(1);
while (multipartMessages !== "") {
var spaceIdx = multipartMessages.indexOf(' ');
var msgLen = +multipartMessages.slice(0, spaceIdx);
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
buildPayload(payload, multipartMessage);
}
} else {
payload.push(JSON.parse(message));
}
}
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) {
var firstChar = message.charAt(0);
if (firstChar == 'J') {
// This is deprecated on the .java side. It doesn't work with CSP enabled.
eval(message.slice(1));
} else if (firstChar == 'S' || firstChar == 'F') {
var success = firstChar == 'S';
var keepCallback = message.charAt(1) == '1';
var spaceIdx = message.indexOf(' ', 2);
var status = +message.slice(2, spaceIdx);
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
var payloadMessage = message.slice(nextSpaceIdx + 1);
var payload = [];
buildPayload(payload, payloadMessage);
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
} else {
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
}
}
function processMessages() {
// Check for the reentrant case.
if (isProcessing) {
return;
}
if (messagesFromNative.length === 0) {
return;
}
isProcessing = true;
try {
var msg = popMessageFromQueue();
// The Java side can send a * message to indicate that it
// still has messages waiting to be retrieved.
if (msg == '*' && messagesFromNative.length === 0) {
nextTick(pollOnce);
return;
}
processMessage(msg);
} finally {
isProcessing = false;
if (messagesFromNative.length > 0) {
nextTick(processMessages);
}
}
}
function popMessageFromQueue() {
var messageBatch = messagesFromNative.shift();
if (messageBatch == '*') {
return '*';
}
var spaceIdx = messageBatch.indexOf(' ');
var msgLen = +messageBatch.slice(0, spaceIdx);
var message = messageBatch.substr(spaceIdx + 1, msgLen);
messageBatch = messageBatch.slice(spaceIdx + msgLen + 1);
if (messageBatch) {
messagesFromNative.unshift(messageBatch);
}
return message;
}
module.exports = androidExec;
});