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.
|
||||
@Override
|
||||
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||
public void init(final CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||
Whitelist internalWhitelist, Whitelist externalWhitelist,
|
||||
CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
@ -127,9 +127,23 @@ public class AndroidWebView extends WebView implements CordovaWebView {
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
cookieManager = new AndroidCookieManager(this);
|
||||
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();
|
||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, CoreAndroid.class.getCanonicalName());
|
||||
pluginManager.init();
|
||||
|
||||
if (this.viewClient == null) {
|
||||
|
@ -18,16 +18,10 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.PluginResult;
|
||||
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.webkit.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.
|
||||
*/
|
||||
private final BridgeMode[] registeredListeners;
|
||||
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
|
||||
|
||||
/**
|
||||
* When null, the bridge is disabled. This occurs during page transitions.
|
||||
@ -72,32 +66,26 @@ public class NativeToJsMessageQueue {
|
||||
*/
|
||||
private BridgeMode activeBridgeMode;
|
||||
|
||||
private final CordovaInterface cordova;
|
||||
private final CordovaWebView webView;
|
||||
|
||||
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 void addBridgeMode(BridgeMode bridgeMode) {
|
||||
bridgeModes.add(bridgeMode);
|
||||
}
|
||||
|
||||
public boolean isBridgeEnabled() {
|
||||
return activeBridgeMode != null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return queue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the bridge mode.
|
||||
*/
|
||||
public void setBridgeMode(int value) {
|
||||
if (value < -1 || value >= registeredListeners.length) {
|
||||
if (value < -1 || value >= bridgeModes.size()) {
|
||||
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||
} else {
|
||||
BridgeMode newMode = value < 0 ? null : registeredListeners[value];
|
||||
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
|
||||
if (newMode != activeBridgeMode) {
|
||||
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
||||
synchronized (this) {
|
||||
@ -105,7 +93,7 @@ public class NativeToJsMessageQueue {
|
||||
if (newMode != null) {
|
||||
newMode.reset();
|
||||
if (!paused && !queue.isEmpty()) {
|
||||
newMode.onNativeToJsMessageAvailable();
|
||||
newMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,7 +134,7 @@ public class NativeToJsMessageQueue {
|
||||
if (activeBridgeMode == null) {
|
||||
return null;
|
||||
}
|
||||
activeBridgeMode.notifyOfFlush(fromOnlineEvent);
|
||||
activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
|
||||
if (queue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@ -179,7 +167,7 @@ public class NativeToJsMessageQueue {
|
||||
/**
|
||||
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
|
||||
*/
|
||||
private String popAndEncodeAsJs() {
|
||||
public String popAndEncodeAsJs() {
|
||||
synchronized (this) {
|
||||
int length = queue.size();
|
||||
if (length == 0) {
|
||||
@ -260,7 +248,7 @@ public class NativeToJsMessageQueue {
|
||||
}
|
||||
queue.add(message);
|
||||
if (!paused) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable();
|
||||
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,133 +263,94 @@ public class NativeToJsMessageQueue {
|
||||
if (!value) {
|
||||
synchronized (this) {
|
||||
if (!queue.isEmpty() && activeBridgeMode != null) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable();
|
||||
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class BridgeMode {
|
||||
abstract void onNativeToJsMessageAvailable();
|
||||
void notifyOfFlush(boolean fromOnlineEvent) {}
|
||||
public static abstract class BridgeMode {
|
||||
abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||
void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||
void reset() {}
|
||||
}
|
||||
|
||||
/** Uses JS polls for messages on a timer.. */
|
||||
private class PollingBridgeMode extends BridgeMode {
|
||||
@Override void onNativeToJsMessageAvailable() {
|
||||
public static class NoOpBridgeMode extends BridgeMode {
|
||||
@Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||
private class LoadUrlBridgeMode extends BridgeMode {
|
||||
final Runnable runnable = new Runnable() {
|
||||
public void run() {
|
||||
String js = popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
webView.loadUrlIntoView("javascript:" + js, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||
private final CordovaWebView webView;
|
||||
private final CordovaInterface cordova;
|
||||
|
||||
@Override void onNativeToJsMessageAvailable() {
|
||||
cordova.getActivity().runOnUiThread(runnable);
|
||||
public LoadUrlBridgeMode(CordovaWebView webView, CordovaInterface cordova) {
|
||||
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. */
|
||||
private class OnlineEventsBridgeMode extends BridgeMode {
|
||||
public static class OnlineEventsBridgeMode extends BridgeMode {
|
||||
private final OnlineEventsBridgeModeDelegate delegate;
|
||||
private boolean online;
|
||||
private boolean ignoreNextFlush;
|
||||
|
||||
final Runnable toggleNetworkRunnable = new Runnable() {
|
||||
public void run() {
|
||||
if (!queue.isEmpty()) {
|
||||
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);
|
||||
public interface OnlineEventsBridgeModeDelegate {
|
||||
void setNetworkAvailable(boolean value);
|
||||
void runOnUiThread(Runnable r);
|
||||
}
|
||||
@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.
|
||||
@Override void notifyOfFlush(boolean fromOnlineEvent) {
|
||||
@Override
|
||||
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
|
||||
if (fromOnlineEvent && !ignoreNextFlush) {
|
||||
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 {
|
||||
final String jsPayloadOrCallbackId;
|
||||
final PluginResult pluginResult;
|
||||
|
Loading…
Reference in New Issue
Block a user