Skip to content

Commit 191a758

Browse files
ZacSweersJakeWharton
authored andcommitted
Add an option to post async messages
This functionality was made public in API 22 but same API has existed since API 16. This prevents locking the message queue for vsync events.
1 parent e09c2d7 commit 191a758

File tree

4 files changed

+99
-14
lines changed

4 files changed

+99
-14
lines changed

rxandroid/src/main/java/io/reactivex/android/schedulers/AndroidSchedulers.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@
1313
*/
1414
package io.reactivex.android.schedulers;
1515

16+
import android.annotation.SuppressLint;
17+
import android.os.Build;
1618
import android.os.Handler;
1719
import android.os.Looper;
18-
19-
import java.util.concurrent.Callable;
20-
20+
import android.os.Message;
2121
import io.reactivex.Scheduler;
2222
import io.reactivex.android.plugins.RxAndroidPlugins;
23+
import java.util.concurrent.Callable;
2324

2425
/** Android-specific Schedulers. */
2526
public final class AndroidSchedulers {
2627

2728
private static final class MainHolder {
28-
29-
static final Scheduler DEFAULT = new HandlerScheduler(new Handler(Looper.getMainLooper()));
29+
static final Scheduler DEFAULT
30+
= new HandlerScheduler(new Handler(Looper.getMainLooper()), false);
3031
}
3132

3233
private static final Scheduler MAIN_THREAD = RxAndroidPlugins.initMainThreadScheduler(
@@ -43,8 +44,32 @@ public static Scheduler mainThread() {
4344

4445
/** A {@link Scheduler} which executes actions on {@code looper}. */
4546
public static Scheduler from(Looper looper) {
47+
return from(looper, false);
48+
}
49+
50+
/**
51+
* A {@link Scheduler} which executes actions on {@code looper}.
52+
*
53+
* @param async if true, the scheduler will use async messaging on API >= 16 to avoid VSYNC
54+
* locking. On API < 16 this value is ignored.
55+
* @see Message#setAsynchronous(boolean)
56+
*/
57+
@SuppressLint("NewApi") // Checking for an @hide API.
58+
public static Scheduler from(Looper looper, boolean async) {
4659
if (looper == null) throw new NullPointerException("looper == null");
47-
return new HandlerScheduler(new Handler(looper));
60+
if (Build.VERSION.SDK_INT < 16) {
61+
async = false;
62+
} else if (async && Build.VERSION.SDK_INT < 22) {
63+
// Confirm that the method is available on this API level despite being @hide.
64+
Message message = Message.obtain();
65+
try {
66+
message.setAsynchronous(true);
67+
} catch (NoSuchMethodError e) {
68+
async = false;
69+
}
70+
message.recycle();
71+
}
72+
return new HandlerScheduler(new Handler(looper), async);
4873
}
4974

5075
private AndroidSchedulers() {

rxandroid/src/main/java/io/reactivex/android/schedulers/HandlerScheduler.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package io.reactivex.android.schedulers;
1515

16+
import android.annotation.SuppressLint;
1617
import android.os.Handler;
1718
import android.os.Message;
1819
import io.reactivex.Scheduler;
@@ -23,9 +24,11 @@
2324

2425
final class HandlerScheduler extends Scheduler {
2526
private final Handler handler;
27+
private final boolean async;
2628

27-
HandlerScheduler(Handler handler) {
29+
HandlerScheduler(Handler handler, boolean async) {
2830
this.handler = handler;
31+
this.async = async;
2932
}
3033

3134
@Override
@@ -41,19 +44,22 @@ public Disposable scheduleDirect(Runnable run, long delay, TimeUnit unit) {
4144

4245
@Override
4346
public Worker createWorker() {
44-
return new HandlerWorker(handler);
47+
return new HandlerWorker(handler, async);
4548
}
4649

4750
private static final class HandlerWorker extends Worker {
4851
private final Handler handler;
52+
private final boolean async;
4953

5054
private volatile boolean disposed;
5155

52-
HandlerWorker(Handler handler) {
56+
HandlerWorker(Handler handler, boolean async) {
5357
this.handler = handler;
58+
this.async = async;
5459
}
5560

5661
@Override
62+
@SuppressLint("NewApi") // Async will only be true when the API is available to call.
5763
public Disposable schedule(Runnable run, long delay, TimeUnit unit) {
5864
if (run == null) throw new NullPointerException("run == null");
5965
if (unit == null) throw new NullPointerException("unit == null");
@@ -69,6 +75,10 @@ public Disposable schedule(Runnable run, long delay, TimeUnit unit) {
6975
Message message = Message.obtain(handler, scheduled);
7076
message.obj = this; // Used as token for batch disposal of this worker's runnables.
7177

78+
if (async) {
79+
message.setAsynchronous(true);
80+
}
81+
7282
handler.sendMessageDelayed(message, unit.toMillis(delay));
7383

7484
// Re-check disposed state for removing in case we were racing a call to dispose().

rxandroid/src/test/java/io/reactivex/android/schedulers/AndroidSchedulersTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
*/
1414
package io.reactivex.android.schedulers;
1515

16+
import android.os.Build;
1617
import android.os.Looper;
18+
import android.os.Message;
1719
import io.reactivex.Scheduler;
1820
import io.reactivex.android.plugins.RxAndroidPlugins;
1921
import io.reactivex.android.testutil.EmptyScheduler;
@@ -25,11 +27,16 @@
2527
import org.junit.runner.RunWith;
2628
import org.robolectric.RobolectricTestRunner;
2729
import org.robolectric.annotation.Config;
30+
import org.robolectric.shadows.ShadowLooper;
31+
import org.robolectric.shadows.ShadowMessageQueue;
32+
import org.robolectric.util.ReflectionHelpers;
2833

2934
import static org.junit.Assert.assertEquals;
35+
import static org.junit.Assert.assertFalse;
3036
import static org.junit.Assert.assertNotNull;
3137
import static org.junit.Assert.assertSame;
3238
import static org.junit.Assert.fail;
39+
import static org.robolectric.Shadows.shadowOf;
3340

3441
@RunWith(RobolectricTestRunner.class)
3542
@Config(manifest=Config.NONE)
@@ -68,8 +75,36 @@ public void fromNullThrows() {
6875
}
6976
}
7077

78+
@Test
79+
public void fromNullThrowsTwoArg() {
80+
try {
81+
AndroidSchedulers.from(null, false);
82+
fail();
83+
} catch (NullPointerException e) {
84+
assertEquals("looper == null", e.getMessage());
85+
}
86+
}
87+
7188
@Test
7289
public void fromReturnsUsableScheduler() {
7390
assertNotNull(AndroidSchedulers.from(Looper.getMainLooper()));
7491
}
92+
93+
@Test
94+
public void asyncIgnoredPre16() {
95+
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 14);
96+
97+
ShadowLooper mainLooper = ShadowLooper.getShadowMainLooper();
98+
mainLooper.pause();
99+
ShadowMessageQueue mainMessageQueue = shadowOf(Looper.getMainLooper().getQueue());
100+
101+
Scheduler main = AndroidSchedulers.from(Looper.getMainLooper(), true);
102+
main.scheduleDirect(new Runnable() {
103+
@Override public void run() {
104+
}
105+
});
106+
107+
Message message = mainMessageQueue.getHead();
108+
assertFalse(message.isAsynchronous());
109+
}
75110
}

rxandroid/src/test/java/io/reactivex/android/schedulers/HandlerSchedulerTest.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@
2222
import io.reactivex.functions.Consumer;
2323
import io.reactivex.functions.Function;
2424
import io.reactivex.plugins.RxJavaPlugins;
25+
import java.util.Arrays;
26+
import java.util.Collection;
2527
import java.util.concurrent.TimeUnit;
2628
import java.util.concurrent.atomic.AtomicReference;
2729
import org.junit.After;
2830
import org.junit.Before;
2931
import org.junit.Ignore;
3032
import org.junit.Test;
3133
import org.junit.runner.RunWith;
32-
import org.robolectric.RobolectricTestRunner;
34+
import org.robolectric.ParameterizedRobolectricTestRunner;
3335
import org.robolectric.annotation.Config;
3436
import org.robolectric.shadows.ShadowLooper;
3537

@@ -46,9 +48,24 @@
4648
import static org.robolectric.shadows.ShadowLooper.runUiThreadTasksIncludingDelayedTasks;
4749
import static org.robolectric.shadows.ShadowLooper.unPauseMainLooper;
4850

49-
@RunWith(RobolectricTestRunner.class)
50-
@Config(manifest=Config.NONE)
51+
@RunWith(ParameterizedRobolectricTestRunner.class)
52+
@Config(manifest=Config.NONE, sdk = 16)
5153
public final class HandlerSchedulerTest {
54+
55+
@ParameterizedRobolectricTestRunner.Parameters(name = "async = {0}")
56+
public static Collection<Object[]> data() {
57+
return Arrays.asList(new Object[][]{
58+
{true},
59+
{false}
60+
});
61+
}
62+
63+
private Scheduler scheduler;
64+
65+
public HandlerSchedulerTest(boolean async) {
66+
this.scheduler = new HandlerScheduler(new Handler(Looper.getMainLooper()), async);
67+
}
68+
5269
@Before
5370
public void setUp() {
5471
RxJavaPlugins.reset();
@@ -61,8 +78,6 @@ public void tearDown() {
6178
unPauseMainLooper();
6279
}
6380

64-
private Scheduler scheduler = new HandlerScheduler(new Handler(Looper.getMainLooper()));
65-
6681
@Test
6782
public void directScheduleOncePostsImmediately() {
6883
CountingRunnable counter = new CountingRunnable();

0 commit comments

Comments
 (0)