mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-22 00:32:55 +08:00
Refactor Native->JS messaging logic into its own class.
This will make it easy to add more modes. This also adds logic to set the move via a prompt() from JS.
This commit is contained in:
parent
a9a5284a67
commit
5e3e9ddb8e
@ -59,8 +59,9 @@ public class CallbackServer implements Runnable {
|
|||||||
private ServerSocket waitSocket;
|
private ServerSocket waitSocket;
|
||||||
/**
|
/**
|
||||||
* The list of JavaScript statements to be sent to JavaScript.
|
* The list of JavaScript statements to be sent to JavaScript.
|
||||||
|
* This can be null when there are no messages available.
|
||||||
*/
|
*/
|
||||||
private LinkedList<String> javascript;
|
private NativeToJsMessageQueue jsMessageQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The port to listen on.
|
* The port to listen on.
|
||||||
@ -77,10 +78,6 @@ public class CallbackServer implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private boolean active;
|
private boolean active;
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the JavaScript statements list is empty
|
|
||||||
*/
|
|
||||||
private boolean empty;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that polling should be used instead of XHR.
|
* Indicates that polling should be used instead of XHR.
|
||||||
@ -98,9 +95,7 @@ public class CallbackServer implements Runnable {
|
|||||||
public CallbackServer() {
|
public CallbackServer() {
|
||||||
//Log.d(LOG_TAG, "CallbackServer()");
|
//Log.d(LOG_TAG, "CallbackServer()");
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.empty = true;
|
|
||||||
this.port = 0;
|
this.port = 0;
|
||||||
this.javascript = new LinkedList<String>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,10 +108,8 @@ public class CallbackServer implements Runnable {
|
|||||||
*/
|
*/
|
||||||
public void init(String url) {
|
public void init(String url) {
|
||||||
//System.out.println("CallbackServer.start("+url+")");
|
//System.out.println("CallbackServer.start("+url+")");
|
||||||
this.active = false;
|
this.stopServer();
|
||||||
this.empty = true;
|
|
||||||
this.port = 0;
|
this.port = 0;
|
||||||
this.javascript = new LinkedList<String>();
|
|
||||||
|
|
||||||
// Determine if XHR or polling is to be used
|
// Determine if XHR or polling is to be used
|
||||||
if ((url != null) && !url.startsWith("file://")) {
|
if ((url != null) && !url.startsWith("file://")) {
|
||||||
@ -133,16 +126,6 @@ public class CallbackServer implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-init when loading a new HTML page into webview.
|
|
||||||
*
|
|
||||||
* @param url The URL of the Cordova app being loaded
|
|
||||||
*/
|
|
||||||
public void reinit(String url) {
|
|
||||||
this.stopServer();
|
|
||||||
this.init(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return if polling is being used instead of XHR.
|
* Return if polling is being used instead of XHR.
|
||||||
* @return
|
* @return
|
||||||
@ -224,11 +207,19 @@ public class CallbackServer implements Runnable {
|
|||||||
// Must have security token
|
// Must have security token
|
||||||
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
|
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
|
||||||
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
|
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
|
||||||
|
String js = null;
|
||||||
|
|
||||||
// Wait until there is some data to send, or send empty data every 10 sec
|
// Wait until there is some data to send, or send empty data every 10 sec
|
||||||
// to prevent XHR timeout on the client
|
// to prevent XHR timeout on the client
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
while (this.empty) {
|
while (this.active) {
|
||||||
|
if (jsMessageQueue != null) {
|
||||||
|
// TODO(agrieve): Should this use popAll() instead?
|
||||||
|
js = jsMessageQueue.pop();
|
||||||
|
if (js != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.wait(10000); // prevent timeout from happening
|
this.wait(10000); // prevent timeout from happening
|
||||||
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
|
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
|
||||||
@ -242,17 +233,14 @@ public class CallbackServer implements Runnable {
|
|||||||
if (this.active) {
|
if (this.active) {
|
||||||
|
|
||||||
// If no data, then send 404 back to client before it times out
|
// If no data, then send 404 back to client before it times out
|
||||||
if (this.empty) {
|
if (js == null) {
|
||||||
//Log.d(LOG_TAG, "CallbackServer -- sending data 0");
|
//Log.d(LOG_TAG, "CallbackServer -- sending data 0");
|
||||||
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
|
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//Log.d(LOG_TAG, "CallbackServer -- sending item");
|
//Log.d(LOG_TAG, "CallbackServer -- sending item");
|
||||||
response = "HTTP/1.1 200 OK\r\n\r\n";
|
response = "HTTP/1.1 200 OK\r\n\r\n";
|
||||||
String js = this.getJavascript();
|
response += encode(js, "UTF-8");
|
||||||
if (js != null) {
|
|
||||||
response += encode(js, "UTF-8");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -305,46 +293,10 @@ public class CallbackServer implements Runnable {
|
|||||||
public void destroy() {
|
public void destroy() {
|
||||||
this.stopServer();
|
this.stopServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||||
* Get the number of JavaScript statements.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public int getSize() {
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
int size = this.javascript.size();
|
this.jsMessageQueue = queue;
|
||||||
return size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the next JavaScript statement and remove from list.
|
|
||||||
*
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getJavascript() {
|
|
||||||
synchronized (this) {
|
|
||||||
if (this.javascript.size() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String statement = this.javascript.remove(0);
|
|
||||||
if (this.javascript.size() == 0) {
|
|
||||||
this.empty = true;
|
|
||||||
}
|
|
||||||
return statement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a JavaScript statement to the list.
|
|
||||||
*
|
|
||||||
* @param statement
|
|
||||||
*/
|
|
||||||
public void sendJavascript(String statement) {
|
|
||||||
synchronized (this) {
|
|
||||||
this.javascript.add(statement);
|
|
||||||
this.empty = false;
|
|
||||||
this.notify();
|
this.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,9 @@ import org.apache.cordova.api.LOG;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
//import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
//import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
//import android.view.View;
|
|
||||||
import android.webkit.ConsoleMessage;
|
import android.webkit.ConsoleMessage;
|
||||||
import android.webkit.JsPromptResult;
|
import android.webkit.JsPromptResult;
|
||||||
import android.webkit.JsResult;
|
import android.webkit.JsResult;
|
||||||
@ -211,9 +208,16 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the native->JS bridge mode.
|
||||||
|
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
|
||||||
|
this.appView.jsMessageQueue.setBridgeMode(Integer.parseInt(message));
|
||||||
|
result.confirm("");
|
||||||
|
}
|
||||||
|
|
||||||
// Polling for JavaScript messages
|
// Polling for JavaScript messages
|
||||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
|
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
|
||||||
String r = this.appView.callbackServer.getJavascript();
|
// TODO(agrieve): Use popAll() here.
|
||||||
|
String r = this.appView.jsMessageQueue.pop();
|
||||||
result.confirm(r);
|
result.confirm(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ public class CordovaWebView extends WebView {
|
|||||||
|
|
||||||
private boolean handleButton = false;
|
private boolean handleButton = false;
|
||||||
|
|
||||||
|
NativeToJsMessageQueue jsMessageQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -190,14 +192,14 @@ public class CordovaWebView extends WebView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize webview.
|
* Initialize webview.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
private void setup() {
|
private void setup() {
|
||||||
|
jsMessageQueue = new NativeToJsMessageQueue(this);
|
||||||
|
|
||||||
this.setInitialScale(0);
|
this.setInitialScale(0);
|
||||||
this.setVerticalScrollBarEnabled(false);
|
this.setVerticalScrollBarEnabled(false);
|
||||||
this.requestFocusFromTouch();
|
this.requestFocusFromTouch();
|
||||||
@ -456,7 +458,7 @@ public class CordovaWebView extends WebView {
|
|||||||
*
|
*
|
||||||
* @param url
|
* @param url
|
||||||
*/
|
*/
|
||||||
private void loadUrlNow(String url) {
|
void loadUrlNow(String url) {
|
||||||
LOG.d(TAG, ">>> loadUrlNow()");
|
LOG.d(TAG, ">>> loadUrlNow()");
|
||||||
super.loadUrl(url);
|
super.loadUrl(url);
|
||||||
}
|
}
|
||||||
@ -495,9 +497,7 @@ public class CordovaWebView extends WebView {
|
|||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public void sendJavascript(String statement) {
|
public void sendJavascript(String statement) {
|
||||||
if (this.callbackServer != null) {
|
this.jsMessageQueue.add(statement);
|
||||||
this.callbackServer.sendJavascript(statement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -230,14 +230,14 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
this.doClearHistory = true;
|
this.doClearHistory = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create callback server and plugin manager
|
// Flush stale messages.
|
||||||
|
this.appView.jsMessageQueue.reset();
|
||||||
|
|
||||||
|
// Create callback server
|
||||||
if (this.appView.callbackServer == null) {
|
if (this.appView.callbackServer == null) {
|
||||||
this.appView.callbackServer = new CallbackServer();
|
this.appView.callbackServer = new CallbackServer();
|
||||||
this.appView.callbackServer.init(url);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.appView.callbackServer.reinit(url);
|
|
||||||
}
|
}
|
||||||
|
this.appView.callbackServer.init(url);
|
||||||
|
|
||||||
// Broadcast message that page has loaded
|
// Broadcast message that page has loaded
|
||||||
this.appView.postMessage("onPageStarted", url);
|
this.appView.postMessage("onPageStarted", url);
|
||||||
|
@ -714,8 +714,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
|||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public void sendJavascript(String statement) {
|
public void sendJavascript(String statement) {
|
||||||
if (this.appView != null && this.appView.callbackServer != null) {
|
if (this.appView != null) {
|
||||||
this.appView.callbackServer.sendJavascript(statement);
|
this.appView.jsMessageQueue.add(statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
163
framework/src/org/apache/cordova/NativeToJsMessageQueue.java
Executable file
163
framework/src/org/apache/cordova/NativeToJsMessageQueue.java
Executable file
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the list of messages to be sent to the WebView.
|
||||||
|
*/
|
||||||
|
public class NativeToJsMessageQueue {
|
||||||
|
private static final String LOG_TAG = "JsMessageQueue";
|
||||||
|
|
||||||
|
// This must match the default value in incubator-cordova-js/lib/android/exec.js
|
||||||
|
private static final int DEFAULT_BRIDGE_MODE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of JavaScript statements to be sent to JavaScript.
|
||||||
|
*/
|
||||||
|
private LinkedList<String> queue = new LinkedList<String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index into registeredListeners to treat as active.
|
||||||
|
*/
|
||||||
|
private int activeListenerIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The array of listeners that can be used to send messages to JS.
|
||||||
|
*/
|
||||||
|
private BridgeMode[] registeredListeners;
|
||||||
|
|
||||||
|
public NativeToJsMessageQueue(CordovaWebView webView) {
|
||||||
|
registeredListeners = new BridgeMode[2];
|
||||||
|
registeredListeners[0] = null;
|
||||||
|
registeredListeners[1] = new CallbackBridgeMode(webView);
|
||||||
|
reset();
|
||||||
|
// POLLING: 0,
|
||||||
|
// HANGING_GET: 1,
|
||||||
|
// LOAD_URL: 2,
|
||||||
|
// ONLINE_EVENT: 3,
|
||||||
|
// PRIVATE_API: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the bridge mode.
|
||||||
|
*/
|
||||||
|
public void setBridgeMode(int value) {
|
||||||
|
if (value < 0 || value >= registeredListeners.length) {
|
||||||
|
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||||
|
} else {
|
||||||
|
if (value != activeListenerIndex) {
|
||||||
|
Log.d(LOG_TAG, "Set native->JS mode to " + value);
|
||||||
|
synchronized (this) {
|
||||||
|
activeListenerIndex = value;
|
||||||
|
BridgeMode activeListener = registeredListeners[value];
|
||||||
|
if (!queue.isEmpty() && activeListener != null) {
|
||||||
|
activeListener.onNativeToJsMessageAvailable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all messages and resets to the default bridge mode.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
synchronized (this) {
|
||||||
|
queue.clear();
|
||||||
|
setBridgeMode(DEFAULT_BRIDGE_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the last statement in the queue.
|
||||||
|
* Returns null if the queue is empty.
|
||||||
|
*/
|
||||||
|
public String pop() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return queue.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines and returns all statements. Clears the queue.
|
||||||
|
* Returns null if the queue is empty.
|
||||||
|
*/
|
||||||
|
public String popAll() {
|
||||||
|
synchronized (this) {
|
||||||
|
int length = queue.size();
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
// Wrap each statement in a try/finally so that if one throws it does
|
||||||
|
// not affect the next.
|
||||||
|
int i = 0;
|
||||||
|
for (String message : queue) {
|
||||||
|
if (++i == length) {
|
||||||
|
sb.append(message);
|
||||||
|
} else {
|
||||||
|
sb.append("try{")
|
||||||
|
.append(message)
|
||||||
|
.append("}finally{");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( i = 1; i < length; ++i) {
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
queue.clear();
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a JavaScript statement to the list.
|
||||||
|
*/
|
||||||
|
public void add(String statement) {
|
||||||
|
synchronized (this) {
|
||||||
|
queue.add(statement);
|
||||||
|
if (registeredListeners[activeListenerIndex] != null) {
|
||||||
|
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface BridgeMode {
|
||||||
|
void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CallbackBridgeMode implements BridgeMode {
|
||||||
|
private CordovaWebView webView;
|
||||||
|
public CallbackBridgeMode(CordovaWebView webView) {
|
||||||
|
this.webView = webView;
|
||||||
|
}
|
||||||
|
public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||||
|
if (webView.callbackServer != null) {
|
||||||
|
webView.callbackServer.onNativeToJsMessageAvailable(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user