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!

Write comment (0 Comments)

Enter EventBus (a.k.a.: Intents! You shaLL NOT PAAASSS!!!)

The Google way: Intents and BroadcastReceivers

Believe me, I tried! I did! I've spent about five minutes trying to figure out what Intents do. About a year ago, when I started coding on Android, was young and stupid, I thought one needs to understand Intents to be a true Android Wizard. But alas! these mysterious concepts are beyond my comprehension.

  • What's with the names?
  • Why do they need data and extras?
  • Why isn't data part of extras?
  • Why do they have about a hundred constructors?
  • Why do I have to write 50 lines of code just to receive them?
  • And how the hell can I receive messages in any Object, not just Activity?
  • Do I really have to create my own Parcelable (hundreds of lines...) to pass my classes around?
  • How fast is constant packaging and unpackaging of basic types?

And so on... So after five minutes I've got tired and gave up on Intents, thinking that the concept is either too complicated or too lame to be understood.

OK, should I really use "lame" keyword without justification? Of course I shouldn't! That would be just... lame! So let me show you an example.

When currently played track changes in XenoAmp music engine, several subsystems have to be notified with new data:

  1. While Playing Screen
  2. Notification
  3. Lockscreen
  4. Scrobbler
  5. Guess what? Many more misc components should know when track changes!

So can my XenoAmpEngine extends CompletelyNothing { just announce: "My dear classes, a new PlayableAbstract is being played"? And could any Object receive this broadcast? Nope... Only BroadcastReceivers can receive Intent broadcasts. Oh, great! so can I add a BroadcastReceiver to my RandomCalss and... Aaaaaah, nope. I can register them only in Activities... Hmmmm... These Intents are pretty useless beasts! But, hey! Just to torture myslef, let me do it the Android Way! It's simple!

Intent bCast = new Intent("something.horrible.has.happened");
bCast.putExtra("playable",currentlyPlayedTrack) // currentlyPlayedTrack is of type PlayableAbstract getContext().sendBroadcast(bCast);

Right... But sending PlayableAbstract over broadcast ain't so easy! To use it as shown above I have to add a Parcelable interface to PlayableAbstract. What does it mean? Well it just means that YourRandomClass has to be enhanced to be elligible to become an Intent extra! Otherwise you have to put each field by hand each time you want to send this broadcast:

Intent bCast = new Intent("something.horrible.has.happened");
bCast.putExtra("title",currentlyPlayedTrack.getTitle())
bCast.putExtra("artist",currentlyPlayedTrack.getArtist())
bCast.putExtra("albumartist",currentlyPlayedTrack.getAlbumArtist())
bCast.putExtra("album",currentlyPlayedTrack.getAlbum())
// 30 or so lines to follow
getContext().sendBroadcast(bCast);

So either way you do it, you need a receiver. Assuming you wanted to save yourself some work and didn't go for Parcelable, you do it this way:

	class UpdateReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(final Context arg0, final Intent intent) {
			if ("something.horrible.has.happened".equals(intent.getAction())) {
				Bundle extra=intent.getExtras();
				PlayableAbstract nowy=new PlayableAbstract();
				nowy.setTitle(extra.getStringExtra("title");
				nowy.setArtist(extra.getStringExtra("artist");
				// stupidity goes on for 32 lines
			}
		}
	}

But wait! There's more! Don't forget to:

updateReceiver = new UpdateReceiver();
IntentFilter onFilter = new IntentFilter("something.horrible.has.happened");
registerReceiver(updateReceiver, onFilter);

in your onResume method and:

unregisterReceiver(updateReceiver);

in your onPause method. Clean and elegant, right? And don't forget that even if you sell your soul to Satan himself, only Services and Activities will get these broadcasts... Sad, sad, sad. Stupid and totally useless...

What else can I say about Intents and BroadcastReceivers? They are not object-oriented. Since you don't extend your own Activities (or do you?) BroadcastReceivers can't be inherited. Since Intents are just pieces of tagged data, not Java classes, no inheritance there either.

The Kewl way: Enter EventBus

There must be some other way! And there is! It's called da EventBus and since I learnt about it I take it anywhere I go!

So how do I send broadcasts in XenoAmp? Just a single line:

EventBus.getDefault().post(new TrackChangeEvent(currentlyPlayedTrack)); // currentlyPlayedTrack is of type PlayableAbstract

Mind that TrackChangeEvent is just a class I've created to make event passing follow some OO convention (like i.e. exception handling), because otherwise EventBus allows passing ANY Object as event! To receive this event in ANY Object in your code just add this:

EventBus.getDefault().register(this);

to your constuctor (or enywhere you think it would work), and then:

EventBus.getDefault().unregister(this);

anywhere you think it is not needed anymore! Of course, instead of this you can pass just any Object that has any of EventBus methods implemented:

  public void onEventPostThread(TrackChangeEvent e) {
} public void onEventMainThread(XenoEvent e) { pokażżmianętraku(e.getChange()); } public void onEventBackgroundThread(TrackChangeEvent e) {
pokażżmianętraku(e.getChange()); } public void onEventAsync(VolimeMagnitudeEvent e) { }

If you've read the above lines carefully, you've may already be full of joy and excitement. YES! EventBus automagically handles task affinity for you! No more ugly:

new Handler(Looper.getMainLooper()).post(new Runnable() {
				@Override
				public void run() {
					// do something on main thread
				}
			});

Just put your code in proper method (post, main, background or async - see EventBus docs for more explanation)!

EventBus gives you added benefit of "onEventXXXXX" inheritance by derived classes and OO event passing system (if you are as anal retentive as I am!).

Now - this is smart, simple, beautiful and useful! Note also the .getDefault() - as you suspect it gets default or global EventBus for you. It's like broadcast channel on CB, but you're not limited to default EventBus object! You can create as many (private, semi-private, accessible to limited number of classes) EventBuses as you want!

Write comment (0 Comments)

Fragments inside fragments inside fragments

Don't know about you, but when programming I always try to think ahead and make my code future proof by thinking how some concept A will behave when fed with data B. So what exactly were Android engineers thinking when they invented fragments and didn't provide any mechanisms to manage fragments inside fragments?

That's right - first they give you a nice concept (I call it nice mainly because when creating a new fragment I don't need to go back to AndroidManifest.xml, whatever this damn file does) and then they say: "You want to have fragments in fragments? Eeerrrrmmm... We didn't think about THAT". Fortunately they had a lot of time to fix it, unfortunately the solution is lame. And if you don't know it, you will spend countless hours trying to figure why your fragemnts don't appear, although in debugger they seem fully initialized.

And that's exactly what happened when I migrated a few of my ugly solutions to FragmentStatePagerAdapter.

XenoAmp main activity layout contains two fragment containers: one for main view and the other one, at the bottom - for playlist drawer. So one day I've decided I want to allow users drag playlist drawer not only up and down, but also left or right (like changing to another drawer). And this means I needed to substitute my drawer contents, which was a simple DragSortListView with ViewPager that holds multiple DragSortListViews. It's a bit like the good old deprecated Gallery, but creates a multitude of fragments to do its magic. In other words - my playlist drawer fragment was now containing a lot of fragments with playlists!

And guess what? Somewhere it worked, somewhere - it didn't! Exactly the same code! The place it didn't work was music row fragment (the one that shows when you tap album icon in music view), which in main view contains exactly same ViewPager with another set of playlists! When both "root" fragments were visible on screen (so: two fragment containers, each containing a single fragment, both of which contained a crowd of other fragments), switching between playlist was broken! It looked like PlaylistStatePagerAdapterForSheetRow returned null for newly created fragments!

I examined everything up to "root" containing fragment and ended here, in my MusicRowFragment onCreateView method:

adapterStron = new PlaylistStatePagerAdapterForSheetRow(getFragmentManager(), getIndex());

So to make long story short, oh mighty Flying Spaghetti Monster! Bless you for StackOverflow! Within minutes I discovered the ugly truth and fortunately the fix was simple, you have to use ChildFragmentManager not FragmentManager

adapterStron = new PlaylistStatePagerAdapterForSheetRow(getChildFragmentManager(), getIndex());

And guess what? Now it worked! Yes, Google. Thank you for elegant API!

Write comment (0 Comments)

FacebookG+TwitterRSS