Literata
Literata

Reputation: 305

Communication Approaches Between Activity and Service in Android

New Android programmer here. I have a Service which performs socket management and async I/O, and I need to establish a communication path between it and the Activity in my app.

The current approach is to equip both the Service and Activity with BroadcastReceivers and use them to send 'command' Intents from the Activity to the Service, and to send 'alert' Intents from the Service to the Activity.

My Service has a runnable which is where the socket read() happens; when data's received, the runnable sends an 'incoming data' intent to the Service, who then alerts the Activity:

    @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            super.onStartCommand(intent, flags, startId);
            if (m_IsRunning == false) {
                m_IsRunning = true;
                (new Thread(new Runnable() {
                    byte[] inputBuffer = new byte[512];
                    public void run() {
                        while (m_IsRunning) {
                            if (m_IsConnected) {
                                try {
                                    m_Nis = m_Socket.getInputStream();
                                    m_Nis.read(inputBuffer, 0, 512);
                                    Intent broadcast = new Intent();
                                    Bundle bun = new Bundle();
                                    bun.putString("ServiceCmd", "ALERT_INCOMING_DATA");
                                    bun.putByteArray("MsgBuffer", inputBuffer);
                                    broadcast.putExtras(bun);
                                    broadcast.setAction(BROADCAST_TO_SERVICE);
                                    sendBroadcast(broadcast);
                                } catch (IOException e) {
                                    // Send fault to activity
                                }
                            }
                        }
                    }
                })).start();
            }
            return START_STICKY;
        }

My approach with the BroadcastReceiver looks like this:

        private BroadcastReceiver serviceReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Bundle bun = intent.getExtras();
                String cmdString = bun.getString("ServiceCmd");

                if (cmdString.equals("CMD_SETHOSTINFO")) {
                    // The activity has requested us to set the host info
                    String hostAddr = bun.getString("HostAddressString");
                    int hostPort = bun.getInt("HostPortNumber");
                    processSetHostInfoCommand(hostAddr, hostPort);
                }
                else if (cmdString.equals("CMD_CONNECT")) {
                    // The activity has requested us to connect
                    if ((m_IsRunning) && (m_IsConnected == false)) {
                        // Attempt to connect
                        processConnectCommand();
                    }
                }
                else if (cmdString.equals("CMD_DISCONNECT")) {
                    // The activity has requested us to disconnect
                    if ((m_IsRunning) && (m_IsConnected == true)) {
                        // Attempt to disconnect
                        processDisconnectCommand();
                    }
                }
                else if (cmdString.equals("CMD_SENDDATA")) {
                    // The activity has requested us to send data
                    if ((m_IsRunning) && (m_IsConnected == true)) {
                        // Attempt to send data
                        byte[] msgBuffer = bun.getByteArray("MsgBuffer");
                        processSendDataCommand(msgBuffer);
                    }
                }
                else if (cmdString.equals("ALERT_INCOMING_DATA")) {
                    // Our TCP receiver thread has received data
                    if (m_IsRunning) {
                        byte[] msgBuffer = bun.getByteArray("MsgBuffer");
                        processIncomingDataAlert(msgBuffer);
                    }
                }
            }
        };

(Those processWhatever() methods in general do the socket management and data transmission.)

Like I said, it seems to work fine, but I'm wondering if this isn't a case where using Messages and Handlers wouldn't be more appropriate.

So, the specific questions are:

  1. What's the 'Tao of Android' in deciding when to use BroadcastReceiver/Intents or Handler/Messages?

  2. Are there any cross-thread considerations when deciding which approach to use?

(And, though it's off-topic, one final question):

  1. Is a Service suited to do the kind of socket-based I/O I'm trying to do?

Upvotes: 0

Views: 1136

Answers (2)

Franci Penov
Franci Penov

Reputation: 75991

The Tao of broadcast intents, intents and handlers

Broadcast intents are for one-to-many pub/sub scenarios, where one component wants to let the world know something happened, but doesn't care if anyone/how many listeners there are, or whether they are currently running.

Regular intents are for one-to-one scenarios, where one component wants specific processing done on its behalf, but doesn't care/know if there is particular component capable of doing it, or whether such component is currently running.

Handlers on the other hand are for one-to-one sync/async scenarios where both parties are well known and are currently running.

How the above relates to your scenario

In the most simplistic implementation, you dont need intent or handler, you can directly call a method on on your service from the background Runnable, like this:

MyService.this.processIncomingDataAlert(inputBuffer);

Note that this will execute the method on the background thread, and will block the socket listener, while processing the data. Which gets us to the threading considerations you asked about.

If you want to unblock the socket listener, and/or process the data on the UI thread, you can create a Handler in the onStartCommand() and use it from the runnable to post another runnable back to the UI thread, like this:

myServiceHandler.post(new Runnable() {
    public void run() {
        MyService.this.processIncomingDataAlert(inputBuffer);
    }
};

Note that this will lead to race condition between when the UI thread processes the Runnable call to onIncomingData and the background thread listener which might update the inputBufffer, so you'll likely want to create a copy.

Of course, there's also now the issue of the processing happening on the UI thread, which is shared between the service and the activity. So, if you data processing is slow, it will impact the responsiveness of your UI.

If you want to make sure both the background socket listener -and_ the UI thread are responsive, you should process your data on (yet) another thread. You could start a new thread for each Runnable, but that will result in bunch of threads created quickly, and waste system resources. Not to mention that creating separate threads will lead to race conditions between processing multiple chunks of your data, which can make your app logic too complex.

Fortunately, Android offers AsyncTask for such scenarios.

AsyncTask has a pool of background threads, where it runs the Runnables scheduled through it. If you are running on GingerBread or lower, or on ICS or higher version, AsyncTask will also serialize them and run only one task at a time.

Read this article for a nice introduction on threads, handlers and AsyncTask. Note that the article shows an example with subclassing AsyncTask and implementing doOnBackground, but that's a bit of overkill for you; the static execute(Runnable) method will work just fine for you.

Is Service suited for your scenario

This question is somewhat orthogonal to the rest. You need a background thread to listen on the socket, that's given. but do you need a Service is not as clear.

Services are for scenarios where an application needs to do a background processing and does not need to engage the user through UI, or continue processing after the user switched to another application.

Thus, if your scenario requires you to listen on the socket only while your activity is shown on the screen and the user is actively engaged with it, you don't need a Service. You can just start the background thread from your activity and use handler to post back new data to it or to AsyncTask as discussed above.

However, if you do need to continue listening on the socket (whether you should is a separate topic :-)) after the user has closed your activity, you need the Service.

The main issue here is the process lifecycle in Android. In order to manage the device resources properly, the OS will kill processes it considers idle. A process is considered idle, if there is no activity or a service running in it. Mere starting a background thread is not enough to let the OS know the process is still busy. So, if you don't have a service, once you activity is closed, from the Android point view, your process is doing nothing, and it can kill it.

Hope this helps.

Upvotes: 4

Nikolay Elenkov
Nikolay Elenkov

Reputation: 52936

You don't really have to use a service. If it is all running in the same process, just make your network code a singleton, and have activities call methods directly. For notification, you could simply have activities implement a certain interface (onData(String data), etc.) and have them register with network class. Just take care to unregister when an activity goes away (onStop()). On the other hand, if the network code needs to run while no UI is visible (e.g., a different app is in the foreground), you need to use a service.

Messages and intents are actually different: you use handlers.messages to communicate with a particular thread, while intents are actually IPC -- they could be delivered to a different app/process. The benefit of using a broadcast receivers is that Android will create a process to handle the incoming intent if one is not currently running.

Don't know if it is the 'Tao', but generally:

  • if you need to run something without a UI, use a service (scheduled network IO, etc.)
  • if a UI is present, just have it run in a different thead (AscynTask offers a handy way to do this)
  • if you are communicating with your own app (in a single process), use handlers/messages
  • if you need cross-app (process) communication, use intents
  • if you need to handle some async event, and there is a chance that the app to handle it won't be running, use a broadcast receiver. Then you might want to start a service (IntentService does processing in a worker thread) to do the actual work if it takes sometime. Additionally, you may need to acquire a wakelock to keep the device from falling asleep again, while you are performing your work (network IO, etc.)

Upvotes: 1

Related Questions