Always send as many messages native->JS in one payload as possible.

This commit is contained in:
Andrew Grieve 2012-09-11 14:35:39 -04:00
parent 4c9a571106
commit f53161d6f5
3 changed files with 45 additions and 35 deletions

View File

@ -214,7 +214,6 @@ public class CallbackServer implements Runnable {
synchronized (this) { synchronized (this) {
while (this.active) { while (this.active) {
if (jsMessageQueue != null) { if (jsMessageQueue != null) {
// TODO(agrieve): Should this use popAll() instead?
payload = jsMessageQueue.popAndEncode(); payload = jsMessageQueue.popAndEncode();
if (payload != null) { if (payload != null) {
break; break;

View File

@ -41,7 +41,6 @@ import org.json.JSONException;
jsMessageQueue.setPaused(true); jsMessageQueue.setPaused(true);
try { try {
pluginManager.exec(service, action, callbackId, arguments, true /* async */); pluginManager.exec(service, action, callbackId, arguments, true /* async */);
// TODO(agrieve): Should this use popAll() instead?
return jsMessageQueue.popAndEncode(); return jsMessageQueue.popAndEncode();
} finally { } finally {
jsMessageQueue.setPaused(false); jsMessageQueue.setPaused(false);
@ -53,7 +52,6 @@ import org.json.JSONException;
} }
public String retrieveJsMessages() { public String retrieveJsMessages() {
// TODO(agrieve): Should this use popAll() instead?
return jsMessageQueue.popAndEncode(); return jsMessageQueue.popAndEncode();
} }
} }

View File

@ -24,7 +24,6 @@ import java.util.LinkedList;
import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult; import org.apache.cordova.api.PluginResult;
import org.json.JSONObject;
import android.os.Message; import android.os.Message;
import android.util.Log; import android.util.Log;
@ -43,6 +42,9 @@ public class NativeToJsMessageQueue {
// JS instead of the custom format (useful for benchmarking). // JS instead of the custom format (useful for benchmarking).
private static final boolean FORCE_ENCODE_USING_EVAL = false; private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
private static final int MAX_PAYLOAD_SIZE = 50 * 1024;
/** /**
* The index into registeredListeners to treat as active. * The index into registeredListeners to treat as active.
*/ */
@ -121,58 +123,67 @@ public class NativeToJsMessageQueue {
message.encodeAsMessage(sb); message.encodeAsMessage(sb);
} }
/**
* Combines and returns queued messages combined into a single string.
* Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
* Returns null if the queue is empty.
*/
public String popAndEncode() { public String popAndEncode() {
synchronized (this) { synchronized (this) {
if (queue.isEmpty()) { if (queue.isEmpty()) {
return null; return null;
} }
int totalPayloadLen = 0;
int numMessagesToSend = 0;
for (JsMessage message : queue) {
int messageSize = calculatePackedMessageLength(message);
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE) {
break;
}
totalPayloadLen += messageSize;
numMessagesToSend += 1;
}
StringBuilder sb = new StringBuilder(totalPayloadLen);
for (int i = 0; i < numMessagesToSend; ++i) {
JsMessage message = queue.removeFirst(); JsMessage message = queue.removeFirst();
StringBuilder sb = new StringBuilder(calculatePackedMessageLength(message));
packMessage(message, sb); packMessage(message, sb);
}
if (!queue.isEmpty()) {
// Attach a char to indicate that there are more messages pending.
sb.append('*');
}
return sb.toString(); return sb.toString();
} }
} }
/** /**
* Combines and returns all messages combined into a single string. * Same as popAndEncode(), except encodes in a form that can be executed as JS.
* Clears the queue.
* Returns null if the queue is empty.
*/ */
public String popAllAndEncode() { private String popAndEncodeAsJs() {
synchronized (this) {
if (queue.isEmpty()) {
return null;
}
int totalPayloadLen = 0;
for (JsMessage message : queue) {
totalPayloadLen += calculatePackedMessageLength(message);
}
StringBuilder sb = new StringBuilder(totalPayloadLen);
for (JsMessage message : queue) {
packMessage(message, sb);
}
queue.clear();
return sb.toString();
}
}
private String popAllAndEncodeAsJs() {
synchronized (this) { synchronized (this) {
int length = queue.size(); int length = queue.size();
if (length == 0) { if (length == 0) {
return null; return null;
} }
int totalPayloadLen = 16 * length; // accounts for try & finally. int totalPayloadLen = 0;
int numMessagesToSend = 0;
for (JsMessage message : queue) { for (JsMessage message : queue) {
totalPayloadLen += message.calculateEncodedLength(); // overestimate. int messageSize = message.calculateEncodedLength() + 50; // overestimate.
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE) {
break;
} }
StringBuilder sb = new StringBuilder(totalPayloadLen); totalPayloadLen += messageSize;
numMessagesToSend += 1;
}
boolean willSendAllMessages = numMessagesToSend == queue.size();
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
// Wrap each statement in a try/finally so that if one throws it does // Wrap each statement in a try/finally so that if one throws it does
// not affect the next. // not affect the next.
int i = 0; for (int i = 0; i < numMessagesToSend; ++i) {
for (JsMessage message : queue) { JsMessage message = queue.removeFirst();
if (++i == length) { if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
message.encodeAsJsMessage(sb); message.encodeAsJsMessage(sb);
} else { } else {
sb.append("try{"); sb.append("try{");
@ -180,10 +191,12 @@ public class NativeToJsMessageQueue {
sb.append("}finally{"); sb.append("}finally{");
} }
} }
for ( i = 1; i < length; ++i) { if (!willSendAllMessages) {
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
}
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
sb.append('}'); sb.append('}');
} }
queue.clear();
return sb.toString(); return sb.toString();
} }
} }
@ -262,7 +275,7 @@ public class NativeToJsMessageQueue {
private class LoadUrlBridgeMode implements BridgeMode { private class LoadUrlBridgeMode implements BridgeMode {
final Runnable runnable = new Runnable() { final Runnable runnable = new Runnable() {
public void run() { public void run() {
String js = popAllAndEncodeAsJs(); String js = popAndEncodeAsJs();
if (js != null) { if (js != null) {
webView.loadUrlNow("javascript:" + js); webView.loadUrlNow("javascript:" + js);
} }
@ -344,7 +357,7 @@ public class NativeToJsMessageQueue {
} }
// webViewCore is lazily initialized, and so may not be available right away. // webViewCore is lazily initialized, and so may not be available right away.
if (sendMessageMethod != null) { if (sendMessageMethod != null) {
String js = popAllAndEncodeAsJs(); String js = popAndEncodeAsJs();
Message execJsMessage = Message.obtain(null, EXECUTE_JS, js); Message execJsMessage = Message.obtain(null, EXECUTE_JS, js);
try { try {
sendMessageMethod.invoke(webViewCore, execJsMessage); sendMessageMethod.invoke(webViewCore, execJsMessage);