Reputation: 305
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):
Upvotes: 0
Views: 1136
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
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:
AscynTask
offers a handy way to do this)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