6. Services, Processes, and Binder IPC

Glendower: I can summon spirits from the vasty deep.

Hotspur: Why, so can I, or so can any man. But will they come when you do call for them?

Shakespeare, Henry IV

The Android Service component is a key resource for managing concurrent processes. Understanding it in some detail is essential to creating well-architected Android applications.

There are three reasons for using a Service in an application:

Image Architectural: It makes sense, architecturally, to separate business logic from the UI that presents it. In general, anything that gets code out of Activity objects is a great idea.

Image Process priority: Delegating a task to a Service can increase the priority of the process that hosts the Service. As discussed in Chapter 3, “The Android Application Model,” the process that hosts the visible Activity has the highest priority. A Service, though, can also change the priority of its host process by declaring to the framework that it is performing useful work. A higher process priority decreases the probability that the task will be interrupted, even when the process is not hosting a visible Activity.

Image Inter-process communication (IPC): A bound Service, described shortly, is the Android IPC tool.

The component called a Service is, actually, two different mechanisms, the started service and the bound service, wrapped up in a single package. Both mechanisms provide system-addressable components that can be used to execute tasks on other threads or even in other processes.

Service Basics

Whether started or bound, all Services are the same in some respects. One simple way of understanding them is as an Activity with no UI. That can sound a bit odd because an Activity’s sole purpose is to power a UI. A review of the features common to all Services, however, will demonstrate that the comparison is apt.

Like any other Android component, a Service, whether bound or started, must subclass its component class (Service in this case). Also, like other components, a Service must be registered in its application’s manifest as shown in Listing 6.1.

Listing 6.1 Declaring a Service


<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="net.callmeike.android.exampleservice"
  >

  <application>
    ...

    <service android:name=".ExampleService" />
  </application>

</manifest>


The Android framework reads an application’s manifest when the application is installed. It registers the components it finds there, including Services, along with their attributes, in a system-wide lookup table. They stay in the table until the application is uninstalled.

The exported attribute in the Service’s manifest declaration controls whether or not it is visible and can be used from other applications. By default, a Service is not exported and not visible externally. The Service in Listing 6.1, for instance, cannot be used by other applications. Either declaring the Service exported="true", or including an intent-filter (described below) in its declaration would make it externally visible.

Also, like other components, there is only one instance of a given Service at any time. An instance of the Service is created in response to the first request for it. Once created, the single instance responds to all subsequent requests—start or bind—until all those requests have been satisfied and the Service is either stopped or the last client unbinds. The Service instance is then destroyed. Subsequent requests create and use a new instance.

Like an Activity, a Service has a lifecycle that is implemented with callbacks. Unlike an Activity—and because a Service does not manage a UI—its lifecycle is fairly simple. It has fewer lifecycle states and also fewer callbacks. The four methods onCreate, onStartCommand, onBind and onDestroy are the essentials.

Because the Service class packages two different mechanisms, its interface is complicated by interface pollution. It contains the lifecycle methods for both interfaces. Although it can be useful to both start and bind a Service—a hybrid service—most implementations will not.

Also like an Activity, Service methods are always executed on the main thread of the process in which they run. It is very important to understand that a long-running task in a Service will stall the UI of the application hosting it, just as surely as would the same task running in an Activity. A Service with expensive methods must make arrangements to run those methods on a background thread.

Finally, like an Activity, the lifecycle of a Service affects the priority of the process that powers it. When a Service announces to the Android framework that it is performing work on behalf of a client, the priority of the host process will be higher than it would be if the process contained no working components.

The specifics of Service priority, especially bound Service priority, are fairly complex. As shown in Chapter 3, Figure 3.2, a process that contains an active Service will have priority that is lower (oom_adj value is higher) than that of the process that contains a visible Activity, but higher than that of a process that contains only Activities that are not visible. That just makes sense; when a process contains a Service that is doing useful work, it is more important than a process that isn’t doing anything. It is not, however, as important as the process that the user is currently viewing.

Process priority is among the most important reasons for using Services. Unless a long-running task is implemented in a Service, the Android system has no way of knowing that the task is running. It can lower the host process’ priority and even reap that process, before the task completes. Services are the best solution to this problem, which was described in detail in Chapter 4, “AsyncTasks and Loaders.”

Although a process that contains a running Service is less likely to be interrupted, there are no guarantees. There is a possibility, somewhat smaller on modern devices with their comparatively large memories, that a Service, even one that has announced that it is doing useful work, will be interrupted. The longer a task runs, the more likely it is to be interrupted.

Started Service Essentials

In general terms, a started Service is usually more autonomous. To start a Service, a client creates a small package of parameters, an Intent, and then uses the Intent as the argument to the Context method, startService. A Service receives the Intent asynchronously as an argument to its onStartCommand method. It performs the task represented by the Intent, frequently without any further interaction with the client. Like a method with void return type, started Services frequently do not return an explicit result. Figure 6.1 illustrates an Activity starting a Service, to perform an asynchronous task.

Image

Figure 6.1 A started Service


Note

Figure 6.1 does not show process boundaries. When discussing Services, process boundaries are frequently irrelevant. As long as the Service on the right side of the figure is exported, the Activity on the left can send an Intent to it, whether it is in the same, or a different, process


As mentioned previously, the Android framework can be forced to interrupt a Service to make room for some other application. A started Service uses the value it returns from its onStartCommand method to request the way it would like the Android framework to handle such an interruption. There are, essentially, three possibilities.

The first possibility is that onStartCommand returns the flag START_NOT_STICKY. This flag indicates to the framework that it need not take any additional actions. If it must interrupt the started Service, the Service is marked as stopped and the framework makes no effort to restart it.

At the other extreme, the onStartCommand can return the flag START_REDELIVER_INTENT. This flag indicates to the framework that if it must stop the Service, it should restart it when conditions permit.

When the framework interrupts such a Service, it marks the Service as stopped. The framework restarts the Service, though, by redelivering the same Intent that started it in the first place. A Service can detect that it has been restarted by checking the second parameter to onStartCommand, the flags. If it is being restarted, the START_FLAG_REDELIVERY is set. Using START_REDELIVER_INTENT is a simple way of restarting idempotent tasks.

The last possibility is slightly more complicated. If onStartCommand returns the flag START_STICKY then the Service, when interrupted, is terminated but it is not marked as stopped. Instead, the framework will, when conditions permit, recreate and rerun the Service by calling its onStartCommand method again. It will not redeliver the original Intent, however. When restarted, the Intent parameter to the onStartCommand call will be null.

Bound Service Essentials

Figure 6.2 illustrates the second type of Service, the bound Service. At first glance, a bound Service appears to be an implementation of the Factory pattern.

Image

Figure 6.2 A bound Service

To bind a Service, a client creates an Intent that is similar to one that might be used to start a Service. Instead of calling startService, though, it calls bindService. In addition to the Intent, it provides a reference to a callback handler, an implementation of the ServiceConnection interface.

The Service receives the Intent asynchronously, this time as the parameter to a call to its onBind method. In response to onBind it returns an instance of its managed object.

The Android framework, again asynchronously, invokes the onConnected method of the callback handler originally passed by the client. As a parameter to the call, it passes a reference to a managed object similar to the one returned from the onBind method. The client now has an instance of the Service-provided object and can interact with it in nearly the same way that it would any other Java object.

As a factory, a bound Service provides a level of abstraction between the type of the object returned to the client, and its actual implementation. In a second layer of abstraction, it is the Android framework that collects the result of the onBind call, and then passes that result—or something like it—in the call to onConnected. This will be important for one of the chief uses of a bound Service, inter-process communication, where the object’s implementation can be in a different process.

Intents

Both started and bound Services are selected by the Intent that is used to start them. The Intent must identify the Service that is the intended target of a call to startService or bindService. An Intent can do this in several ways. For an exhaustive description of Intents, their attributes, and their filters, refer to the Android documentation. Roughly, though, the mechanisms break down into two general categories: explicit and implicit.

An explicit Intent includes the name of a package (application) and the relative name of the specific class within that package that implements the Service. At best, an explicit Intent names exactly one Service in one specific application. At worst it names nothing at all.

Listing 6.2 demonstrates the construction of three identical explicit Intents (assuming the code is a fragment of an application named net.callmeike.android.exampleservice, that contains a Service named net.callmeike.android.exampleservice.svc.ExampleService).

Listing 6.2 Explicit Intents


Intent intent1 = new Intent(context, ExampleService.class);

Intent intent2 = new Intent();
intent2.setComponent(new ComponentName(
  context.getPackageName(),
  ExampleService.class.getName());

Intent intent3 = new Intent();
intent2.setComponent(new ComponentName(
  "net.callmeike.android.exampleservice",
  "net.callmeike.android.exampleservice.svc.ExampleService");


An important thing to notice here is that the explicit Intent is not retaining a reference either to the class object (ExampleService.class) or to the Context object passed to it. Although the simplest constructor in Listing 6.2, the one used to create intent1, takes both of these arguments, it uses them only to create name strings. As illustrated in the creation of intent3, it is entirely possible—though somewhat fragile—to create explicit Intents that name components in other applications, provided the names of those components are known.

An implicit Intent is an Intent that must be caught using an intent filter. An IntentFilter is an Android framework object that is used to match canonical Intent attributes: action, package, category, MIME type, and so on. IntentFilters for Services are created, as shown in Listing 6.3, as part of registering a Service, in its application’s manifest.

When a Service registers an IntentFilter, it becomes a candidate to receive any Intent that matches the filter specifications (it is exported). In the example in Listing 6.3, the service ExampleService is registering to receive Intents whose action attribute is net.callmeike.android.exampleservice.PING.

Listing 6.3 An IntentFilter


<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="net.callmeike.android.exampleservice"
  >

  <application>
    ...

    <service android:name=".ExampleService">
      <intent-filter>
        <action android:name="net.callmeike.android.exampleservice.PING" />
      </intent-filter>
    </service>
  </application>

</manifest>


As most Android users know, ambiguous Activity calls—a call to startActivity with an Intent that matches multiple Activites—are resolved by presenting the user with a system dialog and allowing her to choose among them. Think, for instance, of what happens when there are multiple browsers or multiple telephony apps installed on a device.

The disambiguation protocol for delivering in-flight Intents to a Service, on the other hand, is silent and, essentially, random. Because of this, it is wildly unsafe to use implicit Intents to launch Services. Although it has been unsafe since the origin of Android, it is only as of API 21, KitKat, that an Intent used in a call to startService must name a target application explicitly.

At the time of this writing, the Android documentation suggests that the Intent used to identify a Service must be an explicit Intent. Actually, that isn’t necessary. It is sufficient that the Intent specify a unique target application, by specifying its package name. It is possible to do this minimally, using the Intent method setPackage with a String argument that is the fully qualified package name of the target application.

Obviously, this still leaves open the possibility of ambiguous Services. An ambiguous Service within an application, though, is just a bug, not a security problem.

Also important to understand, in the context of concurrency, is that the Intent that a client uses to invoke a Service is never the Intent that the Service receives as a result of being invoked. In particular, the code in Listing 6.4 always produces output that looks like this:

...
01-10 16:58:51.102 1953-1953/...servicesandbox I/SVC: intent == originalIntent: false
...

Listing 6.4 Intent Identity


public class SimpleService extends Service {
  // ...

  private static Intent startIntent;

  @UiThread
  public static void start(Context ctxt) {
    if (null != startIntent) {
      throw new IllegalStateException("start called twice");
    }
    startIntent = new Intent(ctxt, SimpleService.class);
    ctxt.startService(startIntent);
  }

  // ...

  @UiThread
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(TAG, "intent == originalIntent: " + (intent == startIntent));
    startIntent = null;
    startWork();
    return super.onStartCommand(intent, flags, startId);
  }

  // ...
}


There are two consequences to this. The first is that a Service need not be concerned about whether there are external references to the Intent that is passed to it. The Intent that the Service receives is a deep copy of the one the client used in its call to startService or bindService. The Intent to which the Service holds a reference is its own and the Service can use it without thread safety concerns.

The second consequence is that the Intent must be an object of which the Android system can make a deep copy. In Java the Cloneable interface is the canonical way of indicating that an object can be replicated. Android Intents are Cloneable but they actually support an even stronger contract, Parcelable. Objects that support the Parcelable contract can be transferred between Android processes.

Java’s contract for objects that can be transferred between processes is the Serializable interface. A serializable object can be stored for reconstitution at some other place and time through a process called marshaling. The marshaled representation of the object can be unmarshaled in its new environment to create a new, different object that is effectively identical to the original.

The Parcelable interface, discussed in detail below, is Android’s lightweight equivalent of Java Serializable. Clearly, an object that can be marshaled and unmarshaled can be copied. Because they are Parcelable, though, Intents can also be passed across process boundaries.

The Intent Service

The IntentService is probably the most common way of using a started Service. It is a simple, attractive, and elegant extension of the basic Service class that provides exactly the functionality that developers are actually looking for when they turn to AsyncTasks.

Recall from the previous section that unless the implementation of a Service specifically arranges an alternative, all its code is run on the UI thread. That makes it pretty useless as a way of implementing long-running tasks. Once again, the problem is getting tasks off the UI thread.

Chapter 3 discussed Android’s naïve tool for concurrency, the AsyncTasks, and described the many problems with using them in an Activity context. It will be instructive, though, to reconsider them here, in the context of a Service.

Listing 6.5 shows a Service that executes tasks that are invoked with calls to onStartCommand, but on a background thread.

Listing 6.5 The AsyncTask Service


public class AsyncTaskService extends Service {
  private int running;

  // ...

  @UiThread
  public int onStartCommand(Intent intent) {
    running++;
    new AsyncTask<Intent, Void, Void>() {
      @UiThread protected void onPostExecute(Void result) { finishTask(); }
      @UiThread protected void onCancelled(Void result) { finishTask(); }
      @WorkerThread protected Void doInBackground(Intent... intent) {
        executeTask(intent[0]);
        return null;
      }
    }.execute(intent);
    return Service.START_NOT_STICKY;
  }

  @UiThread
  void finishTask() {
    if (0 >= --running) { stopSelf(); }
  }

  @WorkerThread
  void executeTask(Intent intent) {
    // do task work...
  }
}


First of all, note that there is nothing constraining AsyncTasks to use solely in Activities. They work perfectly well in other components.

The AsyncTask in Listing 6.5 is simply a portal to a background thread. When a client calls the startService method, the parameter Intent is passed to the Service in the call to onStartCommand. The implementation of onStartCommand, in the AsyncTaskService immediate forwards that Intent to a new, anonymous instance of AsyncTask and then executes that instance.

As described in Chapter 3, this will cause the AsyncTask method doInBackground to run on a background thread. In the AsyncTaskService, the anonymous AsyncTask’s doInBackground method in turn calls the Service method executeTask.

What does this accomplish? Well, the Service method executeTask is now nearly identical to onStartCommand except that it is running on the AsyncTask’s worker thread. It is now an excellent place to execute a long-running task.

It gets better. The call to the Service method onStartCommand has a side effect: It causes the framework to mark the Service as “started.” This announces to the Android framework that this process is doing useful work. The framework, in response, will keep the priority of the process hosting the started Service higher than that of a process that is idle. Because of this, the AsyncTask running in this Service has a much better chance of running to completion than the same task running in a process within an Activity that is no longer visible.

The Android framework can easily tell when an Activity is no longer needed. When it is no longer visible, it is no longer doing useful work. There is no analogous way of knowing that a started Service is no longer useful. Once a Service is marked as started, it stays “started” until it is explicitly stopped. In order to be a good neighbor—releasing resources when they are not being used—either the Service’s client must call stopService or the Service must call its own method, stopSelf. These calls announce to the framework that the Service is done with useful work, thus telling the framework that it can reduce the priority of the hosting process.

The AsyncTaskService manages its state by maintaining a count of running tasks. It increments the count when a new Intent arrives in onStartCommand, and decrements it again in the AsyncTask method onPostExecute when a task completes. Because both of these methods run on the main thread, there is no need to synchronize access to the counter. When the counter reaches 0, the Service calls stopSelf to declare to the framework that it is no longer doing useful work and that instance can be destroyed and the host process priority lowered.


Note

Listing 6.5 and the discussion around it are intended only as an example. The code is not complete. It will, for instance, miscount aborted tasks.


AsyncTaskService has exactly the features we’ve been looking for in a background execution mechanism. It runs tasks on a worker thread and it adjusts process priority to reduce the chance that the Android framework will interrupt useful background work. Furthermore, in the isolated environment of a Service, far from the UI machinery, there is much less chance of accidentally sharing state across threads.

A Service that provides all these features seems like a valuable thing. Android’s designers realized the importance of such a tool and, in API level 3, Cupcake, introduced the IntentService—a service with very similar functionality—into the Android library.

An IntentService behaves almost exactly as does the AsyncTaskService, though it is implemented a bit differently. It runs tasks in its canonical method onHandleIntent, on a background thread. Like the AsyncTaskService, it declares itself not sticky, so that Intents are delivered to onHandleIntent only once and are not null. The one very significant difference is that instead of being powered by an AsyncTask and the ThreadPoolExecutor that backs them, the IntentService is powered by a single, private Looper and its worker thread.

There are several consequences to this. The first is that tasks that run on the worker thread of an IntentService can share state. Since they are all guaranteed to run on the very same thread, no state is being shared between threads. Clearly, the Service lifecycle methods, onCreate, onStartCommand, onDestroy, and so on, all of which are run on the main thread, must be careful not to share state with the tasks. The tasks themselves, though, are free to share with each other, without concern.

More significant, though, is that tasks execute in order in an IntentService. As each new Intent appears in a call to onStartCommand, it is enqueued on the Looper’s message queue, eventually dequeued in order, and presented on the background thread as an argument to the onHandleIntent method. Only after that method processes the Intent to completion and returns, can the next Intent be dequeued for a subsequent call to onHandleIntent.

As noted in the discussion of AsyncTasks, sequential execution is frequently an advantage. It is much easier to reason about groups of sequential tasks than about tasks that are all mutually asynchronous. Conceptually, for instance, just because network tasks can happen out of order, with respect to UI tasks, network and UI tasks need not happen out of order with respect to each other. Indeed, the network task that logs a user in to her account must happen before the task that downloads her data. Allowing those things to happen out of order is not an advantage.

Architectures using multiple IntentServices for different classes of task, asynchronous with respect to one another but internally sequential, make a lot of sense. An IntentService is, however, not the right tool for an architecture that requires completely asynchronous task execution. A custom Service modeled on the AsyncTaskService, however, might be.

IntentServices are frequently called via static methods that are part of the Service class. These methods typically take as arguments a Context and any parameters that need to be passed to the task. They marshal the parameters into an Intent and then call startService, passing the new Intent. Both of the common IDEs, Eclipse and Android Studio, will generate this idiom from a code template. The static methods hide the details of Intent construction from the client and provide a fairly elegant way of invoking Service functionality—as elegant as any static reference can be. Listing 6.6 is an example of such a helper method.

Listing 6.6 An Intent Service Helper Method


public class CookieService extends IntentService {
  // ...

  private static final String ACTION_EAT
    = "net.callmeike.android.samplesvc.svc.action.EAT";
  private static final String EXTRA_PARAM_COOKIE
    = "net.callmeike.android.samplesvc.svc.extra.COOKIE";

  // ...

  public static void startActionDoIt(Context ctxt, String cookie) {
    Intent intent = new Intent(context, SampleService.class);
    intent.setAction(ACTION_EAT);
    intent.putExtra(EXTRA_PARAM_COOKIE, cookie);
    context.startService(intent);
  }

  // ...
}


Probably the most significant constraint on the IntentService is how difficult it is for it to return a result. While the Intent that starts the Service can carry, with some restrictions, task parameters, it is not immediately obvious how the IntentService might return a result. The typical Android solution, of course, is some kind of callback. In this case, perhaps this could be accomplished using a Handler to pass the result back to the main thread and then invoking a method on a passed callback handler from there. Hold that thought for the discussion of messengers later on.

Bound Services

Bound services have several appealing features. Within a single process, calls to the object managed by a bound Service are several orders of magnitude faster than sending Intents to an IntentService. Also, with a bound Service, it is much easier to create a bi-directional API: one in which methods return values. Their most important feature, though, is that the managed objects returned by Services can run not just on another thread, but also in another process.

Bound Services are among the most complex things that an Android application developer will ever encounter. For a moment, let’s continue to gloss over most of that complexity and explore a very simplistic bound service client.

As previously noted, a bound Service looks at first like a factory for a managed object. Some closer examination, though, will reveal that this model is only partially accurate.

A Simple Bound Service

Recall from Figure 6.2 that a client initiates the binding process by calling the Context method bindService. In that call, it passes an Intent that identifies the target Service and a reference to a callback handler, an instance of ServiceConnection. It also passes some flags (to be explored later). Listing 6.7 shows the code.

Listing 6.7 A Simple Bound Service Client


public class SimpleActivity
  extends AppCompatActivity
  implements ServiceConnection
{
  @Override
  public void onServiceConnected(ComponentName name, IBinder binder) {
    setService(((LocalService.ServiceBinder) binder).getService());
  }
  @Override
  public void onServiceDisconnected(ComponentName name) {
    setService(null);
  }

  @Override
  protected void onStart() {
    super.onStart();
    bindService(
      new Intent(this, LocalService.class),
      this,
      BIND_AUTO_CREATE);
  }

  @Override
  protected void onStop() {
    super.onStop();
    setService(null);
    unbindService(this);
  }

  // ...

  private void setService(LocalService svc) {
    service = svc;
    addButton.setEnabled(svc != null);
  }
}


If the Android framework cannot successfully deliver the parameter Intent to some Service, the call to bindService returns a boolean false. This could happen either because the Intent does not match any Service registered with the system or because the system fails to start the application containing the registered Service. If bindService returns false, the binding process is aborted.

If the system succeeds in locating a bindable service, the call to bindService returns true. Asynchronously, the target Service receives a copy of the Intent sent by the bindService call, as the parameter to a call to its onBind method. The onBind method eventually returns an object, and the Android framework, again asynchronously, delivers it (or, at least, something very similar) as the parameter to a call to the onServiceConnected method of the callback handler (the second argument to the bindService call).

Just as with the IntentService, application code that binds a Service is responsible for releasing unused resources when it is done with them and assuring that the Service is unbound when it is no longer needed. The client unbinds the Service by calling the Context method unbindService and passing the callback handler that was used to bind it. If it is the last client to unbind—if there are no other bound clients—the framework calls the Service’s onUnbind method.

Binding A Service

Client code must be able to construct an Intent that names the Service it wants to target. As previously mentioned, post Marshmallow, API 23, the client code must know, at the very least, the exact package name for the application containing that Service. In addition, it must know how to match the target Service’s IntentFilter if there is one. If the target Service has not declared an IntentFilter, the client must know the exact name of the class that implements the Service.

In support of this requirement, many developers that create Services meant for public use document the Service API in a file called a contract. A contract is a Java source file that defines symbols for the constants necessary to use a Service. The file must not contain references to application-internal symbols or types. It can be distributed independently, perhaps as a web site download or via e-mail.

Client applications include the contract file in their source and use the symbols it defines—and, hopefully, the documentation it provides—to interact with the public Service correctly. Android documentation recommends using a contract to publish the interface to a ContentProvider. They are equally useful for publishing bindable Service APIs.

The contract is a good idea for a far more significant reason. Notice that the onServiceConnected method—the method called to deliver the Service-managed object to the client—takes as a parameter an object of type IBinder. This is the beginning of the divergence between the Service API and the standard Factory pattern.

Unlike most Java implementations of the Factory pattern, Services are neither synchronous nor type-safe. The client receives the instance of the Service-managed object as a callback, not as the return from the call to the factory method. Furthermore, the Service onBind method returns a Binder object and the client receives an IBinder. It is the client’s responsibility to know exactly what kind of object to expect and to cast the IBinder it receives to the correct Java type.

An improvement on the Service-as-factory metaphor is suggested by the way that the Service onBind method is called. Between the time an instance of a given Service is created and the time it is destroyed, its onBind method is called only once for all Intents that are equals in the Java sense.

For example, all explicit Intents that target some given Service are equal—they all contain the name of the same package and the name of the same class within that package. Because they are the same, only the first attempt to bind a Service with an explicit Intent will produce a call to the Service’s onBind method.

Subsequent calls to bindService do not cause onBind to be called again. Instead, the Android framework simply returns the object obtained from the first call to onBind. Perhaps a Service is more like a lazy singleton than a factory.


Note

An architectural anti-pattern that was common in enterprise software around the turn of the century involved systems built out of large singletons, frequently called “Managers.” Such systems were monolithic and object-oriented in name only. They were difficult to test and abandoned any advantages that O-O programming might provide.

Steve Yegge wrote an excellent and funny essay about singletons and the architectures they inspire in his blog at https://sites.google.com/site/steveyegge2/singleton-considered-stupid.

If you were a developer during that era, the discussion of Android’s Services might make you architecturally uncomfortable.


Unbinding A Service

Even the Service-as-a-singleton model, though, does not hold up. It collapses with the discovery of the unbindService method. Singletons are not usually subject to destruction. The very existence of the unbindService method is curious and requires closer examination.

First, note that bindService and unbindService are stateful methods on an instance of the type Context. When a Service is bound, the binding Context remembers the binding. Only that Context can unbind that connection. The code in Listing 6.8, for instance, generates the following error:

...
java.lang.IllegalArgumentException: Service not registered:
  net.callmeike.android.servicedemo.MainActivity@6ccc2eb
...

Listing 6.8 Unbinding from the Wrong Context


@Override
protected void onStart() {
  super.onStart();
  bindService(new Intent(this, LocalService.class), con, BIND_AUTO_CREATE);
}

@Override
protected void onStop() {
  super.onStop();
  getApplicationContext().unbindService(con); // !!! ERROR!
}


The problem is that the bindService method, in Listing 6.8, is called on the Activity, which is a Context. The unbindService method, however, is called on the application context. The application context is, surely, a Context but it is not the same object as the Activity. The application context has no record of the connection so the attempt to unbind fails.

There is an interesting corollary that arises when an Activity binds a Service. Because the binding belongs to a particular Context—an Activity—and because the framework controls the lifecycle of that Context, it knows when it destroys the Activity that any binding that has not been unbound can never be unbound correctly. When an Activity is destroyed without unbinding a Service that it has bound, the framework generates an error message like this:

 ...
Activity net.callmeike.android.servicesandbox.MainActivity has leaked
  ServiceConnection net.callmeike.android.servicesandbox.MainActivity@1d77f05
  that was originally bound here
...

Binding a Service affects the priority of the process that hosts the Service. The framework expects a component that binds a Service to manage that binding explicitly and to unbind it when it is no longer needed. If it does not, the framework signals an error.

The most important thing about the unbindService method, though, is that it probably has no immediate effect on the object whose reference was passed to the client in its onServiceConnected method. One might reasonably expect that unbinding a Service—particularly if that Service is in a remote process—would sever the connection to the remote object. It does not.

This leads to yet another analogy for a bound Service. A bound Service is like a Java SoftReference to a singleton. When the Service is bound, the framework promises to notify the client whenever it makes a change in the reference. When it sets the reference to point to a valid object, it calls onServiceConnected. When it sets the reference to null, it calls onServiceDisconnected.

When the Service is no longer bound to a callback handler, however, the framework is not under any obligation to notify anyone. The reference provided by the Service does not become invalid. The framework might make it invalid, though, without notifying the client.

When no client is listening to changes in the Service status, the framework assumes that no one cares about it. When clients are listening for changes in status, the framework assumes that the Service is doing work on their behalf and that the Service should not be destroyed unless it is necessary. If it must destroy the Service, it notifies the clients and tries to recreate the Service as soon as possible. Just as an Activity is considered useless if no one can see it, though, bound Services are considered useless if no one is bound to them. The Service object can be destroyed and the host priority lowered.

Binding Multiple Services

Unlike many of the other callback handlers in the Android universe (View.onClickHandler, LoaderManager.LoaderCallbacks), a ServiceConnection can manage only a single binding. Using the Context that binds the Service (often an Activity) as the callback handler, as illustrated in Listing 6.7, is a common idiom. It is an idiom, though, that enables binding only a single Service connection. Connecting to multiple Services requires multiple callback handlers, as shown in Listing 6.9.

Listing 6.9 Binding Multiple Services to a Single Context


public class Bind2Activity extends AppCompatActivity {
  private ServiceConnection con1 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      svc1 = (((LocalService1.ServiceBinder) binder).getService());
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
      svc1 = null;
    }
  }

  private ServiceConnection con2 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      svc2 = (((LocalService2.ServiceBinder) binder).getService());
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
      svc2 = null;
    }
  }

  // ...

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);

    bindService(
      new Intent(this, LocalService1.class),
      con1,
      BIND_AUTO_CREATE)
    bindService(
      new Intent(this, LocalService2.class),
      con2,
      BIND_AUTO_CREATE)
  }

  // ...
}


Service Lifecycle

The end of the Service-as-a-singleton metaphor comes with the realization that a Service requires careful cooperation from its clients to prevent them from getting more than one copy of the object it manages.

The Android framework calls the onBind method of a Service only once during the lifetime of an instance of that Service, for each equivalent Intent. On the other hand, the lifetime of the Service object is, from the point of view of its client, nearly random. Different instances of a single Service can easily return different managed objects.

Consider the minimal Service in Listing 6.10. The MinimalService returns a new instance of its managed object ManagedObject, for each call to onBind. Because onBind is called only once for the explicit Intent used to bind MinimalService, one might be forgiven for thinking that ManagedObject was a singleton.

Listing 6.10 Minimal Bound Service


public class MinimalService extends Service {

  @Override
  public IBinder onBind(Intent intent) {
    return new ManagedObject();
  }
}


It is not. As Figure 6.3 illustrates, a client can quite easily end up with references to two distinct instances of the managed object.

Image

Figure 6.3 Service-managed objects are not singletons

The client’s second call to bindService in Figure 6.3 happens to be forwarded to a different instance of the Service from the first. The framework does not have a cached instance of the managed object—it was cleared when the Service was destroyed—so it calls through to onBind.

The onBind method creates a new object and returns it. The client that incorrectly did not forget the first instance of the managed object when it unbound the Service, now receives a reference to a second instance. Clearly, the client code has a bug. Nonetheless, it now holds two references to distinct “singletons.”

Of course, in addition, there are lifecycles. Even after repairing the code in Listing 6.10 so that the managed object is a singleton (shown in Listing 6.11), expecting singleton-like behavior can still lead to surprises.

Listing 6.11 Singleton Bound Service


public class SingletonService extends Service {
  private static ManagedObject managedObject;
  private static int counter;

  @Override
  public IBinder onBind(Intent intent) {
    if (null == managedObject) {
      managedObject = new ManagedObject(++counter);
    }
    return managedObject;
  }
}


This code is attempting to count the number of instances of ManageObject created by the Service. It is entirely possible, though, that a client will see that count suddenly drop from some large number to 1.

If the Service process is stopped and restarted, even its static variables are reinitialized. When a client and a Service are in different processes, the Service can lose all its state, even though the client does not.

When a client is bound to a Service and the Service must be killed, the framework restarts the Service as soon as it has an opportunity to do so. The client will receive first a call to onServiceDisconnected and then a call to onServiceConnected. In the case of the SingletonService in Listing 6.11, regardless of what the value of counter had been previously, it will now be 1.

It turns out that Services are just not a precise match for any common architectural pattern. Using them correctly requires understanding them and their specific, unique behavior.

Priorities and Flags

The flags supplied as the third argument to the call to bindService are an incomprehensible hairball. They fall, more or less, into three categories: those that affect the connection process, those that affect the priority of the process that hosts the bound Service, and WTF. The flags are a bitset and so can be OR-ed together. Specifying more than one flag from each of the three groups, though, is probably not very meaningful.

In the first category, a flag that affects the connection process is the most important flag of all, BIND_AUTO_CREATE. Nearly every call to bindService should specify this flag.

When specified, this flag tells the framework to start the host process if it is not already running, to create an instance of the Service class if none exists, and to call the onBind method of the now-running instance.

It can be amusing to consider what happens when bindSerive is called without the BIND_AUTO_CREATE flag. Even when the call to bindService returns true (indicating that the framework has successfully located a Service to which to bind) there might be no subsequent call to the callback handler. Milliseconds, seconds, minutes, even hours can go by without a call to the callback handler.

Suppose, though, that for some reason, hours hence, other code makes a call to startService with an Intent targeting the same Service. Because an instance of the Service is created and started in response to that call, the binding client’s callback handler will, hours after it called bindService, suddenly receive the connection. This is so weird that it seems inevitable that someone will find a problem to which it is the perfect solution.

In the WTF category is the BIND_DEBUG_UNBIND flag.

The remaining flags BIND_NOT_FOREGROUND, BIND_ABOVE_CLIENT, BIND_ALLOW_OOM_MANAGEMENT, BIND_WAIVE_PRIORITY, BIND_IMPORTANT, and BIND_ADJUST_WITH_ACTIVITY all control the priority of the process hosting the bound Service. With the exception of BIND_NOT_FOREGROUND, they were all introduced in API 14, Ice Cream Sandwich. While the documentation describes them in some detail, their exact meanings are complex and quite difficult to uncover.

A Local Bound Service

Before moving on to the discussion of inter-process communication there is one more subject to address. It is, perhaps, slightly out of order to introduce here an optimization for inter-process communication before discussing IPC itself. It is an important optimization, however, and describing it here will keep it from interrupting the discussion of IPC to come.

Process boundaries have been largely ignored so far in this chapter. None of the illustrations include them. None of the code mentions them. The obvious assumption—true for most applications in general and even most of the code in most Android applications—is that everything is running in a single process. That is not the case for the Services. Most of the discussion and code in this chapter applies, whether the Service and its client are in the same or different processes. Services are nearly blind to process borders.

Just for a moment, though, consider a Service that is known to be hosted in the same process as its client. In this constrained case, there is a trick that can be used to eliminate most of the overhead of communication with the Service-managed object that is imposed by the IPC framework. It is slightly unfortunate that a Service—the tool for blurring the distinction between in- and out-of-process communication—is often used in a way that constrains it to a single process. The trick, however, has such a dramatic effect on performance that within a process, a developer would be crazy not to use it. Listing 6.12 demonstrates the trick.

Listing 6.12 Local Service


public class LocalService extends Service {
  public class ServiceBinder extends Binder {
    public LocalService getService() { return LocalService.this; }
  }

  @Override
  public IBinder onBind(Intent intent) {
    return new ServiceBinder();
  }

  public Foo doSomething(Bar bar) {
    // ...
  }
}


The inner class, ServiceBinder, has two essential attributes. Since it extends Binder, it implements the IBinder interface and can be returned by the onBind method. A Binder is a large, heavyweight object that implements IBinder and has many super powers. In this particular case, though, those powers are all pretty much irrelevant. Because the Service and its client are in the same process, the client callback handler will receive, as the argument to its onServiceConnected method, exactly the object that onBind returns. Note that this is not the case when the client and the Service are in separate processes!

Because ServiceBinder is an inner class of LocalService, it holds a reference to the Service. That’s just Java. It can expose that reference and does so in its getService method. When the client receives the callback, it simply casts the passed IBinder parameter as the LocalService.ServiceBinder it knows it to be and then calls the getService method. It now holds a reference to the LocalService object. Listing 6.13 demonstrates a LocalService client.

Listing 6.13 Local Service Client


private LocalService service;

// ...

@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
  service = ((LocalService.ServiceBinder) binder).getService();
}

// ...


This is an idiom. In the most common expression of this idiom, exemplified in Listing 6.12, the Binder that is passed between the onBind and onServiceConnected methods holds a reference to the Service itself. That’s just because it is easy to do. The Service is not only the factory for the managed object, but also the managed object that the factory returns.

This is by no means necessary, and it can make testing quite difficult. Constructing unit tests for the client-visible methods on the LocalService, for instance, requires mocking a Service. That will make it difficult to run the tests except in an environment that can instantiate a Service: an Android device, or perhaps Robolectric.

The ServiceBinder, however, can return any object it wants. A more reasonable take on this idiom is that the ServiceBinder’s getService method returns a reference to an object that has been injected into the Service using one of the IOC frameworks. Listing 6.14 is an example of this technique, using Dagger2.

Listing 6.14 Service Injection


public class LocalService extends Service {
  public class ServiceBinder extends Binder {
    private final ServiceProvider provider;
    ServiceBinder(ServiceProvider provider) { this.provider = provider; }
    public ServiceProvider getService() { return provider; }
  }

  private static ServiceBinder serviceBinder;


  @Inject ServiceProvider provider;

  @Override
  public void onCreate() {
    super.onCreate();
    if (serviceBinder == null) {
      DaggerProviderComponent.create().inject(this);
      serviceBinder = new ServiceBinder(provider);
    }
  }

  @Override
  public IBinder onBind(Intent intent) {
    return serviceBinder;
  }
}


Inter-Process Communication

This chapter has alluded several times to the fact that a Service can run in a completely different process from it client, and that the Service is Android’s tool for inter-process communication. The entire discussion of Services is just the groundwork for understanding how to execute a task that is not just on a different thread, but in a different process.

Java’s standard for inter-process communication is the Serializable interface. Android supports Java serialization but introduces a new and simpler contract, Parcelable, for objects that it must serialize. In addition to the Parcelable interface, the Android tools suite includes a compiler for Android’s inter-process communication language, Android Interface Definition Language (AIDL).

Figure 6.4 illustrates the Android IPC mechanism.

Image

Figure 6.4 Binder IPC

Like most IPC mechanisms, Android’s is divided conceptually into two pieces, a client side and a server side. These two sides are called, respectively, the proxy and the stub. The client thinks that it is talking to an instance of some object. It obtained this object from a factory, though, and the factory slyly returned, not an instance of the required object, but a proxy to it. The proxy looks as much as possible like the remote object. In Java terms, that probably means that it implements the same interface.

Instead of actually containing the implementation code, the proxy’s responsibility is to marshal calls into messages. The messages are transferred to the remote process. In Android, this transfer is handled by a module in the Linux kernel, called Binder.

Binder copies the marshaled message into the target process’s memory space, where it hands it to the stub. It is the stub’s job to unmarshal the message and make a call to the actual implementation of the object: the thing that the client thought it was calling.

If the call returns a value, the process takes place in reverse to transfer the return value back to the client.

Parcelables

A Parcelable object is an object that implements s specific API. To begin, it must implement the Parcelable interface. That interface requires the implementation of two methods, describeContents and writeToParcel. In addition, a Parcelable object must have, exactly, a public static member named CREATOR, that is an implementation of Parcelable.Creator for the Parcelable type.

The writeToParcel method and the CREATOR are, respectively, the marshaler and unmarshaler.

When the framework needs to marshal a Parcelable object, it holds a reference to that object. It simply calls the object’s writeToParcel method, passing a Parcel. The object is responsible for marshaling its state into the passed Parcel.

When the framework needs to unmarshal from a Parcel, it reads tokens from the Parcel. A token in the Parcel identifies the type of the next object to be unmarshaled. From that token, the framework can identify the Java class of the object to be unmarshaled. Since CREATOR is a public static member of the class, the unmarshaler can easily obtain a reference to a Parcelable.Creator for the object to be unmarshaled. The framework calls the Creator’s createFromParcel method passing the Parcel. The Creator is responsible for reading the previously marshaled state from the Parcel and returning a new object.

The Android documentation on Parcelables is clear and fairly complete. There are a few tips, however, that can be useful.

First, the usual implementation of createFromParcel is a call to a constructor. In order to keep the marshaling and unmarshaling code near each other, many developers use a special package-protected constructor that takes the Parcel as its only argument. Others favor reading the Parcel in the createFromParcel method, and then calling a constructor that completely specifies object state.

Next, developers might find it surprising that state is unmarshaled from a Parcel in the same order in which it was marshaled. If writeToParcel writes a String and then an int, createFromParcel will read a String and then an int.

Finally, although the Parcelable API makes marshaling and unmarshaling Parcelables very fast (typically about twice as fast as Java Serialization on the Android platform), it makes them nearly impossible to sub-class. Architecting a cross-process API that minimizes the need for inheritance in Parcelables will prevent many headaches.

Messengers

Android’s Messenger class has come up twice already. Messengers are the reason for the remote fields in the Message class, described in the “Gory Details” section of Chapter 5, “Loopers and Handlers.” They are also the way of returning a result from an IntentService alluded to previously in this chapter.

A Messenger is, essentially, a Parcelable reference to a Handler. A Handler enables an external thread to enqueue a Message for a Looper’s worker thread. A Messenger extends that capability, enabling code to pass a reference to a Handler anywhere that it can pass a Parcelable. In particular, passing a Messenger in an Intent, for example, enables the recipient of the Intent, possibly in a different process, to enqueue Messages for local processing. Listing 6.15 illustrates.

Listing 6.15 Using a Messenger


public class MessengerServiceHelper {
  public interface MessengerCallbacks { void onComplete(String val); }

  public static final String SERVICE_PACKAGE
    = "net.callmeike.android.messengersandbox";
  public static final String ACTION_SEND
    = "net.callmeike.android.messengersandbox.svc.action.SEND";
  public static final String EXTRA_PARAM_BASE
    = "net.callmeike.android.messengersandbox.svc.extra.BASE";
  public static final String EXTRA_PARAM_MESSENGER
    = "net.callmeike.android.messengersandbox.svc.extra.MESSENGER";
  public static final String EXTRA_RESPONSE
    = "net.callmeike.android.messengersandbox.svc.extra.RESPONSE";

  public static final int WHAT_REPLY = -1;

  public static void startActionSend(
    Context context,
    String base,
    final MessengerCallbacks callbacks)
  {
    Messenger messenger = new Messenger(new Handler(Looper.myLooper()) {
      @Override
      public void handleMessage(Message msg) {
        switch (msg.what) {
          case WHAT_REPLY:
            Bundle resp = (Bundle) msg.obj;
            callbacks.onComplete(resp.getString(EXTRA_RESPONSE, ""));
            break;
          default:
            super.handleMessage(msg);
        }
      }
    });

    Intent intent = new Intent();
    intent.setPackage(SERVICE_PACKAGE);
    intent.setAction(ACTION_SEND);
    intent.putExtra(EXTRA_PARAM_BASE, base);
    intent.putExtra(EXTRA_PARAM_MESSENGER, messenger);
    context.startService(intent);
  }
}


Consider a client in one application that wants to use the MessengerService, which is part of an entirely separate application called net.callmeike.android.messengersandbox. It does so by creating an instance of the MessengerServiceHelper and calling its method startActionSend.

The magic is in startActionSend. It creates a Messenger wrapping a Handler for the thread on which the method is called (Looper.myLooper), adds it to an Intent, and then launches the Intent.

The MessengerService is shown in Listing 6.16. It receives the Intent sent by the helper method as a parameter to the call of its onHandleIntent method, retrieves the two parameters that the helper method included, and calls handleActionSend with them.

Listing 6.16 A Messenger Service


public class MessengerService extends IntentService {
  private static final String TAG = "MessengerSvc";

  public MessengerService() {
    super(TAG);
  }

  @Override
  protected void onHandleIntent(Intent intent) {
    if (intent == null) { return; }

    final String action = intent.getAction();
    switch (action) {
      case MessengerServiceHelper.ACTION_SEND:
        handleActionSend(
          intent.getStringExtra(
            MessengerServiceHelper.EXTRA_PARAM_BASE),
          (Messenger) intent.getParcelableExtra(
            MessengerServiceHelper.EXTRA_PARAM_MESSENGER));
        break;
      default:
        Log.i(TAG, "unexpected action: " + action);
    }
  }

  private void handleActionSend(String base, Messenger messenger) {
    Bundle resp = new Bundle();
    resp.putString(MessengerServiceHelper.EXTRA_RESPONSE, base + " roundtrip!");
    try {
      messenger.send(
        Message.obtain(null, MessengerServiceHelper.WHAT_REPLY, resp));
    }
    catch (RemoteException e) {
      Log.e(TAG, "reply failed: ", e);
    }
  }
}


handleActionSend performs its task (the trivial task of concatenating the word “roundtrip” to the passed string, in this case), and then prepares to return its result. Returning the result should recall Chapter 5: the code obtains a Message from the pool, and then uses the passed Messenger to enqueue it for processing by a Looper.

There is one detail here that is worthy of note. Unlike the Handlers in Chapter 5, this Messenger is about to send a Message between processes. It must be able to marshal and unmarshal the object that it sends. It follows, therefore, that if the Message field obj contains a reference to an object, that object must be Parcelable. In fact, the Messenger method send verifies this by checking the type of the object to which obj refers.

In the example, the return value is a String. Although a String is one of the primitively parcelable types, it does not implement Parcelable. An attempt to send a String with a Messenger will fail with a message like this:

E/AndroidRuntime: FATAL EXCEPTION: IntentService[MessengerSvc]
Process: net.callmeike.android.messengersandbox:service, PID: 2106
java.lang.RuntimeException:
    Can't marshal non-Parcelable objects across processes.

As the example demonstrates, though, avoiding this error is trivial. Android’s Bundle type does extend Parcelable, and it knows how to marshal a String. Simply wrapping the return value in a Bundle fixes the problem.

Returning to Listing 6.15, the Message sent from the Service is dequeued back in the client process and passed as the argument to a call to the Handler’s handleMessage method. That method parses the message and calls the previously registered callback handler with the result.

As nifty as this is, do not let it cause you to forget the lifecycle problem. If the passed callback Handler is a reference to an Activity, for instance, that Activity cannot be released for garbage collection until the Handler to which it refers, is collected. That happens only after the Message that refers to it is removed from the Looper queue. If the Activity has been destroyed before that, of course, the Activity can be in an inconsistent state and the call to onComplete can explode.

Using AIDL

AIDL, Android Interface Definition Language, is the next step up from simple Parcelables. It supports Java-like method calls and return values over the Binder IPC mechanism. AIDL is a multifeatured language, and a complete discussion of it is out of the scope of this book. It is described in detail in the Android documentation. What follows is an introduction.

The AIDL compiler is an instance of a tool that is very common in IPC mechanisms. Its job is to generate the Java code that implements both a proxy and a stub, given a single interface specification. The AIDL language, the source language for the AIDL compiler, looks a lot like Java.

Using AIDL to communicate with an object managed by a Service running in another process is a multistep process.

1. Define the interface: Listing 6.17 show a simple AIDL definition. Note that clients of a Service with an AIDL-defined API need the Java classes for the API. They can get them by compiling shared AIDL, compiling shared Java code, or by using a library containing the compiled API types. A Service with a public interface must publish its API in one of those three ways. It makes sense to put the API into a separate, sharable module, perhaps along with the contract.

Listing 6.17 A Simple AIDL Definition


package net.callmeike.android.servicesandbox.svc;

interface ILogger {
  void startLogging();
  void stopLogging();
}


2. Compile the interface: It is instructive to examine the compiled output. Gradle, the standard build tool for Android, puts the compiled AIDL output into the directory <builddirectory>/generated/source/aidl/<variant>/<classpath>/<interface>. For instance, the ILogger AIDL definition in Listing 6.17, built for the debug variant of its application, is in the file .../generated/source/aidl/debug/net/callmeike/servicesandbox/service/svc/ILogger.java.

Listing 6.18 shows annotated excerpts of the AIDL output. Perhaps surprisingly, a single Java source file is the implementation of both the proxy and the stub.

Listing 6.18 AIDL-Generated Code


package net.callmeike.android.servicesandbox.svc;

public interface ILogger extends android.os.IInterface
{
  // The service side extends this Stub
  public static abstract class Stub
    extends android.os.Binder
    implements net.callmeike.android.servicesandbox.svc.ILogger
  {

   // Code elided ...

    @Override public boolean onTransact(
      int code,
      android.os.Parcel data,
      android.os.Parcel reply,
      int flags)
      throws android.os.RemoteException
    {
      switch (code)
      {
        // when this opcode appears in the stream,
        // call the implementation of startLogging
        case TRANSACTION_startLogging:
        {
          data.enforceInterface(DESCRIPTOR);
          this.startLogging();
          reply.writeNoException();
          return true;
        }

        // Code elided ...

      }
      return super.onTransact(code, data, reply, flags);
    }

    // The client makes calls to this proxy
    private static class Proxy
      implements net.callmeike.android.servicesandbox.svc.ILogger
    {
      // the proxy has a stream to the remote
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }

      //  ... code elided

      // a call to the startLogging method
      // marshals the arguments into the stream
      @Override public void startLogging() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          mRemote.transact(Stub.TRANSACTION_startLogging, _data, _reply, 0);
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }

      //  ... code elided

    }

    // here are the definitions of the opcodes
    static final int TRANSACTION_startLogging
      = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_stopLogging
      = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  }

  // here is the interface that the proxy and the implementation implement
  public void startLogging() throws android.os.RemoteException;
  public void stopLogging() throws android.os.RemoteException;
}


3. Implement the interface: This is the actual implementation of the task. The important part is the first line, the definition of the class. It must extend the AIDL-generated stub, which in turn implements the AIDL-defined interface and extends Binder, the IPC mechanism. Listing 6.19 shows the implementation of a remote object.

Listing 6.19 Sample Managed Object Implementation


public class Logger extends ILogger.Stub {

  @Override
  public void startLogging() {
    // ...
  }

  public void stopLogging() {
    // ...
  }
}


4. Return the object from a Service: Several listings in this chapter have demonstrated the process of returning a managed object from a bound Service. The Service must be declared in the manifest, must carefully create an instance of the managed object, and must return it from a call to onBind.

Note that this time the object that the client receives in its onServiceConnected call is absolutely not the same object that the Service returned. It is, instead, a connection through Binder to the remote.

5. Wrap the Binder connection in the proxy: The client must have access to the same API definition used by the Service. Whereas when the client and service were both in the same process, the client simply cast the object that it received in the call to onServiceConnected, this time it wraps the passed reference, a Binder connection to the remote process, with the AIDL-generated stub using the asInterface method. The connection is now complete. Listing 6.20 is the implementation of a client to the Service shown in Listing 6.19.

Listing 6.20 Using a Remote Managed Object


private ILogger service;

public void onServiceConnected(ComponentName name, IBinder binder) {
  service = ILogger.Stub.asInterface(binder);
  button.setEnabled(true)
}

void startLogger() {
  service.startLogging();
}


Creating Processes

Running in a remote process has significant overhead. All that marshaling, unmarshaling, and copying across address spaces has a cost. Sometimes, though, that overhead is worth it. Just to pick one example, a remote task can crash horribly without affecting its client.

The most obvious way of running a task in a different process is by running it in a different application. This is simply a matter of using an Intent to address a Service that is part of that different application.

That works fine if target devices already have the required application installed. When an application must be responsible itself for arranging for multiple processes, requiring more than one application is cumbersome. Trying to get users to install a second app, for instance, simply to get the first one to work properly, is just too much friction.


Note

There is no requirement that an application have a UI. It is perfectly possible to create an application that consists of only one or more Services. Such an application will not appear on the Android Desktop and cannot by run explicitly by the user.

It is even possible for such an application to contain an Activity, as long as that Activity does not have an Intent filter that matches action android.intent.action.MAIN and category android.intent.category.LAUNCHER.


A second way to create processes is in the application manifest, using the android:process component attribute. This attribute enables specifying that a component should be run in a process other than the default process for the application. Listing 6.21 is an example of a Service, run in a process named “net.callmeike.android.servicesandbox:other.”

Listing 6.21 Specifying a Component’s Process


<service
  android:name=".svc.SimpleService"
  android:exported="true"
  android:process=":other"
  >
  <intent-filter>
    <action android:name="net.callmeike.android.servicesandbox.LOGGER"/>
  </intent-filter>
  <intent-filter>
    <action android:name="net.callmeike.android.servicesandbox.BLOGGER"/>
  </intent-filter>
</service>


Android distinguishes two types of process, local and remote. A local process is a process whose name begins with a “:”. Other applications with other package names cannot reproduce the name. If other application cannot name the process, they cannot use it in their process specifications and cannot put their components into it.

A remote process, on the other hand, has a name to which other applications have access. Remote names begin with a lower-case alpha character and must contain at least one “.” character. If two different applications signed with the same certificate and running with the same Linux user ID specify the same process for components, those components will all run in the very same process.

Listing 6.22 is nearly identical to Listing 6.21, but puts SimpleService in a process into which another application could put components.

Listing 6.22 Specifying a Component’s Process


<service
  android:name=".svc.SimpleService"
  android:exported="true"
  android:process=" com.callmeike.android.shared"
  >
  <intent-filter>
    <action android:name="net.callmeike.android.servicesandbox.LOGGER"/>
  </intent-filter>
  <intent-filter>
    <action android:name="net.callmeike.android.servicesandbox.BLOGGER"/>
  </intent-filter>
</service>


Binder, Considered Briefly

No discussion of Android inter-process communication would be complete without some consideration of Binder. In a very real sense, Binder is Android’s heart. It enables Android’s security mechanism, its unique cross-application component structure, and the very powerful mechanism for calling other applications as if they were functions.

Android’s Binder is fourth-generation. The original Binder was developed for BeOS. It made an appearance at Palm, and was then published as OpenBinder. The Android version is a reimplementation. Every Android developer should be aware of Binder and have a basic understanding of its behavior.

Binder Threads

An Android remote process is just a different process. All of the normal Android scheduling and threading behavior applies. In particular, Service lifecycle methods are called, in a remote process, as they always are, from the process main thread. Methods on a Service managed object in the remote process, however, are called from Binder threads.

A Binder thread is a thread created explicitly for use by Binder. When requests from one or more remote client processes queue up for execution by a bound Service, the kernel requests that the Server process’s framework spin up new threads to handle them. These new threads, once spun up, request work from the kernel.

An application will not spin up more than fifteen such threads. This is a software limit compiled into Android. While it is possible that this number will vary over time, or even from device vendor to device vendor, the mainline Android code has not changed this limit in the ten years of Android’s existence.

Binder Data Transfer Limits

The memory buffer that Binder uses to transfer data is limited in size. Again, although the specifics will vary over time and vendor, the rule of thumb is that it is not possible to move more than 1MB in a single transaction.

There is a way around this, though. Because Binder is part of the kernel, it can refer to kernel private structures. Among such structures are open files. In much the way that a parent and a child process can have a reference to the same open file after a fork call, so can one process open a file and pass it through Binder to another. A client, passing a large image, to a Service, might open the image as a file, and pass the open file to the Service, instead of passing every single byte of the image, through Binder.

Binding to Death

It might occasionally be necessary for a Service to discover that a client has aborted. This is particularly important when the Service is managing scarce or expensive resources and cannot tolerate a client claiming them and then failing to release them.

When a connection between a Service and its client provides a way for the Service to call the client, the Service will hold an IBinder for the client call. The IBinder interface defines the method linkToDeath. By calling this method, the Service registers for a callback if the connection to the client fails.

This is possible because Binder is part of the kernel. The kernel does not monitor the states of processes; it is the thing that creates those processes. No heartbeat or polling is required. The kernel is guaranteed to know if the client process fails. Binder passes the information to the registered callback.

Summary

Although architecturally they are a kind of a catch-all for functionality that has no other home, Services are an essential tool for the developer. They are the only way in the Android environment of adjusting process priority while doing valuable work that is not visible in the UI. The two kinds of Service, started and bound, provide a narrower and a broader interface, respectively, for tasks run not only on separate threads, but in separate processes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.205.154