Part 2 – Event-Driven Architecture – Implementation with Android Framework

Concepts:

Even-Driven Architecture is employed in Android Framework. The framework provides the interfaces and APIs that work as managers; whereas users can implement components which perform roles as workers.

  1. Android Handler, Looper, and Messages Queues are used to send and receive messages between components on a single process, but multi-threads
  2. Android Binder is an Interprocess Communication mechanism that sends/receives message between different modules, running on different processes.
  3. The events or tasks are coded as integer values, by which corresponding logics should be invoked upon receiving the messages. In addition, data could be wrapped in the messages.
  4. Android Looper manages the messages that are to be sent and received by different Handlers and thus takes the role of the manager in the even-driven architecture. The user implement handler classes that are the workers that could be working on same of different threads.
  5. Android Service Manager itself is the manager that manages the communications between the workers on different processes. We will explore further how Android Service Manager help facilitate communications between commponents as part of Android Binder library.

Implementation

Sending messages between threads

  • In the context of Event-Drive Architecture, Android Looper plays the role of the manager; it manages messages betweens Handlers.
  • Android Handler provides the abstraction or API to send or receive messages. The child classes are workers and send/receive commands. The Handler uses the Looper to send messages and runnable tasks to the thread's message queue, and the Looper processes these messages and tasks one by one in the order they were added to the queue. The SenderHandler and ReceiverHandler must be associated with the same Looper.
  • Android Message are data object that are sent/received by Handlers. The message formay may have the following:

    • int what: a code the identify the message
    • int arg1, arg2: the data values sent in the message.
    • arbitrary object
    • a reply

The following explains details on how to implement a simple program using Message Queue, Handlers and Looper. https://freewindcode.com/2023/02/13/sending-messages-between-threads-via-android-handler-looper-messaqequeues/

Sending messages via processes

  • In the context of Event-Driven Architecture, Android Binder and Android Service Manager both works together as the manager that processes messages between different components.
  • Users implement "components/modules" that run on multiple processes. Those are considered the "workers".
  • Users implement "service" which make use of Android Binder and Android Service Manager to facilitate the communication between different components. The service must be registered to Android Service Manager. The service name must also be specified in AndroidManifest.xml file.
  • The data sent/received are wrapped in a Android Parcel container. The Parcel can packed string, integer values, IBinder object.
  • The operation that sends data (transact function) and receives data (onTransact function) can be 2 ways, that is sending data and reply could be passed in Parcel.
    • sending Data must be given, and may be empty.
    • Reply data can be null
    • Flag: default value = 0 and allow 2 ways communication. Otherwise, set it to FLAG_ONEWAY.

In earlier post, Sending messages between processes using Android Binder, we have explored the communication from module MySensor to MyReceiver using ICommunication service. User in main process can invoke a Remote Procedure Call to MySensor that gets the signal. The request is sent with a integer code = GET_SIGNAL and an instance of MyReceiver. When a signal data is acquired in the other process, it is returned via the Reply Parcel. The instance of MyReceiver, which also store the signal data is wrapped in the Reply Parcel.

Note that an instance of MyReceiver could be wrapped in the Data and Reply Parcel. The signal data is saved as the internal member of MyReceiver.

There are many design patterns in this implementation. MyReceiver implements onSignalReceived() and stored signal_ as its private. MySensor implements getSignal() and save a list of MyReceiver. Therefore, MySensor and MyReceiver follow an Observer Design Pattern. IServiceManage manages services such as ICommunication and help manages messages between processes; it follows Event-Driven architecture. BnInterface and BpInterface sends and receives messages in agreed formats. They follow Client-Server Design Pattern.

The following Activity Diagram illustrates the call operations

Note: There are some mismatch between the activity diagram and the class diagram. Below are some fixing:

  • receiver->callback(int) means the receiver invokes onSignalReceived(int)
  • actor is MyReceiver and can call BpCommunication::getSignal(code, receiver). The function in class digram of BpCommunication should takes 2 parameters.

Implementing a CallBack design pattern

  • In the above section, we already implement ICommunication that allows user to send a request for signal to MySensor. The signal is received via Reply Parcel. In addition, the signal is sent back as an internal memory of MyReceiver. In other words, MyReceiver instance is converted to a Binder object and wrapped in Parcel container and sent back as reply.
  • In this section, we can implement ICallback service that allows MySensor to send signal back to MyReceiver.
  • In main process, MyMachine class has component MyReceiver. It implements registerReceiver and getSignal. Both functions have to use ICommunication to send data to MySensor.
    • registerReceiver requests MySensor to add its member myReceiver_ to its list.
    • getSignal requests for signal, same as earlier implementation
  • MySensor adds myReceiver_ to its list and perform getSignal. However, to return data, instead of sending a reply Parcel, it will invoke ICallBack::onSignalReceived. Since myReceiver_ is an instance of ICallBack, it can call BpCallBack::transact function will initiate sending signal data.
  • both ICommunication and ICallback have to register as service to Android Service Manager by user in main (or whatever module that uses the 2 services)
    • service.Communication and service.Callback are names in manifest.xml
    • after adding service, run ProcessState and IPCThreadState
// on the server side or main process side or MyMachine side, we start service:
// note that we initialized the concrete classes that implement the BnCommunication or BnCallback
MySensor mySensor;
defaultServiceManager()->addService(String16("service.Communication"), &mySensor);

MyReceiver myReceiver;
defaultServiceManager()->addService(String16("service.CallBack"), &myReceiver);

android::ProcessState::self()->startThreadPool();
android::IPCThreadState::self()->joinThreadPool();
  • We still need to run a process at the receiving side. In that process, we have to run getService.
// to get ICommunication service from client side (Main process/MyMachine Process:
android::interface_cast<ICommunication>
                (android::defaultServiceManager()->getService(android::String16(service.communication)));

// to get ICallback service from server side (MySensor process):
android::interface_cast<ICallback>
                (android::defaultServiceManager()->getService(android::String16(service.callback)));
  • registering for myReceiver_ requires that MyMachine class has an association with ICallback. Thus the class has a member sp<ICallBack>myReceiver_ .
  • When MyMachine sends a request to register myReceiver_ using ICommunication, it sends the myReceiver_ to Binder using ICommunication channel.
  • to be specific, a reference to strong binder of ICallback type is passed to ICommunication::registerReceiver as an argument, which is then converted to a binder object by the function asBinder.
  • in the receiving side, the Binder object is read via readStrongBinder and casted back to sp<ICallback>
  • in addition, since MyMachine uses ICommunication service, it must get the service and check if the service is available when it starts to send the request.

const int REGISTER_RECEIVER = 1;
const int GET_SIGNAL = 2;

// class MyMachine
class MyMachine {
    public:
    void registerReceiver () {
        // check if ICommunicationService is up:
        communication_= android::interface_cast<ICommunication>
                (android::defaultServiceManager()->getService(android::String16(service.communication)));
        if (communication.get() != null){     //ICommunication must have been initialized
            myReceiver_ = new MyReceiver();   //Initialize MyReceiver
            communication->registerReceiver(receiver);  //call BpCommunication to send the request
        }
    }
    void getSignal() {
        // check if ICommunicationService is up:
        communication_= android::interface_cast<ICommunication>
                (android::defaultServiceManager()->getService(android::String16(service.communication)));
        if (communication.get() != null){     //ICommunication must have been initialized 
            communication->getSignal();  //call BpCommunication to send the request for signal
        }
    }
    private:
    sp<ICommunication> communication_;
    sp<ICallBack> myReceiver_;
};

// excerpt of class BpCommunication
class BpCommunication : public BpInterface < ICommunication > {
    void registerReceiver ( sp<ICallBack>& receiver) {
        Parcel data, reply;
        data.writeInt32(code);
        sp<IBinder> binder = receiver->asBinder();
        data.writeStrongBinder(binder);
        remote()->transact(REGISTER_RECEIVER, data, &reply);
    }
    void getSignal() {};
};

// excerpt of class BnCommunication
class BnCommunication : public BnInterface < ICommunication > {
    status_t onTransact(uint32_t code, const android::Parcel& data, const     android::Parcel* reply, uint32_t flags){
        switch(code):
        {
            case REGISTER_RECEIVER: {
                CHECK_INTERFACE(ICommunication, data, reply);
                android::sp<ICallBack> receiver = android::interface_cast<ICallBack>(data.readStrongBinder());

                registerReceiver(receiver);   //MySensor class implements this function
            } break;
            case GET_SIGNAL: {
                // more implementation
            } break;
            default:
                 return BBinder::onTransact(code, data, reply, flags);
        }
    }
};
  • initiating request for signal from MyMachine is done normally: the request is sent to BpCommunication, invoking BpCommunication::transact function.
  • on the receiving end, the BnCommunication::onTransact receives the request and performs logic to get signal data.
  • the result is sent back not by Reply Parcel but by ICallBack::onSignalReceived. Since a reference to sp<ICallBack> myReceiver was saved by registerReceiver it could called BpCallBack::onSignalReceived function from the MySensor's process. The follow shows how MySensor is implemented
class MySensor : public BnCommunication {
     public:
     void registerReceiver (int code, sp<ICallback> &myReceiver){
          receivers.push_back(myReceiver);
     }
     void getSignal() {
          // logic to generate signal
          // return signal data
          // there should be more logic to get the particular receiver
          // for eg: a PID is stored into the receiver.
          if (receivers.front().get() != nullptr)     // receiver has to be initialized in main processed and registered
              receivers.front()->onSignalReceived();
     }
     private:
     list <sp<ICallback>> receivers;
}
  • Usually, IPC android design pattern would have a ServiceStub class that serves as a concrete class and implements BnCommunication. MySensor is an association or Friend class of ServiceStub that ServiceStub could call to run logic.
  • Suppose that we write a simple program that take an argument, and if the arg = 1, it will be the client side (MyMachine) and if arg = 2, it will be the server side, the main function will be implemented as follow:
#include <utils/RefBase.h>
#include <utils/Log.h>
#include <binder/TextOutput.h>

#include <binder/IInterface.h>
#include <binder/IBinder.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>

int main(int argc, char **argv) {

    if (argc == 1) {    // client side (main process, MyMachine process)
        std::cout<<"My Machine";
        // the services are created
        defaultServiceManager()->addService(String16("service.communication"),new MySensor());
        defaultServiceManager()->addService(String16("service.callback"),new MyReceiver());

        android::ProcessState::self()->startThreadPool();

        IPCThreadState::self()->joinThreadPool();
        // initialize myMachine

        MyMachine myMachine;
        // myMachine sends request to MySensor. 
        myMachine.registerReceiver();
        myMachine.getSignal();

    } else if (argc == 2) {       // server side, MySensor process)
        std::cout<<"My Sensor";

        // since we use service on this process, we have to get the service

        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder = sm->getService(String16(service.communication"));
        sp<ICommunication> comm = interface_cast<ICommunicatiom>(binder)

        binder = sm->getService(String16(service.callback"));
        sp<ICallback> callback = interface_cast<ICallback>(binder)
    }

    return 0;
}

References

This page is heavily influenced by the guided under Ebixio » Using Android IPC binders from native code. All credits to Gabriel Burca

8 thoughts on “Part 2 – Event-Driven Architecture – Implementation with Android Framework
  1. Its like you read my mind! You appear to know a lot about
    this, like you wrote the book in it or something. I think that you
    can do with some pics to drive the message home a little
    bit, but instead of that, this is great blog. A great read.

    I will certainly be back.

  2. Magnificent beat ! I would like to apprentice while you amend your web site, how can i
    subscribe for a blog website? The account helped me a acceptable deal.

    I had been a little bit acquainted of this your broadcast provided bright clear concept

    1. Thank you for your kind words. You can subscribe to newsletter by inputting your info (email) on the Newsletter box in the footer of the website or the Contact page. I blog to document knowledge that I am still learning every day. I am still very much a junior.

  3. Hello! I just wanted to ask if you ever have any trouble with hackers?
    My last blog (wordpress) was hacked and I ended up losing a few months
    of hard work due to no backup. Do you have any methods to prevent hackers?

    1. We all should backup the sites, and install 2 factor authentications for not only logging into the web hosting panel but also WordPress admin. For WordPress, we have to use plugins.

Leave a Reply to aspor.ua Cancel reply

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