2727import rx .plugins .*;
2828import rx .subscriptions .*;
2929
30+ import static rx .internal .util .PlatformDependent .ANDROID_API_VERSION_IS_NOT_ANDROID ;
31+
3032/**
3133 * @warn class description missing
3234 */
@@ -39,17 +41,25 @@ public class NewThreadWorker extends Scheduler.Worker implements Subscription {
3941 /** Force the use of purge (true/false). */
4042 private static final String PURGE_FORCE_KEY = "rx.scheduler.jdk6.purge-force" ;
4143 private static final String PURGE_THREAD_PREFIX = "RxSchedulerPurge-" ;
42- /** Forces the use of purge even if setRemoveOnCancelPolicy is available. */
43- private static final boolean PURGE_FORCE ;
44+ private static final boolean SHOULD_TRY_ENABLE_CANCEL_POLICY ;
4445 /** The purge frequency in milliseconds. */
4546 public static final int PURGE_FREQUENCY ;
4647 private static final ConcurrentHashMap <ScheduledThreadPoolExecutor , ScheduledThreadPoolExecutor > EXECUTORS ;
4748 private static final AtomicReference <ScheduledExecutorService > PURGE ;
4849 static {
4950 EXECUTORS = new ConcurrentHashMap <ScheduledThreadPoolExecutor , ScheduledThreadPoolExecutor >();
5051 PURGE = new AtomicReference <ScheduledExecutorService >();
51- PURGE_FORCE = Boolean .getBoolean (PURGE_FORCE_KEY );
5252 PURGE_FREQUENCY = Integer .getInteger (FREQUENCY_KEY , 1000 );
53+
54+ // Forces the use of purge even if setRemoveOnCancelPolicy is available
55+ final boolean purgeForce = Boolean .getBoolean (PURGE_FORCE_KEY );
56+
57+ final int androidApiVersion = PlatformDependent .getAndroidApiVersion ();
58+
59+ // According to http://developer.android.com/reference/java/util/concurrent/ScheduledThreadPoolExecutor.html#setRemoveOnCancelPolicy(boolean)
60+ // setRemoveOnCancelPolicy available since Android API 21
61+ SHOULD_TRY_ENABLE_CANCEL_POLICY = !purgeForce
62+ && (androidApiVersion == ANDROID_API_VERSION_IS_NOT_ANDROID || androidApiVersion >= 21 );
5363 }
5464 /**
5565 * Registers the given executor service and starts the purge thread if not already started.
@@ -85,6 +95,7 @@ public void run() {
8595 public static void deregisterExecutor (ScheduledExecutorService service ) {
8696 EXECUTORS .remove (service );
8797 }
98+
8899 /** Purges each registered executor and eagerly evicts shutdown executors. */
89100 static void purgeExecutors () {
90101 try {
@@ -102,32 +113,89 @@ static void purgeExecutors() {
102113 RxJavaPlugins .getInstance ().getErrorHandler ().handleError (t );
103114 }
104115 }
105-
106- /**
116+
117+ /**
118+ * Improves performance of {@link #tryEnableCancelPolicy(ScheduledExecutorService)}.
119+ * Also, it works even for inheritance: {@link Method} of base class can be invoked on the instance of child class.
120+ */
121+ private static volatile Object cachedSetRemoveOnCancelPolicyMethod ;
122+
123+ /**
124+ * Possible value of {@link #cachedSetRemoveOnCancelPolicyMethod} which means that cancel policy is not supported.
125+ */
126+ private static final Object SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED = new Object ();
127+
128+ /**
107129 * Tries to enable the Java 7+ setRemoveOnCancelPolicy.
108130 * <p>{@code public} visibility reason: called from other package(s) within RxJava.
109131 * If the method returns false, the {@link #registerExecutor(ScheduledThreadPoolExecutor)} may
110132 * be called to enable the backup option of purging the executors.
111- * @param exec the executor to call setRemoveOnCaneclPolicy if available.
133+ * @param executor the executor to call setRemoveOnCaneclPolicy if available.
112134 * @return true if the policy was successfully enabled
113135 */
114- public static boolean tryEnableCancelPolicy (ScheduledExecutorService exec ) {
115- if (!PURGE_FORCE ) {
116- for (Method m : exec .getClass ().getMethods ()) {
117- if (m .getName ().equals ("setRemoveOnCancelPolicy" )
118- && m .getParameterTypes ().length == 1
119- && m .getParameterTypes ()[0 ] == Boolean .TYPE ) {
120- try {
121- m .invoke (exec , true );
122- return true ;
123- } catch (Exception ex ) {
124- RxJavaPlugins .getInstance ().getErrorHandler ().handleError (ex );
125- }
136+ public static boolean tryEnableCancelPolicy (ScheduledExecutorService executor ) {
137+ if (SHOULD_TRY_ENABLE_CANCEL_POLICY ) {
138+ final boolean isInstanceOfScheduledThreadPoolExecutor = executor instanceof ScheduledThreadPoolExecutor ;
139+
140+ final Method methodToCall ;
141+
142+ if (isInstanceOfScheduledThreadPoolExecutor ) {
143+ final Object localSetRemoveOnCancelPolicyMethod = cachedSetRemoveOnCancelPolicyMethod ;
144+
145+ if (localSetRemoveOnCancelPolicyMethod == SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED ) {
146+ return false ;
147+ }
148+
149+ if (localSetRemoveOnCancelPolicyMethod == null ) {
150+ Method method = findSetRemoveOnCancelPolicyMethod (executor );
151+
152+ cachedSetRemoveOnCancelPolicyMethod = method != null
153+ ? method
154+ : SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED ;
155+
156+ methodToCall = method ;
157+ } else {
158+ methodToCall = (Method ) localSetRemoveOnCancelPolicyMethod ;
159+ }
160+ } else {
161+ methodToCall = findSetRemoveOnCancelPolicyMethod (executor );
162+ }
163+
164+ if (methodToCall != null ) {
165+ try {
166+ methodToCall .invoke (executor , true );
167+ return true ;
168+ } catch (Exception e ) {
169+ RxJavaPlugins .getInstance ().getErrorHandler ().handleError (e );
126170 }
127171 }
128172 }
173+
129174 return false ;
130175 }
176+
177+ /**
178+ * Tries to find {@code "setRemoveOnCancelPolicy(boolean)"} method in the class of passed executor.
179+ *
180+ * @param executor whose class will be used to search for required method.
181+ * @return {@code "setRemoveOnCancelPolicy(boolean)"} {@link Method}
182+ * or {@code null} if required {@link Method} was not found.
183+ */
184+ static Method findSetRemoveOnCancelPolicyMethod (ScheduledExecutorService executor ) {
185+ // The reason for the loop is to avoid NoSuchMethodException being thrown on JDK 6
186+ // which is more costly than looping through ~70 methods.
187+ for (final Method method : executor .getClass ().getMethods ()) {
188+ if (method .getName ().equals ("setRemoveOnCancelPolicy" )) {
189+ final Class <?>[] parameterTypes = method .getParameterTypes ();
190+
191+ if (parameterTypes .length == 1 && parameterTypes [0 ] == Boolean .TYPE ) {
192+ return method ;
193+ }
194+ }
195+ }
196+
197+ return null ;
198+ }
131199
132200 /* package */
133201 public NewThreadWorker (ThreadFactory threadFactory ) {
0 commit comments