Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for scheduling on Android Handler threads #318

Merged
merged 10 commits into from
Aug 23, 2013
59 changes: 59 additions & 0 deletions rxjava-contrib/rxjava-android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'osgi'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I basically copied this file over from the Swing contrib module. There's a lot of cruft in here which I think can be remove or at least shared with the parent modules? I'm thinking about IDE project file generation and JavaDoc setup.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's probably the case, I have not yet attempted doing that as I'm not the most comfortable with Gradle.

sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6

dependencies {
compile project(':rxjava-core')
provided 'junit:junit-dep:4.10'
provided 'org.mockito:mockito-core:1.8.5'
provided 'org.robolectric:robolectric:2.1.1'
provided 'com.google.android:android:4.0.1.2'
}

eclipse {
classpath {
// include 'provided' dependencies on the classpath
plusConfigurations += configurations.provided

downloadSources = true
downloadJavadoc = true
}
}

idea {
module {
// include 'provided' dependencies on the classpath
scopes.PROVIDED.plus += configurations.provided
}
}

javadoc {
options {
doclet = "org.benjchristensen.doclet.DocletExclude"
docletpath = [rootProject.file('./gradle/doclet-exclude.jar')]
stylesheetFile = rootProject.file('./gradle/javadocStyleSheet.css')
windowTitle = "RxJava Android Javadoc ${project.version}"
}
options.addStringOption('top').value = '<h2 class="title" style="padding-top:40px">RxJava Android</h2>'
}

jar {
manifest {
name = 'rxjava-android'
instruction 'Bundle-Vendor', 'Netflix'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case SoundCloud wants to take ownership of the Android module, should probably update this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough about OSGI requirements to comment on how this behaves. This artifact as part of RxJava however will be on maven central under "com.netflix.rxjava" as that's the namespace on Maven, despite keeping all of the code namespaced to just "rx".

instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava'
instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*'
}
}

test {
testLogging {
exceptionFormat "full"
events "started"
displayGranularity 2
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package rx.android.concurrency;

import android.os.Handler;
import android.os.Looper;
import rx.Scheduler;

/**
* Schedulers that have Android specific functionality
*/
public class AndroidSchedulers {

private static final Scheduler MAIN_THREAD_SCHEDULER =
new HandlerThreadScheduler(new Handler(Looper.getMainLooper()));

private AndroidSchedulers(){

}

/**
* {@link Scheduler} which uses the provided {@link Handler} to execute an action
* @param handler The handler that will be used when executing the action
* @return A handler based scheduler
*/
public static Scheduler handlerThread(final Handler handler) {
return new HandlerThreadScheduler(handler);
}

/**
* {@link Scheduler} which will execute an action on the main Android UI thread.
*
* @return A Main {@link Looper} based scheduler
*/
public static Scheduler mainThread() {
return MAIN_THREAD_SCHEDULER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package rx.android.concurrency;

import android.os.Handler;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import rx.Scheduler;
import rx.Subscription;
import rx.operators.AtomicObservableSubscription;
import rx.util.functions.Func2;

import java.util.concurrent.TimeUnit;

import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
* Schedules actions to run on an Android Handler thread.
*/
public class HandlerThreadScheduler extends Scheduler {

private final Handler handler;

/**
* Constructs a {@link HandlerThreadScheduler} using the given {@link Handler}
* @param handler {@link Handler} to use when scheduling actions
*/
public HandlerThreadScheduler(Handler handler) {
this.handler = handler;
}

/**
* Calls {@link HandlerThreadScheduler#schedule(Object, rx.util.functions.Func2, long, java.util.concurrent.TimeUnit)}
* with a delay of zero milliseconds.
*
* See {@link #schedule(Object, rx.util.functions.Func2, long, java.util.concurrent.TimeUnit)}
*/
@Override
public <T> Subscription schedule(final T state, final Func2<Scheduler, T, Subscription> action) {
return schedule(state, action, 0L, TimeUnit.MILLISECONDS);
}

/**
* Calls {@link Handler#postDelayed(Runnable, long)} with a runnable that executes the given action.
* @param state
* State to pass into the action.
* @param action
* Action to schedule.
* @param delayTime
* Time the action is to be delayed before executing.
* @param unit
* Time unit of the delay time.
* @return A Subscription from which one can unsubscribe from.
*/
@Override
public <T> Subscription schedule(final T state, final Func2<Scheduler, T, Subscription> action, long delayTime, TimeUnit unit) {
final AtomicObservableSubscription subscription = new AtomicObservableSubscription();
final Scheduler _scheduler = this;
handler.postDelayed(new Runnable() {
@Override
public void run() {
subscription.wrap(action.call(_scheduler, state));
}
}, unit.toMillis(delayTime));
return subscription;
}

@RunWith(RobolectricTestRunner.class)
@Config(manifest=Config.NONE)
public static final class UnitTest {

@Test
public void shouldScheduleImmediateActionOnHandlerThread() {
final Handler handler = mock(Handler.class);
final Object state = new Object();
final Func2<Scheduler, Object, Subscription> action = mock(Func2.class);

Scheduler scheduler = new HandlerThreadScheduler(handler);
scheduler.schedule(state, action);

// verify that we post to the given Handler
ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
verify(handler).postDelayed(runnable.capture(), eq(0L));

// verify that the given handler delegates to our action
runnable.getValue().run();
verify(action).call(scheduler, state);
}

@Test
public void shouldScheduleDelayedActionOnHandlerThread() {
final Handler handler = mock(Handler.class);
final Object state = new Object();
final Func2<Scheduler, Object, Subscription> action = mock(Func2.class);

Scheduler scheduler = new HandlerThreadScheduler(handler);
scheduler.schedule(state, action, 1L, TimeUnit.SECONDS);

// verify that we post to the given Handler
ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
verify(handler).postDelayed(runnable.capture(), eq(1000L));

// verify that the given handler delegates to our action
runnable.getValue().run();
verify(action).call(scheduler, state);
}
}
}


3 changes: 2 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ include 'rxjava-core', \
'language-adaptors:rxjava-jruby', \
'language-adaptors:rxjava-clojure', \
'language-adaptors:rxjava-scala', \
'rxjava-contrib:rxjava-swing'
'rxjava-contrib:rxjava-swing', \
'rxjava-contrib:rxjava-android'