Android: Services, building your own MP3 player

14 May 2012

A service in android is used to provide a functionality that doesn't really require user interaction or involves a process that needs to run in the background for a longer period of time. To explain this, I will show you how to build a simple MP3 player. When you are listening to music you don't want song to stop playing when you switch your activity. You want to be able to read a text message or send an email at the same time you are listening to your music. This can be done by playing the songs in a service, while the control pannel is an activity. This post will show you how to create your own android service and connect to it with an IBinder.

The service class

A basic service class looks like this:

public class Mp3PlayerService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        // do something when bound
        return null;
    }
}

We have a class that extends service. We have the 'onBind' method, this method is mandatory. In here you can call methods that should be executed once an activity binds to the service. There are some other optional methods as well, we won't be using all of them so please refer to the API if you want to know more. First let's extend and add this service to our Android Manifest, include it right before closing the application tag:

   <service
            android:name=".Mp3PlayerService" >
        </service>

We define a service with android:name referring to our service class.

Binder

onBind needs to return a Binder object. We shall create our own Binder class as an inner class of our service class:

public class Mp3PlayerService extends Service {
    public final IBinder localBinder = new LocalBinder();
    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }
    @Override
    public void onCreate() {
    }
    public class LocalBinder extends Binder{
        Mp3PlayerService getService(){
            return Mp3PlayerService.this;
        }
    }
}

This allows us to return our service object to our activity so the activity can control methods within the service. To bind our activity to our service we will need to get a connection object. This is done by creating a ServiceConnection object. After creating that object we need to explicitly connect to the service:

public class Mp3ControllerActivity extends Activity {
    private ServiceConnection mp3PlayerServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName arg0, IBinder binder) {
            mp3Service = ((LocalBinder) binder).getService();
        }
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
        }
    };
    @Override
        public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //start our service
        startService(new Intent(this, Mp3PlayerService.class));
        //bind to our service by first creating a new connectionIntent
        Intent connectionIntent = new Intent(this, Mp3PlayerService.class);
        bindService(connectionIntent, mp3PlayerServiceConnection,
                Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        //whenever our activity gets destroyed, unbind from the service
        unbindService(this.mp3PlayerServiceConnection);
        super.onDestroy();
    }
}

Mediaplayer

Now to keep things simple we will just include an MP3 of your liking in our package. I first wanted to automatically make a playlist from a music folder, but that would be beyond the scope of this tutorial. So just pick any mp3 file and name it "mysong.mp3". Create a folder called "raw" in you res folder, put the song in there:

We will define our mediaplayer in our service and add some controller options to play/pauze songs:

public class Mp3PlayerService extends Service {
    public final IBinder localBinder = new LocalBinder();
    private MediaPlayer mplayer;
    private boolean created = false;
    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }
    @Override
    public void onCreate() {
    }
    public class LocalBinder extends Binder {
        Mp3PlayerService getService() {
            return Mp3PlayerService.this;
        }
    }
    public void playSong(Context c) {
        if(!created){
            this.mplayer = MediaPlayer.create(c, R.raw.mysong);
            created = true;
        }
            this.mplayer.start();
    }
    public void pauzeSong(Context c) {
        this.mplayer.pause();
    }
}

I'm checking my MediaPlayer with a boolean rather than with null. Android shut down my service when I checked for null, this was not the case when I used a boolean. I think it might be because of the static call to Mediaplayer. To finish the program we can add some control buttons:

public class Mp3ControllerActivity extends Activity {
    private MediaPlayer mediaPlayer;
    private Mp3PlayerService mp3Service;
    private ServiceConnection mp3PlayerServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName arg0, IBinder binder) {
            mp3Service = ((LocalBinder) binder).getService();
        }
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
        }
    };
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        startService(new Intent(this, Mp3PlayerService.class));
        Intent connectionIntent = new Intent(this, Mp3PlayerService.class);
        bindService(connectionIntent, mp3PlayerServiceConnection,
                Context.BIND_AUTO_CREATE);
        final Button play_button = (Button) findViewById(R.id.button1);
        play_button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mp3Service.playSong(getBaseContext());
            }
        });
        final Button pauze_button = (Button) findViewById(R.id.button2);
        pauze_button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mp3Service.pauzeSong(getBaseContext());
            }
        });
    }
    @Override
    protected void onDestroy() {
        unbindService(this.mp3PlayerServiceConnection);
        super.onDestroy();
    }
}

layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="PLAY" />
    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pauze" />
</LinearLayout>

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="eu.cloud101.mp3"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="10" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".Mp3ControllerActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".Mp3PlayerService" >
        </service>
    </application>
</manifest>

Services as process

Note that a service can be run as a separate process with its own garbage collector. This is better than using a service in the same process as the activity, but it also involves a much more complex communication setup since you can't directly access memory in another process. I will not dig deeper into this, but just wanted to make the reader aware of its existence.