Improvements to the singleton pattern, better encapsulate fields

This commit is contained in:
cpfeiffer 2018-03-18 00:39:36 +01:00
parent f39b11974d
commit d2c952ffa5
4 changed files with 65 additions and 66 deletions

View File

@ -133,7 +133,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
if (showConfig) {
Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity");
Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity");
WebViewSingleton.runJavascriptInterface(this, currentDevice, currentUUID);
WebViewSingleton.getInstance().runJavascriptInterface(this, currentDevice, currentUUID);
}
// FIXME: is this really supposed to be outside the check for SHOW_CONFIG?
@ -156,7 +156,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
private void setupBGWebView() {
setContentView(R.layout.activity_external_pebble_js);
myWebView = WebViewSingleton.getWebView(this);
myWebView = WebViewSingleton.getInstance().getWebView(this);
if (myWebView.getParent() != null) {
((ViewGroup) myWebView.getParent()).removeView(myWebView);
}

View File

@ -110,7 +110,7 @@ class PebbleIoThread extends GBDeviceIoThread {
public static void sendAppMessage(GBDeviceEventAppMessage message) {
final String jsEvent;
try {
WebViewSingleton.checkAppRunning(message.appUUID);
WebViewSingleton.getInstance().checkAppRunning(message.appUUID);
} catch (IllegalStateException ex) {
LOG.warn("Unable to send app message: " + message, ex);
return;
@ -126,7 +126,7 @@ class PebbleIoThread extends GBDeviceIoThread {
final String appMessage = PebbleUtils.parseIncomingAppMessage(message.message, message.appUUID, message.id);
LOG.debug("to WEBVIEW: event: " + jsEvent + " message: " + appMessage);
WebViewSingleton.invokeWebview(new WebViewSingleton.WebViewRunnable() {
WebViewSingleton.getInstance().invokeWebview(new WebViewSingleton.WebViewRunnable() {
@Override
public void invoke(WebView webView) {
webView.evaluateJavascript("if (typeof Pebble == 'object') Pebble.evaluate('" + jsEvent + "',[" + appMessage + "]);", new ValueCallback<String>() {
@ -424,7 +424,7 @@ class PebbleIoThread extends GBDeviceIoThread {
}
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
WebViewSingleton.disposeWebView();
WebViewSingleton.getInstance().disposeWebView();
}
gbDevice.sendDeviceUpdateIntent(getContext());
@ -553,9 +553,9 @@ class PebbleIoThread extends GBDeviceIoThread {
LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid);
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
if (mPebbleProtocol.hasAppMessageHandler(appMgmt.uuid)) {
WebViewSingleton.stopJavascriptInterface();
WebViewSingleton.getInstance().stopJavascriptInterface();
} else {
WebViewSingleton.runJavascriptInterface(gbDevice, appMgmt.uuid);
WebViewSingleton.getInstance().runJavascriptInterface(gbDevice, appMgmt.uuid);
}
}
break;

View File

@ -41,16 +41,10 @@ import java.io.ByteArrayInputStream;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelper;
import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelperBound;
import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelperListener;
import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetResponse;
import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.latch;
import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
public class GBWebClient extends WebViewClient {
@ -85,19 +79,14 @@ public class GBWebClient extends WebViewClient {
private WebResourceResponse mimicReply(Uri requestedUri) {
if (requestedUri.getHost() != null && (StringUtils.indexOfAny(requestedUri.getHost(), AllowedDomains) != -1)) {
if (GBApplication.getGBPrefs().isBackgroundJsEnabled() && internetHelperBound) {
if (GBApplication.getGBPrefs().isBackgroundJsEnabled() && WebViewSingleton.getInstance().internetHelperBound) {
LOG.debug("WEBVIEW forwarding request to the internet helper");
Bundle bundle = new Bundle();
bundle.putString("URL", requestedUri.toString());
Message webRequest = Message.obtain();
webRequest.replyTo = internetHelperListener;
webRequest.setData(bundle);
try {
latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal.
internetHelper.send(webRequest);
latch.await();
return internetResponse;
return WebViewSingleton.getInstance().send(webRequest);
} catch (RemoteException | InterruptedException e) {
LOG.warn("Error downloading data from " + requestedUri, e);
}

View File

@ -31,6 +31,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
@ -54,33 +55,33 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInt
public class WebViewSingleton {
private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class);
private static WebViewSingleton instance = new WebViewSingleton();
private WebView instance = null;
private WebView webView = null;
private MutableContextWrapper contextWrapper;
private Looper mainLooper;
private static WebViewSingleton webViewSingleton = new WebViewSingleton();
private static UUID currentRunningUUID;
public static Messenger internetHelper = null;
public static boolean internetHelperBound;
public static CountDownLatch latch;
public static WebResourceResponse internetResponse;
public static Messenger internetHelperListener;
private static boolean internetHelperInstalled;
private UUID currentRunningUUID;
private Messenger internetHelper = null;
public boolean internetHelperBound;
private boolean internetHelperInstalled;
private CountDownLatch latch;
private WebResourceResponse internetResponse;
private Messenger internetHelperListener;
private WebViewSingleton() {
}
public static synchronized void ensureCreated(Activity context) {
if (webViewSingleton.instance == null) {
webViewSingleton.contextWrapper = new MutableContextWrapper(context);
webViewSingleton.mainLooper = webViewSingleton.contextWrapper.getMainLooper();
webViewSingleton.instance = new WebView(webViewSingleton.contextWrapper);
if (instance.webView == null) {
instance.contextWrapper = new MutableContextWrapper(context);
instance.mainLooper = context.getMainLooper();
instance.webView = new WebView(instance.contextWrapper);
WebView.setWebContentsDebuggingEnabled(true);
webViewSingleton.instance.setWillNotDraw(true);
webViewSingleton.instance.clearCache(true);
webViewSingleton.instance.setWebViewClient(new GBWebClient());
webViewSingleton.instance.setWebChromeClient(new GBChromeClient());
WebSettings webSettings = webViewSingleton.instance.getSettings();
instance.webView.setWillNotDraw(true);
instance.webView.clearCache(true);
instance.webView.setWebViewClient(new GBWebClient());
instance.webView.setWebChromeClient(new GBChromeClient());
WebSettings webSettings = instance.webView.getSettings();
webSettings.setJavaScriptEnabled(true);
//needed to access the DOM
webSettings.setDomStorageEnabled(true);
@ -90,7 +91,7 @@ public class WebViewSingleton {
}
//Internet helper outgoing connection
private static ServiceConnection internetHelperConnection = new ServiceConnection() {
private ServiceConnection internetHelperConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
LOG.info("internet helper service bound");
internetHelperBound = true;
@ -104,8 +105,20 @@ public class WebViewSingleton {
}
};
public static WebViewSingleton getInstance() {
return instance;
}
public WebResourceResponse send(Message webRequest) throws RemoteException, InterruptedException {
webRequest.replyTo = internetHelperListener;
latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal.
internetHelper.send(webRequest);
latch.await();
return internetResponse;
}
//Internet helper inbound (responses) handler
private static class IncomingHandler extends Handler {
private class IncomingHandler extends Handler {
private String getCharsetFromHeaders(String contentType) {
if (contentType != null && contentType.toLowerCase().trim().contains("charset=")) {
@ -137,9 +150,9 @@ public class WebViewSingleton {
}
@NonNull
public static WebView getWebView(Context context) {
webViewSingleton.contextWrapper.setBaseContext(context);
return webViewSingleton.instance;
public WebView getWebView(Context context) {
contextWrapper.setBaseContext(context);
return webView;
}
/**
@ -147,23 +160,21 @@ public class WebViewSingleton {
* @param uuid the uuid of the application expected to be running
* @throws IllegalStateException when the webview is not active or the app is not running
*/
public static void checkAppRunning(@NonNull UUID uuid) {
if (webViewSingleton.instance == null) {
throw new IllegalStateException("webViewSingleton.instance is null!");
public void checkAppRunning(@NonNull UUID uuid) {
if (webView == null) {
throw new IllegalStateException("instance.webView is null!");
}
if (!uuid.equals(currentRunningUUID)) {
throw new IllegalStateException("Expected app " + uuid + " is not running, but " + currentRunningUUID + " is.");
}
}
public static void runJavascriptInterface(@NonNull Activity context, @NonNull GBDevice device, @NonNull UUID uuid) {
if (webViewSingleton.instance == null || webViewSingleton.contextWrapper == null) {
ensureCreated(context);
}
public void runJavascriptInterface(@NonNull Activity context, @NonNull GBDevice device, @NonNull UUID uuid) {
ensureCreated(context);
runJavascriptInterface(device, uuid);
}
public static void runJavascriptInterface(@NonNull GBDevice device, @NonNull UUID uuid) {
public void runJavascriptInterface(@NonNull GBDevice device, @NonNull UUID uuid) {
if (uuid.equals(currentRunningUUID)) {
LOG.debug("WEBVIEW uuid not changed keeping the old context");
} else {
@ -183,10 +194,10 @@ public class WebViewSingleton {
String internetHelperPkg = "nodomain.freeyourgadget.internethelper";
String internetHelperCls = internetHelperPkg + ".MyService";
try {
webViewSingleton.contextWrapper.getPackageManager().getApplicationInfo(internetHelperPkg, 0);
contextWrapper.getPackageManager().getApplicationInfo(internetHelperPkg, 0);
Intent intent = new Intent();
intent.setComponent(new ComponentName(internetHelperPkg, internetHelperCls));
webViewSingleton.contextWrapper.getApplicationContext().bindService(intent, internetHelperConnection, Context.BIND_AUTO_CREATE);
contextWrapper.getApplicationContext().bindService(intent, internetHelperConnection, Context.BIND_AUTO_CREATE);
internetHelperListener = new Messenger(new IncomingHandler());
internetHelperInstalled = true;
}
@ -196,10 +207,9 @@ public class WebViewSingleton {
}
}
}
}
public static void stopJavascriptInterface() {
public void stopJavascriptInterface() {
invokeWebview(new WebViewRunnable() {
@Override
public void invoke(WebView webView) {
@ -209,10 +219,10 @@ public class WebViewSingleton {
});
}
public static void disposeWebView() {
public void disposeWebView() {
if (internetHelperBound) {
LOG.debug("WEBVIEW: will unbind the internet helper");
webViewSingleton.contextWrapper.getApplicationContext().unbindService(internetHelperConnection);
contextWrapper.getApplicationContext().unbindService(internetHelperConnection);
internetHelperBound = false;
}
currentRunningUUID = null;
@ -227,34 +237,34 @@ public class WebViewSingleton {
webView.loadUrl("about:blank");
// webView.freeMemory();
webView.pauseTimers();
// instance.destroy();
// instance = null;
// webView.destroy();
// webView = null;
// contextWrapper = null;
// jsInterface = null;
}
});
}
public static void invokeWebview(final WebViewRunnable runnable) {
if (webViewSingleton.instance == null || webViewSingleton.mainLooper == null) {
public void invokeWebview(final WebViewRunnable runnable) {
if (webView == null || mainLooper == null) {
LOG.warn("Webview already disposed, ignoring runnable");
return;
}
new Handler(webViewSingleton.mainLooper).post(new Runnable() {
new Handler(mainLooper).post(new Runnable() {
@Override
public void run() {
if (webViewSingleton.instance == null) {
if (webView == null) {
LOG.warn("Webview already disposed, cannot invoke runnable");
return;
}
runnable.invoke(webViewSingleton.instance);
runnable.invoke(webView);
}
});
}
public interface WebViewRunnable {
/**
* Called in the main thread with a non-null webView instance
* Called in the main thread with a non-null webView webView
* @param webView the webview, never null
*/
void invoke(WebView webView);