`
rensanning
  • 浏览: 3513693 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
Efef1dba-f7dd-3931-8a61-8e1c76c3e39f
使用Titanium Mo...
浏览量:37477
Bbab2146-6e1d-3c50-acd6-c8bae29e307d
Cordova 3.x入门...
浏览量:604281
C08766e7-8a33-3f9b-9155-654af05c3484
常用Java开源Libra...
浏览量:678011
77063fb3-0ee7-3bfa-9c72-2a0234ebf83e
搭建 CentOS 6 服...
浏览量:87236
E40e5e76-1f3b-398e-b6a6-dc9cfbb38156
Spring Boot 入...
浏览量:399799
Abe39461-b089-344f-99fa-cdfbddea0e18
基于Spring Secu...
浏览量:69058
66a41a70-fdf0-3dc9-aa31-19b7e8b24672
MQTT入门
浏览量:90460
社区版块
存档分类
最新评论

Cordova 3.x 源码分析(6) -- cordova.js本地交互JS<->Native

阅读更多
src/android/android/nativeapiprovider.js JS->Native的具体交互形式
// file: src/android/android/nativeapiprovider.js
define("cordova/android/nativeapiprovider", function(require, exports, module) {

// WebView中是否通过addJavascriptInterface提供了访问ExposedJsApi.java的_cordovaNative对象
// 如果不存在选择prompt()形式的交互方式
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
var currentApi = nativeApi;

module.exports = {
    // 获取当前交互方式
    get: function() { return currentApi; },

    // 设置使用prompt()交互方式
    // (true:prompt false:自动选择)
    setPreferPrompt: function(value) {
        currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi;
    },

    // 直接设置交互方式对象(很少用到)
    set: function(value) {
        currentApi = value;
    }
};

});


src/android/android/promptbasednativeapi.js 通过prompt()和Native交互(Android2.3 simulator的Bug)
// file: src/android/android/promptbasednativeapi.js
define("cordova/android/promptbasednativeapi", function(require, exports, module) {

// 由于Android2.3模拟器存在Bug,不支持addJavascriptInterface()
// 所以借助prompt()来和Native进行交互
// Native端会在CordovaChromeClient.onJsPrompt()中拦截处理
module.exports = {
    // 调用Native API
    exec: function(service, action, callbackId, argsJson) {
        return prompt(argsJson, 'gap:'+JSON.stringify([service, action, callbackId]));
    },

    // 设置Native->JS的桥接模式
    setNativeToJsBridgeMode: function(value) {
        prompt(value, 'gap_bridge_mode:');
    },

    // 接收消息
    retrieveJsMessages: function(fromOnlineEvent) {
        return prompt(+fromOnlineEvent, 'gap_poll:');
    }
};

});


src/android/exec.js 执行JS->Native交互
// 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的交互 
        // ***由于安全问题,默认已经设置成不可用的!!!
        // NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE=false
        LOCATION_CHANGE: 2
    },

    // Native->JS的可选交互形式一览
    nativeToJsModes = {
        // 轮询(JS->Native自助获取消息)
        POLLING: 0,
        // 使用 webView.loadUrl("javascript:") 来执行消息
        // 解决软键盘的Bug
        LOAD_URL: 1,
        // 拦截事件监听,使用online/offline事件来告诉JS获取消息
        // 默认值 NativeToJsMessageQueue.DEFAULT_BRIDGE_MODE=2
        ONLINE_EVENT: 2,
        // 反射Webview的私有API来执行JS(需要Android 3.2.4以上版本)
        PRIVATE_API: 3
    },

    // 当前JS->Native的交互形式
    jsToNativeBridgeMode,
    // 当前Native->JS的交互形式
    nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
    pollEnabled = false,
    messagesFromNative = [];

// 执行Cordova提供的API
// 比如: exec(successCallback, errorCallback, "Camera", "takePicture", args);
function androidExec(success, fail, service, action, args) {
    // 默认采用JavascriptInterface交互方式
    if (jsToNativeBridgeMode === undefined) {
        androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
    }

    // 如果参数中存在ArrayBuffer类型的参数,转化成字符串
    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++,
        // 把所有参数转换成JSON串
        argsJson = JSON.stringify(args);

    // 设置回调函数
    if (success || fail) {
        cordova.callbacks[callbackId] = {success:success, fail:fail};
    }

    if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
        // 基于URL的交互(需要手动修改NativeToJsMessageQueue.java的常量配置才能起效)
        // Native端会在CordovaWebViewClient.shouldOverrideUrlLoading()中拦截处理
        window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
    } else {
        // 选择合适的交互方式和Native进行交互
        // 根据Native端NativeToJsMessageQueue.DISABLE_EXEC_CHAINING的配置,回传消息可以是同步或者异步
        // 默认是同步的,返回PluginResult对象的JSON串。异步的话messages为空。
        var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);

        if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
            // 如果参数被传递到Java端,但是接收到的是null,切换交互方式到prompt()在执行一次
            // Galaxy S2在传递某些Unicode字符的时候少数情况下有问题,
            // 参考 https://issues.apache.org/jira/browse/CB-2666
            androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
            androidExec(success, fail, service, action, args);

            // 执行完成后,把交互方式再切回JavascriptInterface
            androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
            return;
        } else {
            // 处理Native返回的消息
            androidExec.processMessages(messages);
        }
    }
}

function pollOnceFromOnlineEvent() {
    pollOnce(true);
}

// 从Native的消息队列中获取消息
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();

// 外部可以访问到交互方式的常量
androidExec.jsToNativeModes = jsToNativeModes;
androidExec.nativeToJsModes = nativeToJsModes;

// 设置JS->Native的交互方式
androidExec.setJsToNativeBridgeMode = function(mode) {
    // JavascriptInterface方式但是Native无法提供_cordovaNative对象的时候强制切到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;
};

// 设置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;
        // 停顿后执行exec获取消息message
        setTimeout(pollingTimerFunc, 1);
    }
};

// 处理从Native返回的一条消息
// 
// 回传消息的完整格式:
// (1)消息的长度+空格+J+JavaScript代码
// 44 Jcordova.callbackFromNative('InAppBrowser1478332075',true,1,[{"type":"loadstop","url":"http:\/\/www.baidu.com\/"}],true});
// (2)消息的长度+空格+成功失败标示(J/S/F)+keepCallback标示+具体的状态码+空格+回调ID+空格+回传数据
// 78 S11 InAppBrowser970748887 {"type":"loadstop","url":"http:\/\/www.baidu.com\/"}
// 28 S01 Notification970748888 n0
//
// 默认是关闭了返回Js代码,使用返回数据。
// NativeToJsMessageQueue.FORCE_ENCODE_USING_EVAL=false
function processMessage(message) {
    try {
        var firstChar = message.charAt(0);
        if (firstChar == 'J') {
            // 执行回传的JavaScript代码
            eval(message.slice(1));
        } else if (firstChar == 'S' || firstChar == 'F') {
            // S代表处理成功(包含没有数据),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 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') {
                // 数值:n+具体值
                payload = +message.slice(nextSpaceIdx + 2);
            } else if (payloadKind == 'A') {
                // ArrayBuffer:A+数据
                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') {
                // 二进制字符串:S+字符串
                payload = window.atob(message.slice(nextSpaceIdx + 2));
            } else {
                // JSON:JSON串
                payload = JSON.parse(message.slice(nextSpaceIdx + 1));
            }
            // 调用回调函数
            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) {
    // 如果消息存在的话(异步没有直接返回)
    if (messages) {

        // 把传入的消息放到数组中
        messagesFromNative.push(messages);

        // messagesFromNative是全局的,而processMessages方法可重入,
        // 所以只需放到数组,后边循环处理即可
        if (messagesFromNative.length > 1) {
            return;
        }

        // 遍历从Native获得所有消息
        while (messagesFromNative.length) {
            // 处理完成后才可以从数组中删除
            messages = messagesFromNative[0];

            // Native返回星号代表消息需要等一会儿再取
            if (messages == '*') {
                // 删除数组的第一个元素
                messagesFromNative.shift();
                // 再次去获取消息
                window.setTimeout(pollOnce, 0);
                return;
            }

            // 获取消息的长度
            var spaceIdx = messages.indexOf(' ');
            var msgLen = +messages.slice(0, spaceIdx);
            // 获取第一个消息
            var message = messages.substr(spaceIdx + 1, msgLen);
            // 截取掉第一个消息
            messages = messages.slice(spaceIdx + msgLen + 1);

            // 处理第一个消息
            processMessage(message);

            // 如果消息包含多个,继续处理;单个的话删除本地消息数组中的数据
            if (messages) {
                messagesFromNative[0] = messages;
            } else {
                messagesFromNative.shift();
            }
        }
    }
};

module.exports = androidExec;

});
分享到:
评论
2 楼 rensanning 2014-12-08  
总的来说:在cordova.js里发给Webview的请求可以是同步或异步的,如果是同步的会返回PluginResult对象的JSON串,异步的返回空字符串。

源码里能看到nativeToJsModes有四种:POLLING,LOAD_URL,ONLINE_EVENT,PRIVATE_API,默认是ONLINE_EVENT。Java代码NativeToJsMessageQueue专门来处理JsMessage,通过激活online/offline事件来通知JS代码的调用,online事件也被注册为pollOnceFromOnlineEvent(),里边会poll一次Message从而获得执行结果。

如果使用POLL的话就是异步,需要你用JS timer来轮询结果。
1 楼 yijianpiaoxue2011 2014-12-08  
楼主,你好:
   拜读你的文章,受益良多,有个问题想请教一下,PluginManager.exec can be used either synchronously or async. 这是cordova对于插件的注释,楼组怎么理解,他是如何实现同步和异步的,望帮助解答,非常感谢。

相关推荐

    cordova-plugin-nativescript:用于与 nativescript 运行时交互的 Cordova 插件

    一个 Cordova 插件,用于使用 NativeScript 调用任何本机 API。 目前仅支持 iOS。 安装 科尔多瓦插件添加 在 iOS 上,元数据生成脚本必须在编译应用程序和部署到设备之间运行。 after-compile 钩子对此不起作用,...

    ionic-plugin

    键盘cordova.plugins.Keyboard对象提供了使与键盘的交互更容易的函数,并触发事件以指示键盘将隐藏/显示。 cordova plugin add com.ionic.keyboard方法cordova.plugins.Keyboard.hideKeyboardAccessoryBar cordova....

    cordova开发自定义插件(详细篇含jar包调用示例)

    cordova自定义插件,实现toast,jar包的调用(利于对第三方jar包调用的理解),native与js的交互,实现android与js的信息回调。

    cordova 自定义插件demo(含jar包调用示例)

    cordova自定义插件,实现toast,jar包的调用(利于对第三方jar包调用的理解),native与js的交互,实现android与js的信息回调。

    腾讯x5webview和JS交互的三方库-Android开发

    X5Bridge X5Bridge Android本机和JavaScript之间的桥梁的库Google本机具有一组通信,实际上,它也可以完成回调交互i X5Bridge X5Bridge Android本机和JavaScript之间的桥梁的库一组通信,实际上,它也可以完成回调...

    Cordova浅析架构原理

    因为项目使用了Cordova,也使用了很长时间。至于有很多hybride框架,为什么我们使用Cordova,这里不做过多的叙述,我们也是根据项目需求来选定的,需要及时更新、还要输出别人...核心的东西就是H5与Native的交互原理、

    酷炫的爆栈网源码.zip

     本地化 English: https://github.com/unruledboy/WebFrontEndStack/ 中文博客: http://www.cnblogs.com/unruledboy/p/WebFrontEndStack.html Español: ...

    LDJSBridge_IOS:IOS平台零修改集成到产品的JSBridge简化框架

    LDJSBridge_IOS的核心目标是完成在IOS客户端中WAP页面和客户端(Native)的深度交互。 如何集成LDJSBridge_IOS 强烈推荐采用Pod集成:在项目工程的Podfile文件中加载LDJSBridge库: pod 'LDJSBridge' :git =&gt; '...

    是一本介绍如何使用HTML和JavaScript来创建iOS应用程序的书籍

    在这本书中,作者可能会介绍一些跨平台开发框架或工具,如PhoneGap(现在称为Apache Cordova)或React Native等,这些工具允许开发者使用HTML、CSS和JavaScript等Web技术来构建原生移动应用程序。它们通过将Web内容...

    LokiJS:javascript可嵌入的内存数据库

    会话存储) 性能关键型应用cordova / phonegap移动应用程序,您可以在其中利用javascript的功能并避免与本机数据库进行交互数据集加载到浏览器页面中并在工作会话结束时进行同步节点Webkit桌面应用程序nativescript...

    DanaKu.zip

    主要是针对原生和h5 js的交互,最原始的方式进行交互,这种方式也是为了后来的跨平台webjavascritbridge 和cordova 做了铺垫

    LDJSBridge_Android:Android平台零修改集成到产品的JSBridge简化框架

    LDJSBridge_Android =============== LDJSBridge_Android的核心目标是完成在Android客户端中WAP页面和客户端...本LDJSBridge_IOS是基于Phonegap的Cordova引擎的基础上简化而来, Android平台Webview和JS的交互方式共有

Global site tag (gtag.js) - Google Analytics