In this chapter, we'll extend our toolkit by directly extending Service to take control of the level of concurrency applied to our long-running background tasks—how many threads are used to perform the work—and use various methods to send work to Services and receive results from them.
-
Building responsive apps with Service
Service does not provide any background threads and will run all of its callbacks directly on the main thread, just like Activity.
While it is possible to configure the service to launch in a separate process, that process will still run the Service callbacks on its own main thread and will be subject to the same constraints. The only difference is that our foreground process will not be shut down along with the misbehaving Service process.
we can only invoke IntentService via an Intent and itwill queue all work and process it on a single thread.
When we need more control over the level of concurrency
-
Controlling concurrency with Executors
we'll use Executor to create our own alternative to IntentService
We'll allow subclasses to define the level of concurrency by passing an Executor tothe constructor.
public abstract class ConcurrentIntentService extends Service {
private final Executor executor; public ConcurrentIntentService(Executor executor) {
this.executor = executor; }
protected abstract void onHandleIntent(Intent intent); }
@Override public int onStartCommand(
final Intent intent, int flags, int startId) { executor.execute(new Runnable(){
@Override public void run() {
onHandleIntent(intent); }
});
return START_REDELIVER_INTENT; }
we should be responsible and stop the Service when it has no more work to do.We'll need to keep track of how many tasks are running at any given time and whenthe last one completes, invoke stopSelf.
private final CompletionHandler handler = new CompletionHandler();
private int counter;
@Override public void onStart(final Intent intent, int startId) {
counter++;
executor.execute(new Runnable(){ @Override
public void run() {
try {
onHandleIntent(intent);
} finally { handler.sendMessage(Message.obtain(handler));
}
}});
}
private class CompletionHandler extends Handler { @Override
public void handleMessage(Message msg) { if (--counter == 0) {
Log.i(TAG, "0 tasks, stopping");
stopSelf(); } else {
Log.i(TAG, counter + " active tasks"); }
}}
-
Returning results with Messenger
The Android framework provides theMessenger class, which wraps upHandlerand makes it possible to send messages from anywhere—including from remoteServices in other processes.Messenger implements Parcelable, which means wecan pass Messengers around in Intents, which we can't do withHandler directly.
Intent intent = new Intent(this, PrimesIntentService.class); intent.putExtra(PrimesIntentService.PARAM, primeToFind); intent.putExtra(PrimesIntentService.MSNGR, messenger); startService(intent);
Sendingmessages withMessenger is very similar to sending them withHandler—we obtainaMessage with the appropriate parameters and then send it withMessenger.send:
@Override protected void onHandleIntent(Intent intent) {
int primeToFind = intent.getIntExtra(PARAM, -1);
Messenger messenger = intent.getParcelableExtra(MSNGR);
try { if (primeToFind < 2) {
messenger.send(Message.obtain(null, INVALID));
} else {
messenger.send(Message.obtain( null, RESULT, primeToFind, 0, calculateNthPrime(primeToFind)));
} } catch (RemoteException anExc) {
Log.e(TAG, "Unable to send message", anExc); }
}
private static class PrimesHandler extends Handler { private TextView view; public void handleMessage(Message message) {
if (message.what == PrimesIntentService.RESULT) { if (view != null) { // if we're attached
view.setText(message.obj.toString()); }
}}
public void attach(TextView view) { this.view = view;
} public void detach() {
this.view = null; }
}
-
Direct communication with local Services
An Activityor Fragmentthat wants to directly interact with this Service needsto bind to it using thebindService method and supply a ServiceConnection to handle the connect/disconnect callbacks.
The ServiceConnection implementation simply casts the IBinder it receives tothe concrete class defined by theService, obtains a reference to the Service, and records it in a field of theActivity.
public class LocalPrimesActivity extends Activity { private LocalPrimesService service; private ServiceConnection connection;
private class Connection implements ServiceConnection { @Override public void onServiceConnected(
ComponentName name, IBinder binder) {
LocalPrimesService.Access access = ((LocalPrimesService.Access)binder);
service = access.getService(); }
@Override public void onServiceDisconnected(ComponentName name) {
service = null; }
}}
We can make the Activity bind and unbind during its onResume and onPause lifecycle methods:
@Override protected void onResume() {
super.onResume(); bindService(
new Intent(this, LocalPrimesService.class), connection = new Connection(), Context.BIND_AUTO_CREATE);
}
@Override protected void onPause() {
super.onPause();
unbindService(connection); }
This is great—once the binding is made, we have a direct reference to the Service instance and can call its methods!
-
Broadcasting results with Intents
if the Activity and Service are a part of the same process, broadcasting is best done using a local broadcast, as this is more efficient and secure.
private void broadcastResult(String result) { Intent intent = new Intent(PRIMES_BROADCAST); intent.putExtra(RESULT, result);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
We only want this BroadcastReceiver to listen for results while our Activity is at the top of the stack and visible in the application, so we'll register and unregisterit in the onStart and onStop lifecycle methods.
-
Detecting unhandled broadcasts
Ideally, we'll display the results in the app if it is still in the foreground and send a notification otherwise.
If we're broadcasting results, the Service will need to know if anyone handled the broadcast and if not, send a notification.
-
Applications of Services