Android services are the workhorses of the platform. They allow applications to run long running tasks and give functionality to outside applications. Unfortunately, since they run in the background and are not visually obvious to developers, they can be a source of major headaches. If you have seen an ANR (activity not responding) or have wondered why a service was running when it shouldn't be, a poorly implemented service could be the issue.
The dreaded ANR
Let's take a look at two major errors services cause:
- ANRs. These disrupt the user's experience and gives the user an option to force close. This can lead to a user uninstalling your app or the background process being in a disrupted state. The reason for this is that services are launched from your calling thread-- usually this is the UI thread.
- Always running services. Although Android provides mechanisms for scheduling and stopping services, not all developers follow the guidelines. In fact, a lot of the examples I've seen don't even mention stopping your service. This can lead to slower overall performance, user confusion, and battery drain.
I imagine the Android developers at Google saw that this was becoming an issue and introduced the IntentService class in Android 1.5. The IntentService class addresses the above issues and abstracts all of the management of threads and service stopping from the developer. It's very easy and powerful way of offloading tasks from the application's main thread.
To create an IntentService it is as simple as:
public class LongRunningService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
//here is where your long running task goes
}
}
If you haven't used IntentServices in your application, I highly recommend you take a look. They save development time and make your users happier!
For Brief Explanation see here :
Now we’d like to take a quick tour of one of the lesser known, but highly useful types of services you might want to use: the IntentService.
IntentService (android.app.IntentService) is a simple type of service that can be used to handle asynchronous work off the main thread by way of Intent requests. Each intent is added to the IntentService’s queue and handled sequentially.
IntentService is one of the simplest ways to offload “chunks” of processing off the UI thread of your application and into a remote work queue. There’s no need to launch an AsyncTask and manage it each and every time you have more processing. Instead, you simply define your own service, package up an intent with the appropriate data you want to send for processing, and start the service. You can send data back to the application by simply broadcasting the result as an Intent object, and using a broadcast receiver to catch the result and use it within the app.
Step 0: Getting Started
We have provided a sample application which illustrates the difference between trying to perform processing on the main UI thread (a no-no for application responsiveness) and offloading that same processing to an IntentService. The code can be downloaded via the code download link at the top of this tutorial.
Step 1: Defining the Processing of Messages
First, it’s important to understand when and why to use services in general. One good reason to use an IntentService is when you have work that needs to occur off the main thread to keep the application responsive and efficient. Another reason is when you may have multiple processing requests, and they need to be queued up and handled on the fly.
So let’s say we have an app that needs to do some “processing.” We wanted something simple, so we basically will define processing in this case as taking in a string parameter, doing “stuff” to it, and returning the string result. To keep this tutorial simple, the “stuff” we will do is sleep for 30 seconds, pretending to do something useful. In reality, the “stuff” would likely be image processing, connecting to a network, or some other blocking operation.
Therefore, if the entire “processing” were to occur within the main application, you might have an EditText control for taking message input, and a TextView control for splitting out the result. You could place this code in a Button handler to trigger the processing. The code in the Button click handler within the app Activity class would look something like this:
1
2
3
4
5
6
7
| EditText input = (EditText) findViewById(R.id.txt_input); String strInputMsg = input.getText().toString(); SystemClock.sleep( 30000 ); // 30 seconds, pretend to do work TextView result = (TextView) findViewById(R.id.txt_result); result.setText(strInputMsg + " " + DateFormat.format( "MM/dd/yy h:mmaa" , System.currentTimeMillis())); |
Step 2: Implementing an IntentService
We would much rather have our processing not interfere with the application. We would also like to be able to add multiple message process requests easily. This is the perfect time to use an IntentService! So let’s implement one.
Create another class file in your project and add stubs for the methods you need to implement. You should add a constructor with the name of your new service. You will need to implement just one other method called onHandleIntent(). This method is where your processing occurs. Any data necessary for each processing request can be packaged in the intent extras, like so (imports, comments, exception handling removed for code clarity, see the full source code for details):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class SimpleIntentService extends IntentService { public static final String PARAM_IN_MSG = "imsg" ; public static final String PARAM_OUT_MSG = "omsg" ; public SimpleIntentService() { super ( "SimpleIntentService" ); } @Override protected void onHandleIntent(Intent intent) { String msg = intent.getStringExtra(PARAM_IN_MSG); SystemClock.sleep( 30000 ); // 30 seconds String resultTxt = msg + " " + DateFormat.format( "MM/dd/yy h:mmaa" , System.currentTimeMillis()); } } |
Step 3: Launch the Service from your Application Activity
Next, you need to start the service from your application activity. Add a second Button control that, instead of doing the processing on the main thread, delegates the processing to your new service instead. The new button handler looks like this:
1
2
3
4
5
| EditText input = (EditText) findViewById(R.id.txt_input); String strInputMsg = input.getText().toString(); Intent msgIntent = new Intent( this , SimpleIntentService. class ); msgIntent.putExtra(SimpleIntentService.PARAM_IN_MSG, strInputMsg); startService(msgIntent); |
The service takes over from here, catching each intent request, processing it, and shutting itself down when it’s all done. The main user interface remains responsive throughout the processing, allowing the user to continue to interact with the application. The user can input multiple messages, hit the button again and again, and each request is added to the service’s work queue and handled. All in all, a better solution.
But we’re not quite done yet.
Step 4: Define the Broadcast Receiver
Your IntentService can now do its job of processing, but we need it to inform the main application’s activity when it’s processed each request so that the UI can be updated. This only really matters when the activity is running, so we’ll use a simple broadcast sender/receiver model.
Let’s begin by defining a BroadcastReceiver subclass within the main activity.
1
2
3
4
5
6
7
8
9
10
11
| public class ResponseReceiver extends BroadcastReceiver { public static final String ACTION_RESP = "com.mamlambo.intent.action.MESSAGE_PROCESSED" ; @Override public void onReceive(Context context, Intent intent) { TextView result = (TextView) findViewById(R.id.txt_result); String text = intent.getStringExtra(SimpleIntentService.PARAM_OUT_MSG); result.setText(text); } } |
Note: If you were updating a database or shared preferences, there are alternate ways for the user interface to receive these changes. In these cases, the overhead of a broadcast receiver would not be necessary.
Step 5: Broadcast the Result
Next, you need to send a broadcast from the onHandleIntent() method of the IntentService class after the processing is complete and a result is available, like this:
1
2
3
4
5
6
| // processing done here…. Intent broadcastIntent = new Intent(); broadcastIntent.setAction(ResponseReceiver.ACTION_RESP); broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT); broadcastIntent.putExtra(PARAM_OUT_MSG, resultTxt); sendBroadcast(broadcastIntent); |
Step 7: Register the Broadcast Receiver
Finally, you must instantiate and register the receiver you’ve defined in your activity. Register the receiver in the onCreate() method with the appropriate intent filter to catch the specific result intent being sent from the IntentService.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class IntentServiceBasicsActivity extends Activity { private ResponseReceiver receiver; @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.main); IntentFilter filter = new IntentFilter(ResponseReceiver.ACTION_RESP); filter.addCategory(Intent.CATEGORY_DEFAULT); receiver = new ResponseReceiver(); registerReceiver(receiver, filter); } } |
Conclusion
Offloading work from the main UI thread of an application to a work queue in an IntentService is an easy and efficient way to process multiple requests. Doing so keeps your main application responsive and your users happy. For many purposes, using an IntentService may be easier and more desirable than an AsyncTask, which is limited to a single execution, or a Thread, which has more coding overhead.
No comments:
Post a Comment