Communicating from Thread to Thread using GreenRobot EventBus
Asked Answered
O

2

11

Just started with GreenRobot's EventBus.

There is only one thing that keeps me struggling: How do you communicate between different threads so that the onEventXY() method is actually also executed within the subscribed thread.

It seems that when you post an event, the subscribers onEvent method is called from the same thread as the thread on which the event was posted. That is not what I want.

I want to able to use the EventBus for inter-thread communication in such a way that the actual received event object is handled by the onEvent method within the thread that received the event object.

Is that possible?

Example:

  • mainThread
  • backGroundThread1
  • backGroundThread2

MainThread posts an event on the EventBus, backGroundThread1 receives it in its onEventXY() method and executes code within its own thread (sets some variable), backGroundThread2 receives it in its onEventXY method and executes code within its own thread (sets some variable).

If this isn't possible (yet), I am doomed to use Thread Queues, like BlockingQueue, which is harder to implement.

Any ideas?

Ornis answered 13/11, 2013 at 10:37 Comment(8)
have you found a solution for this?Brilliantine
@Brilliantine I haven't found a solution for this (yet) because the internal code of the GreenRobot EventBus needs to be adjusted afaik. Although the code is open source, I haven't forked the code to rewrite the code so that it would be possible to do so. I contacted the author for this feature request, but sofar Markus isn't able or doesn't want to add this feature. On Jan 9th 2014 I got an email response from Markus that he "might be able to revisit my email later this year". I sent him a complex email :) So for now: don't count on it to happen (soon).Ornis
This is so damn frustrating, I can communicate with service -- in my case TCP service, send commands but I can't get back the response. When I saw this lib and benchmark I was happy enough to try itBrilliantine
Now I have to implement a lot of boilerplate code just to make a simple thing workBrilliantine
I've never used this library before, but after quickly reading the docs it looks like you need : ThreadMode.Async? See github.com/greenrobot/EventBus for the "Delivery Threads" section.Jonajonah
@Jonajonah Your comment is incorrect. I wish it was that simple to do. Async creates its own thread, but doesn't handle the eventhandler in the thread I am already running. I want the eventhandler to be executed in the SAME custom thread. This is afaik not (yet) possible within the greenrobot library.Ornis
Ok, that makes it clear why its not working, sorry. I don't do any mobile development but on the server-side I've had good luck with Akka for this pattern ( doc.akka.io/docs/akka/snapshot/java/event-bus.html ). Anyway, I wish you luck.Jonajonah
Yeah that's what I thought too. I want to use EventBus to do the same. But doesn't look like it's supported. I had to resort to using BroadcastReceiver and a HandlerCordova
S
3

From Greenrobot docs at https://github.com/greenrobot/EventBus

BackgroundThread
Subscriber will be called in a background thread. If posting thread is not the main thread, event handler methods will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single background thread that will deliver all its events sequentially. Event handlers using this mode should try to return quickly to avoid blocking the background thread.

Async
Event handler methods are called in a separate thread. This is always independent from the posting thread and the main thread. Posting events never wait for event handler methods using this mode. Event handler methods should use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number of long running asynchronous handler methods at the same time to limit the number of concurrent threads. EventBus uses a thread pool to efficiently reuse threads from completed asynchronous event handler notifications.

When creating a callback, name suffix need to be added to onEvent as follow:

  • onEventMainThread(YourEvent eventInstance) and shorthand onEvent(YourEvent eventInstance)
    always dispatches into the main UI thread
  • onEventBackgroundThread(YourEvent eventInstance)
    this is most suitable for your problem
  • onEventAsync(YourEvent eventInstance)
    always in a new thread, risky if you dispatch multitude of events from here, can hit a Thread exception easy
Striptease answered 8/7, 2014 at 13:12 Comment(1)
This is not what I want. I want a mechanism like java's non blocking queues. Like this. EventBus doesn't do this (yet?).Ornis
D
3

I had the same problem and I used Looper, Handler and HandlerThread.

It is my BackgroundHandlerThread class:

import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.util.Log;

import java.lang.reflect.Method;
import java.util.UUID;


public class BackgroundHandlerThread extends Handler {

    private static final String TAG = BackgroundHandlerThread.class.getSimpleName();

    private HandlerThread handlerThread;
    private Object busHandler;

    public BackgroundHandlerThread(HandlerThread handlerThread, Object busHandler) {
        super(handlerThread.getLooper());
        this.handlerThread = handlerThread;
        this.busHandler = busHandler;
    }

    public void onEvent(Object event) {
        Log.d(TAG, "onEvent(Object), thread: " + Thread.currentThread().getId() + ", class: " + event.getClass().getName());
        Message message = obtainMessage();
        message.obj = event;
        sendMessage(message);
    }

    @Override
    public void handleMessage(Message msg) {
        Method[] aClassMethods = busHandler.getClass().getDeclaredMethods();
        for (Method m : aClassMethods) {
            if (m.getName().equals("onHandlerThreadEvent")) {
                if (m.getParameterTypes().length == 1 && m.getParameterTypes()[0].equals(msg.obj.getClass())) {
                    try {
                        m.invoke(busHandler, msg.obj);
                    } catch (Exception e) {
                        Log.wtf(TAG, e);
                    }
                }
            }
        }
    }

    public boolean quit() {
        return handlerThread.quit();
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    public boolean quitSafely() {
        return handlerThread.quitSafely();
    }

    public static class Builder {
        private HandlerThread handlerThread;
        private Object busHandler;

        public Builder(Object busHandler) {
            this.busHandler = busHandler;
        }

        public Builder setHandlerThread(HandlerThread handlerThread) {
            this.handlerThread = handlerThread;
            return this;
        }

        public BackgroundHandlerThread build() {
            if (handlerThread == null) {
                handlerThread = new HandlerThread("BackgroundHandlerThread: " + UUID.randomUUID().toString(), Process.THREAD_PRIORITY_BACKGROUND);
            }

            if (!handlerThread.isAlive()) {
                handlerThread.start();
            }

            return new BackgroundHandlerThread(handlerThread, busHandler);
        }
    }
}

I used it in my service but BackgroundHandlerThread object can be bind to any object.

import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import de.greenrobot.event.EventBus;

public class DeviceService extends Service {

    private static final String TAG = DeviceService.class.getSimpleName();

    private BluetoothDevice bluetoothDevice;
    private BackgroundHandlerThread handlerThread;
    private boolean connected = false;

    //region Lifecycle
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate, thread: " + Thread.currentThread().getId());
        handlerThread = new BackgroundHandlerThread.Builder(this).build();
        EventBus.getDefault().register(handlerThread);
    }

    @Override
    public void onDestroy() {
        EventBus.getDefault().unregister(handlerThread);
        handlerThread.quit();
        super.onDestroy();
    }
    //endregion

    public void onHandlerThreadEvent(ConnectToDeviceEvent event) {
        Log.d(TAG, "onHandlerThreadEvent, thread: " + Thread.currentThread().getId());
        connected = true;
        bluetoothDevice = event.device;
        EventBus.getDefault().post(new ConnectionStateChangedEvent(bluetoothDevice, connected));
    }

    //region Static manipulation
    public static void startService(Context context) {
        Intent intent = new Intent(context, DeviceBinder.class);
        context.startService(intent);
    }
    //endregion
}

And activity class:

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.util.Log;

import de.greenrobot.event.EventBus;

public class MainActivity extends Activity {

    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.startButton).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "onStartClick, thread: " + Thread.currentThread().getId());
                EventBus.getDefault().post(new ConnectToDeviceEvent(application.getCurrentStateProvider().getDevice()));
            }
        });
        DeviceService.startService(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
    }

    public void onEventMainThread(ConnectionStateChangedEvent event) {
        Log.d(TAG, "onEventMainThread(ConnectionStateChangedEvent), thread: " + Thread.currentThread().getId());
    }

}

Log output:

D/MainActivity: onStartClick, thread: 1
D/BackgroundHandlerThread: onEvent(Object), thread: 1, class: ConnectToDeviceEvent
D/DeviceService: onHandlerThreadEvent, thread: 4399
D/BackgroundHandlerThread: onEvent(Object), thread: 4399, class: ConnectionStateChangedEvent
D/MainActivity: onEventMainThread(ConnectionStateChangedEvent), thread: 1

Similar: Best practice for eventbus with thread safety

Dialyser answered 8/10, 2015 at 19:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.