From 1dbff05bf375557ea73e07632d732bd90a71af9e Mon Sep 17 00:00:00 2001 From: Kevin Jin Date: Fri, 6 Mar 2015 11:30:46 -0800 Subject: runOnMainSync on a single thread Add DroidDriverException.propagate Change-Id: I7bdcbe1642b4448cc40a06995950573ebd890b4f --- src/io/appium/droiddriver/base/BaseUiElement.java | 11 ++------ .../exceptions/DroidDriverException.java | 33 ++++++++++++++++++++++ .../appium/droiddriver/helpers/DroidDrivers.java | 16 ++--------- .../droiddriver/util/InstrumentationUtils.java | 23 +++++++-------- 4 files changed, 49 insertions(+), 34 deletions(-) (limited to 'src/io/appium/droiddriver') diff --git a/src/io/appium/droiddriver/base/BaseUiElement.java b/src/io/appium/droiddriver/base/BaseUiElement.java index 293ee9c..e98ee44 100644 --- a/src/io/appium/droiddriver/base/BaseUiElement.java +++ b/src/io/appium/droiddriver/base/BaseUiElement.java @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import io.appium.droiddriver.UiElement; @@ -201,14 +200,8 @@ public abstract class BaseUiElement> implements try { return futureTask.get(); - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException) { - throw (RuntimeException) cause; - } - throw new DroidDriverException(cause); - } catch (InterruptedException e) { - throw new DroidDriverException(e); + } catch (Throwable t) { + throw DroidDriverException.propagate(t); } } diff --git a/src/io/appium/droiddriver/exceptions/DroidDriverException.java b/src/io/appium/droiddriver/exceptions/DroidDriverException.java index e7ba2b7..482130f 100644 --- a/src/io/appium/droiddriver/exceptions/DroidDriverException.java +++ b/src/io/appium/droiddriver/exceptions/DroidDriverException.java @@ -34,4 +34,37 @@ public class DroidDriverException extends RuntimeException { public DroidDriverException(String message, Throwable cause) { super(message, cause); } + + /** + * Adapted from Guava libraries.

+ * Propagates {@code throwable} as-is if it is an instance of {@link RuntimeException} or {@link + * Error}, or else as a last resort, wraps it in a {@code DroidDriverException} and then + * propagates.

This method always throws an exception. The {@code DroidDriverException} return + * type is only for client code to make Java type system happy in case a return value is required + * by the enclosing method. Example usage: + *

+   *   T doSomething() {
+   *     try {
+   *       return someMethodThatCouldThrowAnything();
+   *     } catch (IKnowWhatToDoWithThisException e) {
+   *       return handle(e);
+   *     } catch (Throwable t) {
+   *       throw DroidDriverException.propagate(t);
+   *     }
+   *   }
+   * 
+ * + * @param throwable the Throwable to propagate + * @return nothing will ever be returned; this return type is only for your convenience, as + * illustrated in the example above + */ + public static DroidDriverException propagate(Throwable throwable) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + if (throwable instanceof Error) { + throw (Error) throwable; + } + throw new DroidDriverException(throwable); + } } diff --git a/src/io/appium/droiddriver/helpers/DroidDrivers.java b/src/io/appium/droiddriver/helpers/DroidDrivers.java index 5cddb4f..7725bf5 100644 --- a/src/io/appium/droiddriver/helpers/DroidDrivers.java +++ b/src/io/appium/droiddriver/helpers/DroidDrivers.java @@ -20,8 +20,6 @@ import android.annotation.TargetApi; import android.app.Instrumentation; import android.os.Build; -import java.lang.reflect.InvocationTargetException; - import io.appium.droiddriver.DroidDriver; import io.appium.droiddriver.exceptions.DroidDriverException; import io.appium.droiddriver.instrumentation.InstrumentationDriver; @@ -77,18 +75,8 @@ public class DroidDrivers { try { return (DroidDriver) Class.forName(driverClass).getConstructor(Instrumentation.class) .newInstance(instrumentation); - } catch (ClassNotFoundException e) { - throw new DroidDriverException(e); - } catch (NoSuchMethodException e) { - throw new DroidDriverException(e); - } catch (InstantiationException e) { - throw new DroidDriverException(e); - } catch (IllegalAccessException e) { - throw new DroidDriverException(e); - } catch (IllegalArgumentException e) { - throw new DroidDriverException(e); - } catch (InvocationTargetException e) { - throw new DroidDriverException(e); + } catch (Throwable t) { + throw DroidDriverException.propagate(t); } } diff --git a/src/io/appium/droiddriver/util/InstrumentationUtils.java b/src/io/appium/droiddriver/util/InstrumentationUtils.java index a019008..95eb48c 100644 --- a/src/io/appium/droiddriver/util/InstrumentationUtils.java +++ b/src/io/appium/droiddriver/util/InstrumentationUtils.java @@ -22,6 +22,8 @@ import android.os.Looper; import android.util.Log; import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; @@ -41,6 +43,7 @@ public class InstrumentationUtils { public void run() { } }; + private static final Executor RUN_ON_MAIN_SYNC_EXECUTOR = Executors.newSingleThreadExecutor(); /** * Initializes this class. If you use a runner that is not DroidDriver-aware, you need to call @@ -112,8 +115,8 @@ public class InstrumentationUtils { Logs.log(Log.INFO, "Timed out after " + timeoutMillis + " milliseconds waiting for idle on main looper"); return false; - } catch (Throwable e) { - throw new DroidDriverException(e); + } catch (Throwable t) { + throw DroidDriverException.propagate(t); } return true; } @@ -132,12 +135,10 @@ public class InstrumentationUtils { * Runs {@code callable} on the main thread on best-effort basis up to a time limit, which * defaults to {@code 10000L} and can be set as an am instrument option under the key {@code * dd.runOnMainSyncTimeout}.

This is a safer variation of {@link Instrumentation#runOnMainSync} - * because the latter may hang. But it is heavy because a new thread is created for each call. You - * may turn off this behavior by setting {@code "-e dd.runOnMainSyncTimeout 0"} on the am command - * line.

The {@code callable} may never run, for example, in case that the main Looper has - * exited due to uncaught exception. + * because the latter may hang. You may turn off this behavior by setting {@code "-e + * dd.runOnMainSyncTimeout 0"} on the am command line.

The {@code callable} may never run, for + * example, if the main Looper has exited due to uncaught exception. */ - // TODO: call runOnMainSync on a single worker thread? public static V runOnMainSyncWithTimeout(Callable callable) { validateNotAppThread(); final RunOnMainSyncFutureTask futureTask = new RunOnMainSyncFutureTask<>(callable); @@ -146,12 +147,12 @@ public class InstrumentationUtils { // Call runOnMainSync on current thread without time limit. futureTask.runOnMainSyncNoThrow(); } else { - new Thread() { + RUN_ON_MAIN_SYNC_EXECUTOR.execute(new Runnable() { @Override public void run() { futureTask.runOnMainSyncNoThrow(); } - }.start(); + }); } try { @@ -159,8 +160,8 @@ public class InstrumentationUtils { } catch (java.util.concurrent.TimeoutException e) { throw new TimeoutException("Timed out after " + runOnMainSyncTimeoutMillis + " milliseconds waiting for Instrumentation.runOnMainSync", e); - } catch (Throwable e) { - throw new DroidDriverException(e); + } catch (Throwable t) { + throw DroidDriverException.propagate(t); } finally { futureTask.cancel(false); } -- cgit v1.2.3