The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Remote Services and the Binding Pattern

Earlier in this book, we covered using services by sending commands to them to be processed. That “command pattern” is one of two primary means of interacting with a service — the binding pattern is the other. With the binding pattern, your service exposes a more traditional API, in the form of a “binder” object with methods of your choosing. On the plus side, you get a richer interface. However, it more tightly ties your activity to your service, which may cause you problems with configuration changes.

Either the command pattern or the binding pattern can be used, if desired, across process boundaries, with the client being some third-party application. In either case, you will need to export your service via an <intent-filter>. And, in the case of the binding pattern, your “binder” implementation will have some restrictions.

This chapter covers the binding pattern for local services, plus inter-process commands and binding (a.k.a., remote services).

Prerequisites

Understanding this chapter requires that you have read the chapters on:

The Binding Pattern

Implementing the binding pattern requires work on both the service side and the client side. The service will need to have a full implementation of the onBind() method, which typically just returns null or throws some sort of runtime exception for a service solely implementing the command pattern. And, the client (e.g., an activity) will need to ask to bind to the service, instead of (or perhaps in addition to) starting the service.

What the Service Does

The service implements a subclass of Binder that represents the service’s exposed API. For a local service, your Binder can have pretty much whatever methods you want: method names, parameters, return types, and exceptions thrown are up to you. When you get into remote services, your Binder implementation will be substantially more constrained, to support inter-process communication.

Then, your onBind() method returns an instance of the Binder.

What the Client Does

Clients call bindService(), supplying the Intent that identifies the service, a ServiceConnection object representing the client side of the binding, and an optional BIND_AUTO_CREATE flag. As with startService(), bindService() is asynchronous. The client will not know anything about the status of the binding until the ServiceConnection object is called with onServiceConnected(). This not only indicates the binding has been established, but for local services it provides the Binder object that the service returned via onBind(). At this point, the client can use the Binder to ask the service to do work on its behalf.

Note that if the service is not already running, and if you provide BIND_AUTO_CREATE, then the service will be created first before being bound to the client. If you skip BIND_AUTO_CREATE, and the service is not already running, bindService() is supposed to return false, indicating there was no existing service to bind to. However, in actuality, Android returns true, due to an apparent bug.

Eventually, the client will need to call unbindService(), to indicate it no longer needs to communicate with the service. For example, an activity might call bindService() in its onCreate() method, then call unbindService() in its onDestroy() method. Once you call unbindService(), your Binder object is no longer safe to be used by the client. If there are no other bound clients to the service, Android will shut down the service as well, releasing its memory. Hence, we do not need to call stopService() ourselves — Android handles that, if needed, as a side effect of unbinding.

Your ServiceConnection object will also need an onServiceDisconnected() method. This will be called only if there is an unexpected disconnection, such as the service crashing with an unhandled exception.

A Binding Sample

In the chapter introducing services, we saw a sample app that would download a file off of a Web server. That sample used the command pattern, telling the service what to download via an Intent extra. In this chapter, we will review a few variations of that sample, all of which use the binding pattern instead of the command pattern.

Right now, we are focused on local services, and so the Binding/Local sample project does the download via a local bound service.

We start by defining an interface that will serve as the “contract” between the client (fragment) and service. This interface, IDownload, contains a single download() method:

package com.commonsware.android.advservice.binding;

// Declare the interface.
interface IDownload {
  void download(String url);
}

Our service, DownloadService, implements just one method, onBind(), which returns an instance of a DownloadBinder:

package com.commonsware.android.advservice.binding;

import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class DownloadService extends Service {
  @Override
  public IBinder onBind(Intent intent) {
    return(new DownloadBinder());
  }

  private static class DownloadBinder extends Binder implements IDownload {
    @Override
    public void download(String url) {
      new DownloadThread(url).start();
    }
  }

  private static class DownloadThread extends Thread {
    String url=null;

    DownloadThread(String url) {
      this.url=url;
    }

    @Override
    public void run() {
      try {
        File root=
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

        root.mkdirs();

        File output=new File(root, Uri.parse(url).getLastPathSegment());

        if (output.exists()) {
          output.delete();
        }

        HttpURLConnection c=(HttpURLConnection)new URL(url).openConnection();

        FileOutputStream fos=new FileOutputStream(output.getPath());
        BufferedOutputStream out=new BufferedOutputStream(fos);

        try {
          InputStream in=c.getInputStream();
          byte[] buffer=new byte[8192];
          int len=0;

          while ((len=in.read(buffer)) >= 0) {
            out.write(buffer, 0, len);
          }

          out.flush();
        }
        finally {
          fos.getFD().sync();
          out.close();
          c.disconnect();
        }
      }
      catch (IOException e2) {
        Log.e("DownloadJob", "Exception in download", e2);
      }
    }
  }
}

DownloadBinder implements the IDownload interface. Its download() method, in turn, forks a DownloadThread to perform the download in the background — remember, for local services, the methods you invoke on the Binder are executed on whatever thread you call them on.

Our fragment, DownloadFragment, loads our layout, res/layout/main.xml, containing a Button to trigger the download:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/go"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
    android:text="@string/go"/>

The implementation of onCreateView() simply loads that layout, gets the Button, sets up the fragment as being the click listener for the Button, and disables the Button:

  @Override
  public View onCreateView(LayoutInflater inflater,
                           ViewGroup container,
                           Bundle savedInstanceState) {
    View result=inflater.inflate(R.layout.main, container, false);

    btn=(Button)result.findViewById(R.id.go);
    btn.setOnClickListener(this);
    btn.setEnabled(binding!=null);

    return(result);
  }

The reason why we disable the Button is because we are not connected to our service at this point, and until we are, we cannot allow the user to try to download a file.

In onCreate() of our fragment, we mark the fragment as retained and bind to the service:

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setRetainInstance(true);

    appContext=(Application)getActivity().getApplicationContext();
    appContext.bindService(new Intent(getActivity(),
        DownloadService.class),
      this, Context.BIND_AUTO_CREATE);
  }

You will notice something curious here: getApplicationContext(). Technically, we could bind to the service directly from the Activity, by calling bindService() on it, as bindService() is a method on Context. However, our service binding represents some state, and it is possible that this state will hold a reference to the Context that created the binding. In that case, we run the risk of leaking our original activity during a configuration change. The getApplicationContext() method returns the global Application singleton, which is a Context suitable for binding, but one that cannot be leaked, since it is already in a global scope. In effect, it is “pre-leaked”.

The call to setRetainInstance() allows the fragment – serving as our ServiceConnection — to survive a configuration change, so we can cleanly unbind from the service later on, when onDestroy() is called.

Some time after onCreate() is called and we call bindService(), our onServiceConnected() method will be called, as we designated our fragment to be the ServiceConnection. Here, we can cast the IBinder object we receive to be our IDownload interface to the service, and we can enable the Button:

  @Override
  public void onServiceConnected(ComponentName className, IBinder binder) {
    binding=(IDownload)binder;
    btn.setEnabled(true);
  }

Since we are implementing the ServiceConnection interface, our fragment also needs to implement the onServiceDisconnected() method, invoked if our service crashes. Here, we delegate responsibility to a disconnect() private method, which removes our link to the IDownload object and disables our Button:

  @Override
  public void onServiceDisconnected(ComponentName className) {
    disconnect();
  }

  private void disconnect() {
    binding=null;
    btn.setEnabled(false);
  }

And, when our fragment is destroyed, we unbind from the service (using the same Context as before, from getApplicationContext()) and disconnect():

  @Override
  public void onDestroy() {
    appContext.unbindService(this);
    disconnect();

    super.onDestroy();
  }

However, in between onServiceConnected() and either onServiceDisconnected() or onDestroy(), the user can click the Button, which will trigger the download via a call to download() on our IDownload instance:

  @Override
  public void onClick(View view) {
    binding.download(TO_DOWNLOAD);
  }

The DownloadBindingDemo activity adds our DownloadFragment via a FragmentTransaction:

package com.commonsware.android.advservice.binding;

import android.app.Activity;
import android.os.Bundle;

public class DownloadBindingDemo extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
      getFragmentManager().beginTransaction()
                                 .add(android.R.id.content,
                                      new DownloadFragment()).commit();
    }
  }
}

Starting and Binding

Some developers will use both startService() and bindService() at the same time. The typical argument is that they need frequent updates from the service (e.g., percentage of progress, for updating a ProgressBar) in the client and are concerned about the overhead of sending broadcasts.

With the advent of LocalBroadcastManager and other event bus implementations, binding to a service you are using with startService() should no longer be necessary.

When IPC Attacks!

The preview of this section is out seeking fame and fortune as the Dread Pirate Roberts.

Service From Afar

The preview of this section is unavailable right now, but if you leave your name and number at the sound of the tone, it might get back to you (BEEEEEEEEEEEEP!).

Tightening Up the Security

The preview of this section was last seen in the Bermuda Triangle.

Servicing the Service

The preview of this section may contain nuts.

Thinking About Security

The preview of this section was the victim of a MITM ('Martian in the middle') attack.

The “Everlasting Service” Anti-Pattern

The preview of this section is sleeping in.