mirror of
https://github.com/apache/cordova-android.git
synced 2025-01-31 17:32:51 +08:00
Separate the registering of BridgeModes from NativeToJsMessageQueue
This makes the class usable no matter how a webview's bridge is implemented under-the-hood. This also deletes the PrivateApi bridge mode, which has never been a good idea to use, and which we should replace with a Lollipop "evaluateJavascript"-based bridge.
This commit is contained in:
parent
5b2fa128a4
commit
4cb64580fd
@ -113,7 +113,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
|
|||||||
|
|
||||||
// Use two-phase init so that the control will work with XML layouts.
|
// Use two-phase init so that the control will work with XML layouts.
|
||||||
@Override
|
@Override
|
||||||
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
public void init(final CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||||
Whitelist internalWhitelist, Whitelist externalWhitelist,
|
Whitelist internalWhitelist, Whitelist externalWhitelist,
|
||||||
CordovaPreferences preferences) {
|
CordovaPreferences preferences) {
|
||||||
if (this.cordova != null) {
|
if (this.cordova != null) {
|
||||||
@ -127,9 +127,23 @@ public class AndroidWebView extends WebView implements CordovaWebView {
|
|||||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||||
cookieManager = new AndroidCookieManager(this);
|
cookieManager = new AndroidCookieManager(this);
|
||||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
||||||
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName());
|
NativeToJsMessageQueue nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(this, cordova));
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||||
|
@Override
|
||||||
|
public void setNetworkAvailable(boolean value) {
|
||||||
|
AndroidWebView.this.setNetworkAvailable(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runOnUiThread(Runnable r) {
|
||||||
|
cordova.getActivity().runOnUiThread(r);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue, this.cordova.getActivity().getPackageName());
|
||||||
initWebViewSettings();
|
initWebViewSettings();
|
||||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
pluginManager.addService(CoreAndroid.PLUGIN_NAME, CoreAndroid.class.getCanonicalName());
|
||||||
pluginManager.init();
|
pluginManager.init();
|
||||||
|
|
||||||
if (this.viewClient == null) {
|
if (this.viewClient == null) {
|
||||||
|
@ -18,16 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.util.ArrayList;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import org.apache.cordova.CordovaInterface;
|
|
||||||
import org.apache.cordova.PluginResult;
|
|
||||||
|
|
||||||
import android.os.Message;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the list of messages to be sent to the WebView.
|
* Holds the list of messages to be sent to the WebView.
|
||||||
@ -63,7 +57,7 @@ public class NativeToJsMessageQueue {
|
|||||||
/**
|
/**
|
||||||
* The array of listeners that can be used to send messages to JS.
|
* The array of listeners that can be used to send messages to JS.
|
||||||
*/
|
*/
|
||||||
private final BridgeMode[] registeredListeners;
|
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When null, the bridge is disabled. This occurs during page transitions.
|
* When null, the bridge is disabled. This occurs during page transitions.
|
||||||
@ -72,32 +66,26 @@ public class NativeToJsMessageQueue {
|
|||||||
*/
|
*/
|
||||||
private BridgeMode activeBridgeMode;
|
private BridgeMode activeBridgeMode;
|
||||||
|
|
||||||
private final CordovaInterface cordova;
|
public void addBridgeMode(BridgeMode bridgeMode) {
|
||||||
private final CordovaWebView webView;
|
bridgeModes.add(bridgeMode);
|
||||||
|
|
||||||
public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) {
|
|
||||||
this.cordova = cordova;
|
|
||||||
this.webView = webView;
|
|
||||||
registeredListeners = new BridgeMode[4];
|
|
||||||
registeredListeners[0] = new PollingBridgeMode();
|
|
||||||
registeredListeners[1] = new LoadUrlBridgeMode();
|
|
||||||
registeredListeners[2] = new OnlineEventsBridgeMode();
|
|
||||||
registeredListeners[3] = new PrivateApiBridgeMode();
|
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBridgeEnabled() {
|
public boolean isBridgeEnabled() {
|
||||||
return activeBridgeMode != null;
|
return activeBridgeMode != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return queue.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the bridge mode.
|
* Changes the bridge mode.
|
||||||
*/
|
*/
|
||||||
public void setBridgeMode(int value) {
|
public void setBridgeMode(int value) {
|
||||||
if (value < -1 || value >= registeredListeners.length) {
|
if (value < -1 || value >= bridgeModes.size()) {
|
||||||
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||||
} else {
|
} else {
|
||||||
BridgeMode newMode = value < 0 ? null : registeredListeners[value];
|
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
|
||||||
if (newMode != activeBridgeMode) {
|
if (newMode != activeBridgeMode) {
|
||||||
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
@ -105,7 +93,7 @@ public class NativeToJsMessageQueue {
|
|||||||
if (newMode != null) {
|
if (newMode != null) {
|
||||||
newMode.reset();
|
newMode.reset();
|
||||||
if (!paused && !queue.isEmpty()) {
|
if (!paused && !queue.isEmpty()) {
|
||||||
newMode.onNativeToJsMessageAvailable();
|
newMode.onNativeToJsMessageAvailable(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,7 +134,7 @@ public class NativeToJsMessageQueue {
|
|||||||
if (activeBridgeMode == null) {
|
if (activeBridgeMode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
activeBridgeMode.notifyOfFlush(fromOnlineEvent);
|
activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
|
||||||
if (queue.isEmpty()) {
|
if (queue.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -179,7 +167,7 @@ public class NativeToJsMessageQueue {
|
|||||||
/**
|
/**
|
||||||
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
|
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
|
||||||
*/
|
*/
|
||||||
private String popAndEncodeAsJs() {
|
public String popAndEncodeAsJs() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
int length = queue.size();
|
int length = queue.size();
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
@ -260,7 +248,7 @@ public class NativeToJsMessageQueue {
|
|||||||
}
|
}
|
||||||
queue.add(message);
|
queue.add(message);
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
activeBridgeMode.onNativeToJsMessageAvailable();
|
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,133 +263,94 @@ public class NativeToJsMessageQueue {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (!queue.isEmpty() && activeBridgeMode != null) {
|
if (!queue.isEmpty() && activeBridgeMode != null) {
|
||||||
activeBridgeMode.onNativeToJsMessageAvailable();
|
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class BridgeMode {
|
public static abstract class BridgeMode {
|
||||||
abstract void onNativeToJsMessageAvailable();
|
abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||||
void notifyOfFlush(boolean fromOnlineEvent) {}
|
void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||||
void reset() {}
|
void reset() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Uses JS polls for messages on a timer.. */
|
/** Uses JS polls for messages on a timer.. */
|
||||||
private class PollingBridgeMode extends BridgeMode {
|
public static class NoOpBridgeMode extends BridgeMode {
|
||||||
@Override void onNativeToJsMessageAvailable() {
|
@Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||||
private class LoadUrlBridgeMode extends BridgeMode {
|
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||||
final Runnable runnable = new Runnable() {
|
private final CordovaWebView webView;
|
||||||
public void run() {
|
private final CordovaInterface cordova;
|
||||||
String js = popAndEncodeAsJs();
|
|
||||||
if (js != null) {
|
|
||||||
webView.loadUrlIntoView("javascript:" + js, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override void onNativeToJsMessageAvailable() {
|
public LoadUrlBridgeMode(CordovaWebView webView, CordovaInterface cordova) {
|
||||||
cordova.getActivity().runOnUiThread(runnable);
|
this.webView = webView;
|
||||||
|
this.cordova = cordova;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
String js = queue.popAndEncodeAsJs();
|
||||||
|
if (js != null) {
|
||||||
|
webView.loadUrl("javascript:" + js);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Uses online/offline events to tell the JS when to poll for messages. */
|
/** Uses online/offline events to tell the JS when to poll for messages. */
|
||||||
private class OnlineEventsBridgeMode extends BridgeMode {
|
public static class OnlineEventsBridgeMode extends BridgeMode {
|
||||||
|
private final OnlineEventsBridgeModeDelegate delegate;
|
||||||
private boolean online;
|
private boolean online;
|
||||||
private boolean ignoreNextFlush;
|
private boolean ignoreNextFlush;
|
||||||
|
|
||||||
final Runnable toggleNetworkRunnable = new Runnable() {
|
public interface OnlineEventsBridgeModeDelegate {
|
||||||
public void run() {
|
void setNetworkAvailable(boolean value);
|
||||||
if (!queue.isEmpty()) {
|
void runOnUiThread(Runnable r);
|
||||||
ignoreNextFlush = false;
|
|
||||||
webView.setNetworkAvailable(online);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
final Runnable resetNetworkRunnable = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
online = false;
|
|
||||||
// If the following call triggers a notifyOfFlush, then ignore it.
|
|
||||||
ignoreNextFlush = true;
|
|
||||||
webView.setNetworkAvailable(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@Override void reset() {
|
|
||||||
cordova.getActivity().runOnUiThread(resetNetworkRunnable);
|
|
||||||
}
|
}
|
||||||
@Override void onNativeToJsMessageAvailable() {
|
|
||||||
cordova.getActivity().runOnUiThread(toggleNetworkRunnable);
|
public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
delegate.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
online = false;
|
||||||
|
// If the following call triggers a notifyOfFlush, then ignore it.
|
||||||
|
ignoreNextFlush = true;
|
||||||
|
delegate.setNetworkAvailable(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||||
|
delegate.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (!queue.isEmpty()) {
|
||||||
|
ignoreNextFlush = false;
|
||||||
|
delegate.setNetworkAvailable(online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Track when online/offline events are fired so that we don't fire excess events.
|
// Track when online/offline events are fired so that we don't fire excess events.
|
||||||
@Override void notifyOfFlush(boolean fromOnlineEvent) {
|
@Override
|
||||||
|
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
|
||||||
if (fromOnlineEvent && !ignoreNextFlush) {
|
if (fromOnlineEvent && !ignoreNextFlush) {
|
||||||
online = !online;
|
online = !online;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses Java reflection to access an API that lets us eval JS.
|
|
||||||
* Requires Android 3.2.4 or above.
|
|
||||||
*/
|
|
||||||
private class PrivateApiBridgeMode extends BridgeMode {
|
|
||||||
// Message added in commit:
|
|
||||||
// http://omapzoom.org/?p=platform/frameworks/base.git;a=commitdiff;h=9497c5f8c4bc7c47789e5ccde01179abc31ffeb2
|
|
||||||
// Which first appeared in 3.2.4ish.
|
|
||||||
private static final int EXECUTE_JS = 194;
|
|
||||||
|
|
||||||
Method sendMessageMethod;
|
|
||||||
Object webViewCore;
|
|
||||||
boolean initFailed;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private void initReflection() {
|
|
||||||
Object webViewObject = webView;
|
|
||||||
Class webViewClass = WebView.class;
|
|
||||||
try {
|
|
||||||
Field f = webViewClass.getDeclaredField("mProvider");
|
|
||||||
f.setAccessible(true);
|
|
||||||
webViewObject = f.get(webView);
|
|
||||||
webViewClass = webViewObject.getClass();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
// mProvider is only required on newer Android releases.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Field f = webViewClass.getDeclaredField("mWebViewCore");
|
|
||||||
f.setAccessible(true);
|
|
||||||
webViewCore = f.get(webViewObject);
|
|
||||||
|
|
||||||
if (webViewCore != null) {
|
|
||||||
sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
|
|
||||||
sendMessageMethod.setAccessible(true);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
initFailed = true;
|
|
||||||
Log.e(LOG_TAG, "PrivateApiBridgeMode failed to find the expected APIs.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override void onNativeToJsMessageAvailable() {
|
|
||||||
if (sendMessageMethod == null && !initFailed) {
|
|
||||||
initReflection();
|
|
||||||
}
|
|
||||||
// webViewCore is lazily initialized, and so may not be available right away.
|
|
||||||
if (sendMessageMethod != null) {
|
|
||||||
String js = popAndEncodeAsJs();
|
|
||||||
Message execJsMessage = Message.obtain(null, EXECUTE_JS, js);
|
|
||||||
try {
|
|
||||||
sendMessageMethod.invoke(webViewCore, execJsMessage);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Log.e(LOG_TAG, "Reflection message bridge failed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static class JsMessage {
|
private static class JsMessage {
|
||||||
final String jsPayloadOrCallbackId;
|
final String jsPayloadOrCallbackId;
|
||||||
final PluginResult pluginResult;
|
final PluginResult pluginResult;
|
||||||
|
Loading…
Reference in New Issue
Block a user