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:
-
What’s the ‘Tao of Android’ in deciding when to use BroadcastReceiver/Intents or Handler/Messages?
-
Are there any cross-thread considerations when deciding which approach to use?
(And, though it’s off-topic, one final question):
- Is a Service suited to do the kind of socket-based I/O I’m trying to do?
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:
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:Note that this will lead to race condition between when the UI thread processes the Runnable call to
onIncomingDataand the background thread listener which might update theinputBufffer, 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
AsyncTaskfor 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 staticexecute(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.