Main menu

Die Hard Android Service

Android Service is another example of concept that could be potentially useful, but as with BroadcastReceiver, it ended up being just shameful. You see - Service is ideal (or so I have read) to play music in background. It stays put. Android has hard time killing it even in low memory conditions. It has no GUI. It just does some work silently. Since playing music in background is basicly everything XenoAmp does, EngineAndroid and EngineFFMpeg are two Services derived from abstract XenoAmpService. Of course besides listening to commands like "pause", "volume up" or "stop and kill" the Service should be able to provide some info, you know - things like currently played track, active equalizer preset, current track progress... But guess what? Only Activiries (and other Services) can bind to a Service, making the whole Service concept as useless as a BroadcastReceiver. I know, I know - I could bind anywhere as long as I pass Context around, but do I really need to pass around Android Context to classes like PlaylistFactory? Not to mention - I have no idea what Context is, apart from the fact that it likes to leak.

To be able to get information back from XenoAmpService I've tried to make it a singleton, you know:

	public static XenoAmpService getInstance() {
		return instance;
	}

	public void onCreate() {
		super.onCreate();

		instance = this;

But regardless of how cautios I was using XenoAmpService instance, I was punished anyway by Android with NullPointerExceptin even at most unlikely places:

if(XenoAmpService.getInstance()!=null) XenoAmpService.getInstance().doSomething();

Finally I gave up and decided to do it "the right way", by using service binding, wherever it was possible. On this occassion I've discovered that all service binding examples in Android documentations are bad, because they cause leaking (Context leaking, I'd guess?) and you shouldn't under any circumstances use binding code as shown in Google docs! So the first thing is to create your own Binder class and put it in separate file (not as internal class - this is crucial!):

/**
 * A generic implementation of Binder to be used for local services
 * @author Geoff Bruckner  12th December 2009
 *
 * @param s The type of the service being bound
 */

public class LocalBinder<S> extends Binder {
    private String TAG = "LocalBinder";
    private  WeakReference<S> mService;
    
    public LocalBinder(S service){
        mService = new WeakReference<S>(service);
    }
    
    public S getService() {
        return mService.get();
    }
}

Then in the Activity in which you want to bind:

	private ServiceConnection mConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {
			główny = ((LocalBinder) service).getService();
		}

		public void onServiceDisconnected(ComponentName className) {
			główny = null;
		}
	};

And use this ServiceConnection with your bindService(...). OK, so now, feeling a bit warmer knowing, that there are places where following Google docs shouldn't be followied, we may start binding... And binding... And binding... Everywhere I need to read my service state, I need to bind. And then I listen to some music, and then I get tired, press small red "X" in XenoAmp notification, that supposedly closes XenoAmp by calling finishSelf() in XenoAmpService and... gues what? The damn thing doesn't stop!

Of course at this very moment I know already that finishSelf() probably doesn't finish because I have less unbinds than binds, in other words - some of clients connecting to my Service weren't cleanly unbound. Just to be sure, I'm checking google docs for finishSelf() if my intuition was right. Not a single word there! So Google thinks finishSelf() should always work. Oh well.

Of course - for finishSelf() to kill your service you need to unbind everything. In my case it was XenoStreaming service, which is started by XenoAmpService itself. So I had to unbind it before each occurence of finishSelf(). Then it worked. Thanks for good docs, Google!

FacebookG+TwitterRSS