Sending messages between threads via Android Handler, Looper, MessaqeQueues

Concepts

  1. Handler:
  • According to Android Document, 2 usages of handler are:
    • "to schedule a message or object to be executed in future". In other words, it could receive messages and perform action.
    • "the enqueue and action to be performed on a different thread", same process.
  • when sending a message, the message is placed in a MessageQueue associated with a Looper. A Looper is used to manage a message queue for a thread and it allows a thread to process messages one at a time. Handler constructs a message via obtainMessage fucntion, and sends the message via sendMessage.
  • when receiving a message, a Looper retrieves the messages from the queue and dispatches them to appropriate Handler. Receiving is done via function handleMessage.
  • sendMessage sends a message which is received in the handleMessage in the thread attached to this handler.
  • the "sending_message_class" and the "receiving_message_class" can be on different threads but must be associated with the same Looper for communication to occur via message queue.
  1. Looper:
  • According to Android Document, Looper is class that "runs a message loop for a thread". In other words, user "can initialize current thread as a looper. This gives a chance to create a handler that then references this looper". (see function prepare in Android Document).
  • MainLooper is the application main looper, lives on the main thread of the application. Looper and handler can live on child threads.
  1. MessageQueue: is the class that holds the list of messages to be dispatched by a Looper. Messages are added to Looper via Handler.
  • The message should include at least a custom code what to identify the type of message. Message can include arg1, arg2 which stores primitive data or obj which is object . (see function obtainMessage function and its arguments in Android Document )

The following program implements 2 classes: MySensor and MyReceiver. MySensor can send a message that includes and EventCode and and integer the MyReceiver. Both classes communicate through default Looper.

Implementation

Using default Looper

#include <iostream>
#include <android/looper.h>
#include <android/handler.h>

using namespace android;

enum EventType {
  DIAGNOSTIC_SIGNAL,
  CONTROL_SIGNAL,
  STOP_SIGNAL
};

class MySensor : public Handler {
 public:
  void sendSignal(EventType event, int data) {
    Message msg = obtainMessage(event, data);
    sendMessage(msg);
  }
};

class MyReceiver : public Handler {
 public:
  void handleMessage(const Message& msg) {
    EventType event = (EventType) msg.what;
    int data = msg.arg1;
    switch (event) {
      case DIAGNOSTIC_SIGNAL:
        std::cout << "Received DIAGNOSTIC_SIGNAL with data: " << data << std::endl;
        break;
      case CONTROL_SIGNAL:
        std::cout << "Received CONTROL_SIGNAL with data: " << data << std::endl;
        break;
      case STOP_SIGNAL:
        std::cout << "Received STOP_SIGNAL with data: " << data << std::endl;
        break;
      default:
        std::cout << "Received unknown signal with data: " << data << std::endl;
        break;
    }
  }
};

int main() {
  MySensor sensor;
  MyReceiver receiver;

  sensor.sendSignal(DIAGNOSTIC_SIGNAL, 123);
  sensor.sendSignal(CONTROL_SIGNAL, 456);
  sensor.sendSignal(STOP_SIGNAL, 789);

  return 0;
}

Notes: MySensor and MyReceiver can run on multiple threads, and use custom Looper. So long as they use the same Looper, the messages can be sent between the two components. The looper is passed into the constructor of MySensor and MyReceiver.

Using custom Looper

  • In the below programs, we have 2 threads. The custom Looper is created on the main thread.
  • MySensor, and MyReceiver are each created (they could be on different threads, but in this program they are both on main thread). They are handler classes and bounded to the Looper via the parameterized constructor:
MySensor(sp<Looper> looper) : sl:Handler(looper) {}

MyReceiver(sp<Looper> looper) : sl:Handler(looper)
  • Handler is always bound to a Looper, and subsequently bound to the thread associated with the Looper
#include <iostream>
#include <android/looper.h>
#include <android/handler.h>
#include <thread>

using namespace android;

enum EventType {
  DIAGNOSTIC_SIGNAL,
  CONTROL_SIGNAL,
  STOP_SIGNAL
};

class MySensor : public Handler {
 public:
  MySensor(sp<Looper> looper) : Handler(looper) {}

  void sendSignal(EventType event, int data) {
    Message msg = obtainMessage(event, data);
    sendMessage(msg);
  }
};

class MyReceiver : public Handler {
 public:
  MyReceiver(sp<Looper> looper) : Handler(looper) {}

  void handleMessage(const Message& msg) {
    EventType event = (EventType) msg.what;
    int data = msg.arg1;
    switch (event) {
      case DIAGNOSTIC_SIGNAL:
        std::cout << "Received DIAGNOSTIC_SIGNAL with data: " << data << std::endl;
        break;
      case CONTROL_SIGNAL:
        std::cout << "Received CONTROL_SIGNAL with data: " << data << std::endl;
        break;
      case STOP_SIGNAL:
        std::cout << "Received STOP_SIGNAL with data: " << data << std::endl;
        break;
      default:
        std::cout << "Received unknown signal with data: " << data << std::endl;
        break;
    }
  }
};

void runSensor(sp<Looper> looper) {
  MySensor sensor(looper);
  sensor.sendSignal(DIAGNOSTIC_SIGNAL, 123);
  sensor.sendSignal(CONTROL_SIGNAL, 456);
  sensor.sendSignal(STOP_SIGNAL, 789);
}

void runReceiver(sp<Looper> looper) {
  MyReceiver receiver(looper);
  looper->pollOnce(-1);
}

int main() {
  sp<Looper> looper = new Looper(false);

  std::thread sensorThread(runSensor, looper);
  std::thread receiverThread(runReceiver, looper);

  sensorThread.join();
  receiverThread.join();

  return 0;
}
  • To send a message to Looper, we need to construct a message using obtainMessage and then send the message to the MessageQueue using either sendToTarget or sendMessage function.
  • The difference between sendToTarget and sendMessage is that sendToTarget is used when the sender wants to send the message back to the handler that created it, whereas sendMessage() is used when the sender wants to send the message to a different handler.
  • In the above program, Sensor and Receiver were created on different threads. Consequently, the handlers are different, but still bound to the same looper!!!
  • sendMessage allows sending message to the receiving handler on a different thread. The handler s can communicate because they are bounded to the same Looper.
// to construct message: 
Message msg = obtainMessage(event, data);
// to send the message to the same handler that the message is constructed:
msg->sendToTarget();
// to send the message to a different handler:
sendMessage();

Leave a Reply

Your email address will not be published. Required fields are marked *