src/android/android/nativeapiprovider.js JS->Native 的具體交互形式
Js 代碼
// file: src/android/android/nativeapiprovider.js
define("cordova/android/nativeapiprovider", function(require, exports, module) {
// WebView中是否通過(guò)addJavascriptInterface提供了訪問(wèn)ExposedJsApi.java的_cordovaNative對(duì)象
// 如果不存在選擇prompt()形式的交互方式
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
var currentApi = nativeApi;
module.exports = {
// 獲取當(dāng)前交互方式
get: function() { return currentApi; },
// 設(shè)置使用prompt()交互方式
// (true:prompt false:自動(dòng)選擇)
setPreferPrompt: function(value) {
currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi;
},
// 直接設(shè)置交互方式對(duì)象(很少用到)
set: function(value) {
currentApi = value;
}
};
});
src/android/android/promptbasednativeapi.js 通過(guò) prompt()和 Native 交互(Android2.3 simulator 的 Bug)
Js 代碼
// file: src/android/android/promptbasednativeapi.js
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
// 由于Android2.3模擬器存在Bug,不支持addJavascriptInterface()
// 所以借助prompt()來(lái)和Native進(jìn)行交互
// Native端會(huì)在CordovaChromeClient.onJsPrompt()中攔截處理
module.exports = {
// 調(diào)用Native API
exec: function(service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:'+JSON.stringify([service, action, callbackId]));
},
// 設(shè)置Native->JS的橋接模式
setNativeToJsBridgeMode: function(value) {
prompt(value, 'gap_bridge_mode:');
},
// 接收消息
retrieveJsMessages: function(fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:');
}
};
});
src/android/exec.js 執(zhí)行 JS->Native 交互
Js 代碼
// file: src/android/exec.js
define("cordova/exec", function(require, exports, module) {
var cordova = require('cordova'),
nativeApiProvider = require('cordova/android/nativeapiprovider'),
utils = require('cordova/utils'),
base64 = require('cordova/base64'),
// JS->Native的可選交互形式一覽
jsToNativeModes = {
// 基于prompt()的交互
PROMPT: 0,
// 基于JavascriptInterface的交互
JS_OBJECT: 1,
// 基于URL的交互
// ***由于安全問(wèn)題,默認(rèn)已經(jīng)設(shè)置成不可用的?。。? // NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE=false
LOCATION_CHANGE: 2
},
// Native->JS的可選交互形式一覽
nativeToJsModes = {
// 輪詢(JS->Native自助獲取消息)
POLLING: 0,
// 使用 webView.loadUrl("javascript:") 來(lái)執(zhí)行消息
// 解決軟鍵盤的Bug
LOAD_URL: 1,
// 攔截事件監(jiān)聽(tīng),使用online/offline事件來(lái)告訴JS獲取消息
// 默認(rèn)值 NativeToJsMessageQueue.DEFAULT_BRIDGE_MODE=2
ONLINE_EVENT: 2,
// 反射Webview的私有API來(lái)執(zhí)行JS(需要Android 3.2.4以上版本)
PRIVATE_API: 3
},
// 當(dāng)前JS->Native的交互形式
jsToNativeBridgeMode,
// 當(dāng)前Native->JS的交互形式
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
pollEnabled = false,
messagesFromNative = [];
// 執(zhí)行Cordova提供的API
// 比如: exec(successCallback, errorCallback, "Camera", "takePicture", args);
function androidExec(success, fail, service, action, args) {
// 默認(rèn)采用JavascriptInterface交互方式
if (jsToNativeBridgeMode === undefined) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
// 如果參數(shù)中存在ArrayBuffer類型的參數(shù),轉(zhuǎn)化成字符串
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
args[i] = base64.fromArrayBuffer(args[i]);
}
}
var callbackId = service + cordova.callbackId++,
// 把所有參數(shù)轉(zhuǎn)換成JSON串
argsJson = JSON.stringify(args);
// 設(shè)置回調(diào)函數(shù)
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
// 基于URL的交互(需要手動(dòng)修改NativeToJsMessageQueue.java的常量配置才能起效)
// Native端會(huì)在CordovaWebViewClient.shouldOverrideUrlLoading()中攔截處理
window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
} else {
// 選擇合適的交互方式和Native進(jìn)行交互
// 根據(jù)Native端NativeToJsMessageQueue.DISABLE_EXEC_CHAINING的配置,回傳消息可以是同步或者異步
// 默認(rèn)是同步的,返回PluginResult對(duì)象的JSON串。異步的話messages為空。
var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
// 如果參數(shù)被傳遞到Java端,但是接收到的是null,切換交互方式到prompt()在執(zhí)行一次
// Galaxy S2在傳遞某些Unicode字符的時(shí)候少數(shù)情況下有問(wèn)題,
// 參考 https://issues.apache.org/jira/browse/CB-2666
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
androidExec(success, fail, service, action, args);
// 執(zhí)行完成后,把交互方式再切回JavascriptInterface
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
return;
} else {
// 處理Native返回的消息
androidExec.processMessages(messages);
}
}
}
function pollOnceFromOnlineEvent() {
pollOnce(true);
}
// 從Native的消息隊(duì)列中獲取消息
function pollOnce(opt_fromOnlineEvent) {
var msg = nativeApiProvider.get().retrieveJsMessages(!!opt_fromOnlineEvent);
androidExec.processMessages(msg);
}
function pollingTimerFunc() {
if (pollEnabled) {
pollOnce();
setTimeout(pollingTimerFunc, 50);
}
}
function hookOnlineApis() {
function proxyEvent(e) {
cordova.fireWindowEvent(e.type);
}
window.addEventListener('online', pollOnceFromOnlineEvent, false);
window.addEventListener('offline', pollOnceFromOnlineEvent, false);
cordova.addWindowEventHandler('online');
cordova.addWindowEventHandler('offline');
document.addEventListener('online', proxyEvent, false);
document.addEventListener('offline', proxyEvent, false);
}
// 添加online/offline事件
hookOnlineApis();
// 外部可以訪問(wèn)到交互方式的常量
androidExec.jsToNativeModes = jsToNativeModes;
androidExec.nativeToJsModes = nativeToJsModes;
// 設(shè)置JS->Native的交互方式
androidExec.setJsToNativeBridgeMode = function(mode) {
// JavascriptInterface方式但是Native無(wú)法提供_cordovaNative對(duì)象的時(shí)候強(qiáng)制切到prompt()
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
console.log('Falling back on PROMPT mode since _cordovaNative is missing. Expected for Android 3.2 and lower only.');
mode = jsToNativeModes.PROMPT;
}
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
jsToNativeBridgeMode = mode;
};
// 設(shè)置Native->JS的交互方式
androidExec.setNativeToJsBridgeMode = function(mode) {
if (mode == nativeToJsBridgeMode) {
return;
}
// 如果以前是Poll的方式,,先回置到非Poll
if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
pollEnabled = false;
}
nativeToJsBridgeMode = mode;
// 告訴Native端,JS端獲取消息的方式
nativeApiProvider.get().setNativeToJsBridgeMode(mode);
· // 如果是在JS端Poll的方式的話
if (mode == nativeToJsModes.POLLING) {
pollEnabled = true;
// 停頓后執(zhí)行exec獲取消息message
setTimeout(pollingTimerFunc, 1);
}
};
// 處理從Native返回的一條消息
//
// 回傳消息的完整格式:
// (1)消息的長(zhǎng)度+空格+J+JavaScript代碼
// 44 Jcordova.callbackFromNative('InAppBrowser1478332075',true,1,[{"type":"loadstop","url":"http:\/\/www.baidu.com\/"}],true});
// (2)消息的長(zhǎng)度+空格+成功失敗標(biāo)示(J/S/F)+keepCallback標(biāo)示+具體的狀態(tài)碼+空格+回調(diào)ID+空格+回傳數(shù)據(jù)
// 78 S11 InAppBrowser970748887 {"type":"loadstop","url":"http:\/\/www.baidu.com\/"}
// 28 S01 Notification970748888 n0
//
// 默認(rèn)是關(guān)閉了返回Js代碼,使用返回?cái)?shù)據(jù)。
// NativeToJsMessageQueue.FORCE_ENCODE_USING_EVAL=false
function processMessage(message) {
try {
var firstChar = message.charAt(0);
if (firstChar == 'J') {
// 執(zhí)行回傳的JavaScript代碼
eval(message.slice(1));
} else if (firstChar == 'S' || firstChar == 'F') {
// S代表處理成功(包含沒(méi)有數(shù)據(jù)),F(xiàn)代表處理失敗
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 payloadKind = message.charAt(nextSpaceIdx + 1);
var payload;
if (payloadKind == 's') {
// 字符串:s+字符串
payload = message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 't') {
// 布爾值:t/f
payload = true;
} else if (payloadKind == 'f') {
// 布爾值:t/f
payload = false;
} else if (payloadKind == 'N') {
// Null:N
payload = null;
} else if (payloadKind == 'n') {
// 數(shù)值:n+具體值
payload = +message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 'A') {
// ArrayBuffer:A+數(shù)據(jù)
var data = message.slice(nextSpaceIdx + 2);
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload = arraybuffer.buffer;
} else if (payloadKind == 'S') {
// 二進(jìn)制字符串:S+字符串
payload = window.atob(message.slice(nextSpaceIdx + 2));
} else {
// JSON:JSON串
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
}
// 調(diào)用回調(diào)函數(shù)
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
} else {
console.log("processMessage failed: invalid message:" + message);
}
} catch (e) {
console.log("processMessage failed: Message: " + message);
console.log("processMessage failed: Error: " + e);
console.log("processMessage failed: Stack: " + e.stack);
}
}
// 處理Native返回的消息
androidExec.processMessages = function(messages) {
// 如果消息存在的話(異步?jīng)]有直接返回)
if (messages) {
// 把傳入的消息放到數(shù)組中
messagesFromNative.push(messages);
// messagesFromNative是全局的,而processMessages方法可重入,
// 所以只需放到數(shù)組,后邊循環(huán)處理即可
if (messagesFromNative.length > 1) {
return;
}
// 遍歷從Native獲得所有消息
while (messagesFromNative.length) {
// 處理完成后才可以從數(shù)組中刪除
messages = messagesFromNative[0];
// Native返回星號(hào)代表消息需要等一會(huì)兒再取
if (messages == '*') {
// 刪除數(shù)組的第一個(gè)元素
messagesFromNative.shift();
// 再次去獲取消息
window.setTimeout(pollOnce, 0);
return;
}
// 獲取消息的長(zhǎng)度
var spaceIdx = messages.indexOf(' ');
var msgLen = +messages.slice(0, spaceIdx);
// 獲取第一個(gè)消息
var message = messages.substr(spaceIdx + 1, msgLen);
// 截取掉第一個(gè)消息
messages = messages.slice(spaceIdx + msgLen + 1);
// 處理第一個(gè)消息
processMessage(message);
// 如果消息包含多個(gè),繼續(xù)處理;單個(gè)的話刪除本地消息數(shù)組中的數(shù)據(jù)
if (messages) {
messagesFromNative[0] = messages;
} else {
messagesFromNative.shift();
}
}
}
};
module.exports = androidExec;
});