Cordova – in-depth analysis – Communication



Cordova – in-depth analysis – Communication

0 1


cordova-intro

a tnternal technology sharing for cordova

On Github zfkun / cordova-intro

Cordova

in-depth analysis

Created by 范坤 / @zfkun

Communication

Native Bridge

Init in Android

// CordovaWebView.java
pluginManager = new PluginManager(this, this.cordova);
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
exposeJsInterface();
// exposeJsInterface()
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
// similar in ActionScript
ExternalInterface.addCallback("say", this._say);

JS to Native (Android)

Prompt (2.3 simulator, 3.2-)

'cordova/android/promptbasednativeapi'

JS to Native (Android)

JS Object (addJavascriptInterface)

get NativeApi

nativeApiProvider = require('cordova/android/nativeapiprovider');
nativeApiProvider.get()

NativeApi Provider

var nativeApi = this._cordovaNative
    || require('cordova/android/promptbasednativeapi');

JS to Native (Android)

Location Change (CordovaWebViewClient)

This mode is currently for benchmarking purposes only

// NativeToJsMessageQueue.java
ENABLE_LOCATION_CHANGE_EXEC_MODE = true;
window.location = 'http://cdv_exec/'
                + service + '#' + action + '#'
                + callbackId + '#' + argsJson;

Native to JS (Android)

onJsPrompt

boolean reqOk = false;
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
    reqOk = true;
}
// Synchronous: 'gap:*', 'gap_poll:', 'gap_init:'
String r = this.appView.exposedJsApi.exec(..); // -> PluginManager.exec()
result.confirm(r == null ? "" : r); // JsPromptResult

// Asynchronous: 'gap_bridge_mode:'
result.confirm(''); // JsPromptResult

Native to JS (Android)

// PluginManager.java
exec(..) -> app.sendPluginResult(..) -> CordovaWebView
// CordovaWebView.java
this.jsMessageQueue.addPluginResult(..) -> NativeToJsMessageQueue
// NativeToJsMessageQueue.java
[?BridgeMode].onNativeToJsMessageAvailable(..)

// make encoded JS
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
// OnlineEventsBridgeMode
webView.setNetworkAvailable(online);

// LoadUrlBridgeMode
webView.loadUrlNow("javascript:" + js);

// PrivateApiBridgeMode (Android 3.2.4+)
sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
sendMessageMethod.invoke(webViewCore, execJsMessage);
// This is called from the NativeToJsMessageQueue.java.
androidExec.processMessages = function(messages) {..};

Native to JS (Android)

OnlineEventsBridge - 'cordova/exec'

window.addEventListener('online', pollOnceFromOnlineEvent, false);
window.addEventListener('offline', pollOnceFromOnlineEvent, false);

function pollOnceFromOnlineEvent() {
    pollOnce(true);
}

function pollOnce(opt_fromOnlineEvent) {
    var msg = nativeApiProvider.get().retrieveJsMessages(!!opt_fromOnlineEvent);
    androidExec.processMessages(msg);
}

function pollingTimerFunc() {
    if (pollEnabled) {
        pollOnce();
        setTimeout(pollingTimerFunc, 50);
    }
}

Native to JS (Android)

JS to Native (iOS)

iFrame

nav (fastest)

var command = [callbackId, service, action, actionArgs];
commandQueue.push(JSON.stringify(command));

execIframe.src = "gap://ready";

hash

execHashIframe.contentWindow.location.hash = hashValue;

XMLHttpRequest (5.x)

execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());

Native to JS (iOS)

ViewController

// shouldStartLoadWithRequest:navigationType:
if ([[url scheme] isEqualToString:@"gap"]) {
    [_commandQueue fetchCommandsFromJs];
    [_commandQueue executePending];
    return NO;
}

CommandQueue

- (void)fetchCommandsFromJs
{
NSString* queuedCommandsJSON = [_viewController.webView
    stringByEvaluatingJavaScriptFromString:
        @"cordova.require('cordova/exec').nativeFetchMessages()"];
}

Plugin

- (NSString*)writeJavascript:(NSString*)javascript
{
    return [self.webView stringByEvaluatingJavaScriptFromString:javascript];
}

JS to Native (Windows Phone 7/8)

external.Notify()

var command = service + "/" + action + "/" + callbackId
            + "/" + JSON.stringify(args);
window.external.Notify(command);

ScriptNotify

void CordovaBrowser_ScriptNotify(object sender, NotifyEventArgs e)
{
    ..
}

Native to JS (Windows Phone 7/8)

NativeExecution

// NativeExecution.cs
private void InvokeScriptCallback(ScriptCallback script)
{
    this.webBrowser.InvokeScript(script.ScriptName, script.Args);
}

WebBrowser

// WebBrowser.cs
public Object InvokeScript(String scriptName, params Object[] args)
{
    ..    
}

JS to Native (Blackberry 10)

XMLHttpRequest

Sync / Async

function RemoteFunctionCall(functionUri) {
    ..
    function composeUri() {
        return "http://localhost:8472/" + functionUri;
    }
    ..
}
function createXhrRequest(uri, isAsync) {
    ..
    request.open("POST", uri, isAsync);
    request.setRequestHeader("Content-Type", "application/json");
    ..
}

response = JSON.parse(decodeURIComponent(request.responseText) || "null");

JS to Native (Blackberry 10)

Native

webview.js

var requestObj =  networkResourceRequested.createHandler(_webviewObj);
_webviewObj.onNetworkResourceRequested = requestObj.networkResourceRequestedHandler;

networkResourceRequested.js

module.exports = {
    createHandler: function (webview) {
        return new NetworkResourceRequestHandler(webview);
    }
};

Native to JS (Blackberry 10)

executeJavaScript

this.webview.executeJavaScript('');

PluginResult

var executeString = "cordova.callbackFromNative('" + callbackId + "', " +
    !!success + ", " + status + ", [" + data + "], " + !!keepCallback + ");";
env.webview.executeJavaScript(executeString);

Android vs iOS

  • JS to Native: iOS Asynchronous, Android Async + Sync
  • Native to JS: iOS actively, Android passive

Debug

  • Browser
  • Native
  • Remote

Browser Debug

no Cordova API

  • Standard Browser
  • Browser With Plugin

Standard Browser

Chrome / Safari / Firefox

support cross-domain ajax

open -a "Google Chrome.app" --args --disable-web-security --allow-file-access-from-files

web inspector tools

F12 / Alt + Command + J

Browser With Plugin

Ripple Emulate

Apache Ripple™

Install

npm install -g ripple-emulator

Useage

ripple emulate --path path/to/app
ripple emulate --remote http://mytest.com

Ripple Emulate

Intel XDK (appMobi)

Native Debug

  • emulator
  • phone / pad

Android

Logcat

adb logcat
adb logcat -s CordovaWebViewClient

Android

Monitor

DDMS (Dalvik Debug Monitor Service)

{Andriod SDK Home}/tools/monitor

iOS

Emulator + Safari Web Inspector

iOS

XCode (Code Sign)

Remote Debug

Debug Target(phone) <=> Debug Server <=> Debug Client(PC)

Weinre

WEb INspector REmote ['waɪnəri]

1. install

sudo npm -g install weinre

2. start

weinre --boundHost 192.168.1.2 --httpPort 8888

3. inject script into index.html

//192.168.1.2:8888/target/target-script-min.js

4. launch your app

5. debugging with browser

http://192.168.1.2:8888

PhoneGap

http://debug.phonegap.com

1. setup guid

bd

2. inject script into index.html

<script src="//debug.phonegap.com/target/target-script-min.js#bd"></script>

3. launch your app

4. debugging with browser

http://debug.phonegap.com/client/#bd

iOS Private API

just for iOS 5.0 && 5.1

+ edite AppDelegate.m

[NSClassFromString(@"WebView") _enableRemoteInspector];

+ iWebInspector

Web debugging tool for iOS Simuator

云窗调试器

基于新浪移动云的移动应用

Android 2.2+

PhoneGap 1.8-

iOS (JailBreak)

支持版本不详

Adobe Edge Inspect CC

Preview & inspect web designs on devices

  • Synchronized browsing and refreshing
  • Remote inspection
  • Screenshots
  • Extensibility with Edge Inspect API

Online

via `index.html` from local server

1. set `content` in config.xml

<content src="http://192.168.1.2:8000/index.html"></content>

2. build & install & launch

cordova run *** (platform)

3. debuging with Safari

but, doen't work (only iOS emulator)...

3. debuging with Weinre

Reference Links

Q & A

Thanks!