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

Async improvements #509

Merged
merged 1 commit into from
Aug 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ android:
- tools
- platform-tools
- build-tools-27.0.3
- android-27
- android-28
- extra-google-google_play_services
- extra-android-m2repository
- extra-android-support
Expand Down
4 changes: 2 additions & 2 deletions blessedDeps.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
rootProject.ext.JAVA_SOURCE_VERSION = JavaVersion.VERSION_1_7
rootProject.ext.JAVA_TARGET_VERSION = JavaVersion.VERSION_1_7

rootProject.ext.TARGET_SDK_VERSION = 27
rootProject.ext.COMPILE_SDK_VERSION = 27
rootProject.ext.TARGET_SDK_VERSION = 28
rootProject.ext.COMPILE_SDK_VERSION = 28
rootProject.ext.MIN_SDK_VERSION = 14
rootProject.ext.MIN_SDK_VERSION_LITHO = 15

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
buildscript {

ext.KOTLIN_VERSION = "1.2.60"
ext.ANDROID_PLUGIN_VERSION = "3.1.3"
ext.ANDROID_PLUGIN_VERSION = "3.1.4"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.airbnb.epoxy;

import android.os.Handler;

import static com.airbnb.epoxy.EpoxyAsyncUtil.MAIN_THREAD_HANDLER;
import static com.airbnb.epoxy.EpoxyAsyncUtil.getAsyncBackgroundHandler;

/**
* A subclass of {@link EpoxyController} that makes it easy to do model building and diffing in
* the background.
* <p>
* See https://github.com/airbnb/epoxy/wiki/Epoxy-Controller#asynchronous-support
*/
public abstract class AsyncEpoxyController extends EpoxyController {

/**
* A new instance that does model building and diffing asynchronously.
*/
public AsyncEpoxyController() {
this(true);
}

/**
* @param enableAsync True to do model building and diffing asynchronously, false to do them
* both on the main thread.
*/
public AsyncEpoxyController(boolean enableAsync) {
this(true, true);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

enableAsync is unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@baoti whoops! thanks a lot for catching that


/**
* Individually control whether model building and diffing are done async or on the main thread.
*/
public AsyncEpoxyController(boolean enableAsyncModelBuilding, boolean enableAsyncDiffing) {
super(getHandler(enableAsyncModelBuilding), getHandler(enableAsyncDiffing));
}

private static Handler getHandler(boolean enableAsync) {
return enableAsync ? getAsyncBackgroundHandler() : MAIN_THREAD_HANDLER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ private void onRunCompleted(
@Nullable final DiffResult result
) {

MainThreadExecutor.INSTANCE.execute(new Runnable() {
// We use an asynchronous handler so that the Runnable can be posted directly back to the main
// thread without waiting on view invalidation synchronization.
MainThreadExecutor.ASYNC_INSTANCE.execute(new Runnable() {
@Override
public void run() {
final boolean dispatchResult = tryLatchList(newList, runGeneration);
Expand Down
103 changes: 103 additions & 0 deletions epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyAsyncUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.airbnb.epoxy;

import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;

import java.lang.reflect.Constructor;

/**
* Various helpers for running Epoxy operations off the main thread.
*/
public final class EpoxyAsyncUtil {
private EpoxyAsyncUtil() {
}

/**
* A Handler class that uses the main thread's Looper.
*/
public static final Handler MAIN_THREAD_HANDLER =
createHandler(Looper.getMainLooper(), false);

/**
* A Handler class that uses the main thread's Looper. Additionally, this handler calls
* {@link Message#setAsynchronous(boolean)} for
* each {@link Message} that is sent to it or {@link Runnable} that is posted to it
*/
public static final Handler AYSNC_MAIN_THREAD_HANDLER =
createHandler(Looper.getMainLooper(), true);

private static Handler asyncBackgroundHandler;

/**
* A Handler class that uses a separate background thread dedicated to Epoxy. Additionally,
* this handler calls {@link Message#setAsynchronous(boolean)} for
* each {@link Message} that is sent to it or {@link Runnable} that is posted to it
*/
@MainThread
public static Handler getAsyncBackgroundHandler() {
// This is initialized lazily so we don't create the thread unless it will be used.
// It isn't synchronized so it should only be accessed on the main thread.
if (asyncBackgroundHandler == null) {
asyncBackgroundHandler = createHandler(buildBackgroundLooper("epoxy"), true);
}

return asyncBackgroundHandler;
}

/**
* Create a Handler with the given Looper
*
* @param async If true the Handler will calls {@link Message#setAsynchronous(boolean)} for
* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
*/
public static Handler createHandler(Looper looper, boolean async) {
if (!async) {
return new Handler(looper);
}

if (Build.VERSION.SDK_INT >= 28) {
return Handler.createAsync(looper);
}

if (Build.VERSION.SDK_INT >= 17) {
Constructor<Handler> handlerConstructor = asyncHandlerConstructor();
if (handlerConstructor != null) {
try {
return handlerConstructor.newInstance(looper, null, true);
} catch (Throwable e) {
// Fallback
}
}
}

return new Handler(looper);
}

/**
* Create a new looper that runs on a new background thread.
*/
public static Looper buildBackgroundLooper(String threadName) {
HandlerThread handlerThread = new HandlerThread(threadName);
handlerThread.start();
return handlerThread.getLooper();
}

@Nullable
private static Constructor<Handler> asyncHandlerConstructor() {
try {
//noinspection JavaReflectionMemberAccess
return Handler.class.getConstructor(
Looper.class,
Handler.Callback.class,
Boolean.class
);
} catch (Throwable e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.airbnb.epoxy;

import android.os.Handler;
import android.os.Looper;

import static com.airbnb.epoxy.EpoxyAsyncUtil.createHandler;

class MainThreadExecutor extends HandlerExecutor {
static final MainThreadExecutor INSTANCE = new MainThreadExecutor();
static final MainThreadExecutor INSTANCE = new MainThreadExecutor(false);
static final MainThreadExecutor ASYNC_INSTANCE = new MainThreadExecutor(true);

MainThreadExecutor() {
super(new Handler(Looper.getMainLooper()));
MainThreadExecutor(boolean async) {
super(createHandler(Looper.getMainLooper(), async));
}
}


Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.airbnb.epoxy.sample;

import android.os.Handler;
import android.os.HandlerThread;

import com.airbnb.epoxy.AutoModel;
import com.airbnb.epoxy.TypedEpoxyController;
import com.airbnb.epoxy.sample.models.CarouselModelGroup;
import com.airbnb.epoxy.sample.views.HeaderViewModel_;

import java.util.List;

import static com.airbnb.epoxy.EpoxyAsyncUtil.getAsyncBackgroundHandler;

public class SampleController extends TypedEpoxyController<List<CarouselData>> {
public interface AdapterCallbacks {
void onAddCarouselClicked();
Expand All @@ -31,18 +30,10 @@ public interface AdapterCallbacks {

private final AdapterCallbacks callbacks;

private static final Handler BACKGROUND_HANDLER;

static {
HandlerThread handlerThread = new HandlerThread("epoxy");
handlerThread.start();
BACKGROUND_HANDLER = new Handler(handlerThread.getLooper());
}

SampleController(AdapterCallbacks callbacks) {
// Demonstrating how model building and diffing can be done in the background.
// You can control them separately by passing in separate handler, as shown below.
super(BACKGROUND_HANDLER, BACKGROUND_HANDLER);
super(getAsyncBackgroundHandler(), getAsyncBackgroundHandler());
// super(new Handler(), BACKGROUND_HANDLER);
// super(BACKGROUND_HANDLER, new Handler());

Expand Down