You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2012/09/18 19:52:50 UTC

[4/14] android commit: Optimize encoding of PluginResults within NativeToJsMessageQueue.

Optimize encoding of PluginResults within NativeToJsMessageQueue.

Also included in this refactoring:
 -Better use of StringBuilder when encoding messages
 -Defers actual encoding of messages until they are popped.
 -Add a pause/unpause of message queue so that all pending messages will
 be sent to JS in order at the end of a PROMPT or JS_OBJECT context.
 Before they may not have been sent in order.
 -Tweaked LOAD_URL so that it always happens on the UI thread (avoids
 log warning).


Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/commit/365edcad
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/tree/365edcad
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/diff/365edcad

Branch: refs/heads/master
Commit: 365edcad168550a845a8543b2b60cca4c4a05321
Parents: ae9047a
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue Sep 11 10:21:54 2012 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Sep 18 13:24:37 2012 -0400

----------------------------------------------------------------------
 framework/src/org/apache/cordova/ExposedJsApi.java |   11 +-
 .../org/apache/cordova/NativeToJsMessageQueue.java |  227 ++++++++++----
 2 files changed, 170 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/365edcad/framework/src/org/apache/cordova/ExposedJsApi.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/ExposedJsApi.java b/framework/src/org/apache/cordova/ExposedJsApi.java
index 461f320..daef571 100755
--- a/framework/src/org/apache/cordova/ExposedJsApi.java
+++ b/framework/src/org/apache/cordova/ExposedJsApi.java
@@ -38,9 +38,14 @@ import org.json.JSONException;
     }
 
     public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
-        pluginManager.exec(service, action, callbackId, arguments, true /* async */);
-        // TODO(agrieve): Should this use popAll() instead?
-        return jsMessageQueue.popAndEncode();
+        jsMessageQueue.setPaused(true);
+        try {
+            pluginManager.exec(service, action, callbackId, arguments, true /* async */);
+            // TODO(agrieve): Should this use popAll() instead?
+            return jsMessageQueue.popAndEncode();
+        } finally {
+            jsMessageQueue.setPaused(false);
+        }
     }
     
     public void setNativeToJsBridgeMode(int value) {

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/365edcad/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
index 08cadd3..779926d 100755
--- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
+++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
@@ -19,9 +19,7 @@
 package org.apache.cordova;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.LinkedList;
 
 import org.apache.cordova.api.CordovaInterface;
@@ -55,7 +53,7 @@ public class NativeToJsMessageQueue {
     /**
      * The list of JavaScript statements to be sent to JavaScript.
      */
-    private final LinkedList<String> queue = new LinkedList<String>();
+    private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
 
     /**
      * The array of listeners that can be used to send messages to JS.
@@ -89,7 +87,7 @@ public class NativeToJsMessageQueue {
                 synchronized (this) {
                     activeListenerIndex = value;
                     BridgeMode activeListener = registeredListeners[value];
-                    if (!queue.isEmpty() && activeListener != null) {
+                    if (!paused && !queue.isEmpty() && activeListener != null) {
                         activeListener.onNativeToJsMessageAvailable();
                     }
                 }
@@ -107,16 +105,26 @@ public class NativeToJsMessageQueue {
         }
     }
 
+    private int calculatePackedMessageLength(JsMessage message) {
+        int messageLen = message.calculateEncodedLength();
+        String messageLenStr = String.valueOf(messageLen);
+        return messageLenStr.length() + messageLen + 1;        
+    }
+    
+    private void packMessage(JsMessage message, StringBuilder sb) {
+        sb.append(message.calculateEncodedLength())
+          .append(' ');
+        message.encodeAsMessage(sb);
+    }
+    
     public String popAndEncode() {
         synchronized (this) {
             if (queue.isEmpty()) {
                 return null;
             }
-            String message = queue.removeFirst();
-            StringBuffer sb = new StringBuffer(message.length() + 8)
-                .append(message.length())
-                .append(' ')
-                .append(message);
+            JsMessage message = queue.removeFirst();
+            StringBuilder sb = new StringBuilder(calculatePackedMessageLength(message));
+            packMessage(message, sb);
             return sb.toString();
         }        
     }
@@ -131,16 +139,14 @@ public class NativeToJsMessageQueue {
             if (queue.isEmpty()) {
                 return null;
             }
-            int totalMessageLen = 0;
-            for (String message : queue) {
-                totalMessageLen += message.length();
+            int totalPayloadLen = 0;
+            for (JsMessage message : queue) {
+                totalPayloadLen += calculatePackedMessageLength(message);
             }
 
-            StringBuffer sb = new StringBuffer(totalMessageLen + 8 * queue.size());
-            for (String message : queue) {
-                sb.append(message.length())
-                  .append(' ')
-                  .append(message);
+            StringBuilder sb = new StringBuilder(totalPayloadLen);
+            for (JsMessage message : queue) {
+                packMessage(message, sb);
             }
             queue.clear();
             return sb.toString();
@@ -148,69 +154,61 @@ public class NativeToJsMessageQueue {
     }
     
     private String popAllAndEncodeAsJs() {
-        String ret = popAllAndEncode();
-        if (ret != null) {
-            ret = "cordova.require('cordova/exec').processMessages(\"" + JSONObject.quote(ret) + "\")";
-        }
-        return ret;
-    }
-
-    private String encodePluginResult(PluginResult result, String callbackId) {
-        int status = result.getStatus();
-        boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
-        boolean resultOk = status == PluginResult.Status.OK.ordinal();
-        boolean keepCallback = result.getKeepCallback();
-        if (noResult && keepCallback) {
-            return null;
-        }
-
-        StringBuilder sb = new StringBuilder(result.getMessage().length() + 50);
-        sb.append((noResult || resultOk) ? 'S' : 'F')
-          .append(keepCallback ? '1' : '0')
-          .append(status)
-          .append(' ')
-          .append(callbackId)
-          .append(' ');
-        switch (result.getMessageType()) {
-            case PluginResult.MESSAGE_TYPE_BOOLEAN:
-                sb.append(result.getMessage().charAt(0)); // t or f.
-                break;
-            case PluginResult.MESSAGE_TYPE_NUMBER: // n
-                sb.append('n')
-                  .append(result.getMessage());
-                break;
-            case PluginResult.MESSAGE_TYPE_STRING: // s
-                sb.append('s')
-                  .append(result.getStrMessage());
-                break;
-            case PluginResult.MESSAGE_TYPE_JSON:
-            default:
-                sb.append(result.getMessage()); // [ or {
+        synchronized (this) {
+            int length = queue.size();
+            if (length == 0) {
+                return null;
+            }
+            int totalPayloadLen = 16 * length; // accounts for try & finally.
+            for (JsMessage message : queue) {
+                totalPayloadLen += message.calculateEncodedLength(); // overestimate.
+            }
+            StringBuilder sb = new StringBuilder(totalPayloadLen);
+            // Wrap each statement in a try/finally so that if one throws it does 
+            // not affect the next.
+            int i = 0;
+            for (JsMessage message : queue) {
+                if (++i == length) {
+                    message.encodeAsJsMessage(sb);
+                } else {
+                    sb.append("try{");
+                    message.encodeAsJsMessage(sb);
+                    sb.append("}finally{");
+                }
+            }
+            for ( i = 1; i < length; ++i) {
+                sb.append('}');
+            }
+            queue.clear();
+            return sb.toString();
         }
-        return sb.toString();
-    }
+    }   
 
     /**
      * Add a JavaScript statement to the list.
      */
     public void addJavaScript(String statement) {
-        enqueueMessage("J" + statement);
+        enqueueMessage(new JsMessage(statement));
     }
 
     /**
      * Add a JavaScript statement to the list.
      */
     public void addPluginResult(PluginResult result, String callbackId) {
-        String message = encodePluginResult(result, callbackId);
-        if (message != null) {
-            enqueueMessage(message);
+        // Don't send anything if there is no result and there is no need to
+        // clear the callbacks.
+        boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
+        boolean keepCallback = result.getKeepCallback();
+        if (noResult && keepCallback) {
+            return;
         }
+        enqueueMessage(new JsMessage(result, callbackId));
     }
     
-    private void enqueueMessage(String encodedMessage) {
+    private void enqueueMessage(JsMessage message) {
         synchronized (this) {
-            queue.add(encodedMessage);
-            if (registeredListeners[activeListenerIndex] != null) {
+            queue.add(message);
+            if (!paused && registeredListeners[activeListenerIndex] != null) {
                 registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
             }
         }        
@@ -251,8 +249,17 @@ public class NativeToJsMessageQueue {
     
     /** Uses webView.loadUrl("javascript:") to execute messages. */
     private class LoadUrlBridgeMode implements BridgeMode {
+        final Runnable runnable = new Runnable() {
+            public void run() {
+                String js = popAllAndEncodeAsJs();
+                if (js != null) {
+                    webView.loadUrlNow("javascript:" + js);
+                }
+            }
+        };
+        
         public void onNativeToJsMessageAvailable() {
-            webView.loadUrlNow("javascript:" + popAllAndEncodeAsJs());
+            cordova.getActivity().runOnUiThread(runnable);
         }
     }
 
@@ -336,4 +343,94 @@ public class NativeToJsMessageQueue {
         	}
         }
     }    
+    private static class JsMessage {
+        final String jsPayloadOrCallbackId;
+        final PluginResult pluginResult;
+        JsMessage(String js) {
+            jsPayloadOrCallbackId = js;
+            pluginResult = null;
+        }
+        JsMessage(PluginResult pluginResult, String callbackId) {
+            jsPayloadOrCallbackId = callbackId;
+            this.pluginResult = pluginResult;
+        }
+        
+        int calculateEncodedLength() {
+            if (pluginResult == null) {
+                return jsPayloadOrCallbackId.length() + 1;
+            }
+            int statusLen = String.valueOf(pluginResult.getStatus()).length();
+            int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
+            switch (pluginResult.getMessageType()) {
+                case PluginResult.MESSAGE_TYPE_BOOLEAN:
+                    ret += 1;
+                    break;
+                case PluginResult.MESSAGE_TYPE_NUMBER: // n
+                    ret += 1 + pluginResult.getMessage().length();
+                    break;
+                case PluginResult.MESSAGE_TYPE_STRING: // s
+                    ret += 1 + pluginResult.getStrMessage().length();
+                    break;
+                case PluginResult.MESSAGE_TYPE_JSON:
+                default:
+                    ret += pluginResult.getMessage().length();
+            }
+            return ret;
+        }
+        
+        void encodeAsMessage(StringBuilder sb) {
+            if (pluginResult == null) {
+                sb.append('J')
+                  .append(jsPayloadOrCallbackId);
+                return;
+            }
+            int status = pluginResult.getStatus();
+            boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
+            boolean resultOk = status == PluginResult.Status.OK.ordinal();
+            boolean keepCallback = pluginResult.getKeepCallback();
+
+            sb.append((noResult || resultOk) ? 'S' : 'F')
+              .append(keepCallback ? '1' : '0')
+              .append(status)
+              .append(' ')
+              .append(jsPayloadOrCallbackId)
+              .append(' ');
+            switch (pluginResult.getMessageType()) {
+                case PluginResult.MESSAGE_TYPE_BOOLEAN:
+                    sb.append(pluginResult.getMessage().charAt(0)); // t or f.
+                    break;
+                case PluginResult.MESSAGE_TYPE_NUMBER: // n
+                    sb.append('n')
+                      .append(pluginResult.getMessage());
+                    break;
+                case PluginResult.MESSAGE_TYPE_STRING: // s
+                    sb.append('s')
+                      .append(pluginResult.getStrMessage());
+                    break;
+                case PluginResult.MESSAGE_TYPE_JSON:
+                default:
+                    sb.append(pluginResult.getMessage()); // [ or {
+            }
+        }
+        
+        void encodeAsJsMessage(StringBuilder sb) {
+            if (pluginResult == null) {
+                sb.append(jsPayloadOrCallbackId);
+            } else {
+                int status = pluginResult.getStatus();
+                boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
+                sb.append("cordova.callbackFromNative('")
+                  .append(jsPayloadOrCallbackId)
+                  .append("',")
+                  .append(success)
+                  .append(",")
+                  .append(status)
+                  .append(",")
+                  .append(pluginResult.getMessage())
+                  .append(",")
+                  .append(pluginResult.getKeepCallback())
+                  .append(");");
+            }
+        }
+    }
 }