Many Android applications need to do some things in the background when your application isn't open. Maybe you need to periodically ping a server for information. Or maybe you need to have certain tasks run on an interval that is specified by you or the user. All of these cases are great examples when using a service would be the best (and sometimes the only) method of accomplishing your task. But first, let's clarify some things...
How is a service different from a thread?
Well, I'm glad you asked! Both a service and a thread run in the background, and both are great for accomplishing tasks that the user does not need to see. However, they are very different in how they are applied within an application, and most importantly, a service is not a thread. It runs on the UI thread along with your app. If you do not want the service to cause lag in your app when doing some heavy lifting, you'll have to use a Thread inside of the Service!
A thread is a great way to do something in the background of an application only while a user is using it. Using a thread runs the code within the activity, but on a thread separate from the UI thread. It allows you do things like load things from file, run calculations, and do searches without causing issues with lag or jank in the user interface.
Services, however, run your code on the UI thread whether your application is open or not, allowing them to do things such as play music no matter what the user is doing, run a timer, check a server on an interval, and many other things. They are also not directly controlled from within your activity, instead having to be controlled through a special interface specified in AIDL (Android Interface Definition Language). This is a language similar to Java, but not quite Java. Whenever you want to control the service from your app, you will need to bind your application with the service using this interface.
Now, this may all seem daunting at first glance. But it's really quite simple and can be done with a few easy steps. And what a coincidence! Here's a few easy steps!
Now, this may all seem daunting at first glance. But it's really quite simple and can be done with a few easy steps. And what a coincidence! Here's a few easy steps!
Make your service
The first and most important step that we're going to complete is to create our actual service. It will start out like any other class, with a few changes. Were going to call the following file Service_PlaySong:
public class Service_MusicPlayer extends Service {
MediaPlayer mp;
String file = "";
@Override
public IBinder onBind(Intent arg0) {
Log.d("OURAPP_SERVICE", "BOUND");
return mBinder;
}
/**
* Initialize the media player
*/
@Override
public void onCreate(){
mp = new MediaPlayer();
mp.setOnCompletionListener(new OnCompletionListener(){
@Override
public void onCompletion(MediaPlayer mp) {
mp.stop();
}
});
}
}
Let's just add a play() method for our service to actually play a song:
public void play(String file) {
try {
this.file = file;
mp.reset();
mp.setDataSource(file);
mp.prepare();
ready = true;
mp.start();
} catch (IllegalStateException e){
e.printStackTrace();
mp.release();
mp = new MediaPlayer();
mp.reset();
openFile(file);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
And just like that, we have a method that takes the directory of your file, and begins playing it on the device! You may notice that we used a String to pass the file to the service. This is because only basic data types (String, int, byte, double, float, etc.) are supported unless they are made parcelable. This, however, is not necessary for this example, so to save from confusion, I'll keep it out of the scope of this post.
Finally, we will need to insert some code to allow the Activity to hook into the service. This is pretty straightforward, and can be repeated for any and all methods that need to be accessed by Activities. Add this to the same file, Service_MusicPlayer:
static class ServiceStub extends IService_MusicPlayer.Stub {
WeakReference
ServiceStub(Service_MusicPlayer service) {
mService = new WeakReference
}
@Override
public void play(String path) throws RemoteException {
mService.get().play(path);
}
}
private final IBinder mBinder = new ServiceStub(this);
Now, you may notice that I said that ServiceStub extends IService_MusicPlayer.Stub. This is where the AIDL file comes into play.
Make a new file in the same directory as the Service class, but this time, name it IService_MusicPlayer.aidl. This file will define your methods used in the Service, again allowing Activities to hook into it. It should look like the following:
package com.echo.ourserviceapp;
interface IService_MusicPlayer {
void play(String path);
}
Make sure that the package is correctly stated, just as it would be in our Service. Otherwise, it will fail to compile. You will also notice that there is only one line in the interface. This is because we only have one accessible method. Should you make more, or want to return a value, you can modify this line to your pleasing.
Make your application
Fantastic! Now we have to make our application hook into the service in order to play music. It will start out like any normal Activity, with a few exceptions:
public class Activity_Main {
IService_MusicPlayer mService = null;
boolean disconnected = true;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Setup play/pause button in bar on bottom
((Button)findViewById(R.id.play_song)).setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
try {
mService.play("yourpath.mp3");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
So far, pretty normal. We have a button in our UI that, when clicked, will play a song that (currently) we have specified as yourpath.mp3. But this code will fail alone because we haven't connected the Service to our class! To do that, we need to add the following:
@Override
public void onResume(){
super.onResume();
Intent i2 = new Intent(Service_MusicPlayer.class.getName());
startService(i2);
bindService(i2, osc, 0);
}
@Override
public void onPause(){
super.onPause();
if (!disconnected){
unbindService(osc);
}
}
/**
* Service connection to IService_MusicPlayer, allowing communications
* and playing of song
*/
private ServiceConnection osc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IService_MusicPlayer.Stub.asInterface(service);
disconnected = false;
}
@Override
public void onServiceDisconnected(ComponentName name) {
disconnected = true;
}
};
The above simply handles the binding and unbinding in onResume() and onPause(), preventing two Activities from trying to access the same Service (this is not possible and will result in an error).
Manifest entry
Just like an Activity, a Service also requires a manifest entry. To do this, simply create it like an Activity entry, but adding a tag that specifies that the Service can run remotely:
<service android:name=".Service_MusicPlayer"
android:process=":remote">
<intent-filter>
<action android:name="com.echo.ourserviceapp.Service_MusicPlayer" />
</intent-filter>
</service>
android:process=":remote">
<intent-filter>
<action android:name="com.echo.ourserviceapp.Service_MusicPlayer" />
</intent-filter>
</service>
And boom! Just like that, you have a working Service! You can now click the home button and you'll still hear the music playing.
No comments:
Post a Comment