Fork me on GitHub

Sunday, March 10, 2013

Introducing LocalService

Activities has a very short-term lifecycle. Even when you rotate your device, the activity is destroyed and newly created. Therefore, a service is needed for a longer term behavior that should persist when the current application is not in foreground. Service is designed to be a pretty powerful tool, that is intended to support inter-process communication. However, I (and probably you) just use a service as an in-process singleton object for most of the time. Just accessing an object in the same VM need not be complex at all. However, the Android Developer Guide misguides developers to write such a horrible length of code:

public class MyService extends Service {
    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        MyService getService() {
            return MyService.this;
        }
    }

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

    private final Random mGenerator = new Random();

    public int getRandomNumber() {
        return mGenerator.nextInt(100);
    }
}
public class BindingActivity extends Activity {
    MyService mService;
    boolean mBound = false;

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    public void onButtonClick(View v) {
      if (mBound) {
        int num = mService.getRandomNumber();
        Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
      }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}
This implementation is full of boilerplate code. In Scala, we can rewrite it as follows:
class MyService extends LocalService {
    private val generator = new Random()

    def getRandomNumber() = generator.nextInt(100)
}
class MyActivity extends SActivity {
  val random = new LocalServiceConnection[MyService]

  def onButtonClick(v:View) {
    random( s => toast("number: " + s.getRandomNumber()))
  }
}
This code snippet does the same thing with the original Java code. All of the clutters are encapsulated in LocalService and LocalServiceConnection, so you can just focus on your idea.

We often initializes a variable with some default value when the service is not yet connected:
val num = if(random.connected) random.service.getRandomNumber() else 0
doSomethingWith(num)

With LocalServiceConnection.apply, the expression above can be shortened as shown below:

val num = random(_.getRandomNumber(), 0)
doSomethingWith(num)

The full source code of these traits can be found here

2 comments:

  1. Hi, I've tried your example code. It didn't work: looked like random.connected is always false.
    Am I missing something? Any suggest?
    Thanks a lot.

    ReplyDelete
  2. Never mind. I forgot to declare the service in the manifest. Thanks

    ReplyDelete