Wednesday, March 27, 2013

Stop IntentService

I have numbers of post about IntentService before; include Perform background processing with IntentService, Share IntentService among Fragments, Send data from IntentService to Activity, via additional Broadcast and Generate Notification in IntentService. This post is a good exercise to understand the life-cycle of IntentService.

To stop a IntentService, call the method stopService (Intent service). It request that a given application service be stopped. If the service is not running, nothing happens. Otherwise it is stopped. Note that calls to startService() are not counted -- this stops the service no matter how many times it was started. So imple? not at all.

In this exercise, buttons are added to start and stop IntentService. It can be noted that:
  • If you click the "Start IntentService" multi times, only one request will be processed at a time, All requests are handled on a single worker thread. The extra requests will be place in a queue.
  • If you click the "Start IntentService" multi times, only ONE call of stopService() is need to stop all request.
  • If you remove the checking on the boolean stopped, in onHandleIntent() of MyIntentService.java; call stopService() will call onDestroy() of MyIntentService, but the code in onHandleIntent() will keep running until finish by itself. Because onHandleIntent() is running on another worker thread.

Stop IntentService


Layout file
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start IntentService" />
    <Button
        android:id="@+id/stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop IntentService" />
    <TextView
        android:id="@+id/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        style="?android:attr/progressBarStyleHorizontal"
        android:max="10"
        android:progress="0"/>

</LinearLayout>


MainActivity.java
package com.example.androidintentservice;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {

 Button buttonStart, buttonStop;
 TextView textResult;
 ProgressBar progressBar;
 
 private MyBroadcastReceiver myBroadcastReceiver;
 private MyBroadcastReceiver_Update myBroadcastReceiver_Update;
 
 Intent intentMyIntentService;
 int numberOfIntentService;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textResult = (TextView)findViewById(R.id.result);
  progressBar = (ProgressBar)findViewById(R.id.progressbar);
  buttonStart = (Button)findViewById(R.id.start);
  buttonStop = (Button)findViewById(R.id.stop);
  
  buttonStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    numberOfIntentService++;
    
    //prepare String passing to intentMyIntentService
    String msgToIntentService = "Android-er: " + numberOfIntentService;

    //Start MyIntentService
    intentMyIntentService = new Intent(MainActivity.this, MyIntentService.class);
    intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_IN, msgToIntentService);
    startService(intentMyIntentService);
   }});
  
  buttonStop.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    if(intentMyIntentService != null){
     stopService(intentMyIntentService);
     intentMyIntentService = null;
    }
   }});

  numberOfIntentService = 0;
  
  myBroadcastReceiver = new MyBroadcastReceiver();
  myBroadcastReceiver_Update = new MyBroadcastReceiver_Update();
  
  //register BroadcastReceiver
  IntentFilter intentFilter = new IntentFilter(MyIntentService.ACTION_MyIntentService);
  intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver, intentFilter);
  
  IntentFilter intentFilter_update = new IntentFilter(MyIntentService.ACTION_MyUpdate);
  intentFilter_update.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver_Update, intentFilter_update);
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  //un-register BroadcastReceiver
  unregisterReceiver(myBroadcastReceiver);
  unregisterReceiver(myBroadcastReceiver_Update);
 }

 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   String result = intent.getStringExtra(MyIntentService.EXTRA_KEY_OUT);
   textResult.setText(result);
  }
 }
 
 public class MyBroadcastReceiver_Update extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   int update = intent.getIntExtra(MyIntentService.EXTRA_KEY_UPDATE, 0);
   progressBar.setProgress(update);
  }
 }

}


MyIntentService.java
package com.example.androidintentservice;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

public class MyIntentService extends IntentService {
 
 private static final int MY_NOTIFICATION_ID=1;
 private static final int MY_DESTORY_NOTIFICATION_ID=2;
 NotificationManager notificationManager;
 Notification myNotification;
 
 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String ACTION_MyUpdate = "com.example.androidintentservice.UPDATE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 public static final String EXTRA_KEY_UPDATE = "EXTRA_UPDATE";
 String msgFromActivity;
 String extraOut;
 
 boolean success;
 boolean stopped;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
  success = false;
  stopped = false;
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  msgFromActivity = intent.getStringExtra(EXTRA_KEY_IN);
  extraOut = "Hello: " +  msgFromActivity;
  
  //total 10 sec
  for(int i = 0; i <=10; i++){
   
   try {
    Thread.sleep(1000); //every 1 sec
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
   //--- Try to comment it ---//
   if(stopped){
    break;
   }
   
   //send update 
   Intent intentUpdate = new Intent();
   intentUpdate.setAction(ACTION_MyUpdate);
   intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
   intentUpdate.putExtra(EXTRA_KEY_UPDATE, i);
   sendBroadcast(intentUpdate);
   
   PendingIntent pendingIntent_doNothing = PendingIntent.getActivity(
     getApplicationContext(), 
     0, 
     new Intent(),      //empty Intent do nothing
     Intent.FLAG_ACTIVITY_NEW_TASK);
   
   //generate notification
   String notificationText = String.valueOf((int)(100 * i / 10)) + " %";
   myNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle("Progress")
   .setContentText(notificationText)
   .setTicker("Notification!")
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .setContentIntent(pendingIntent_doNothing)
   .build();
   
   notificationManager.notify(MY_NOTIFICATION_ID, myNotification);
  }
  
  success = true;
  myNotification = new NotificationCompat.Builder(getApplicationContext())
  .setContentTitle("Success Finished")
  .setContentText("Successful Finished")
  .setTicker("Successful Finished")
  .setWhen(System.currentTimeMillis())
  .setDefaults(Notification.DEFAULT_SOUND)
  .setAutoCancel(true)
  .setSmallIcon(R.drawable.ic_launcher)
  .build();
  notificationManager.notify(MY_NOTIFICATION_ID, myNotification);
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

 @Override
 public void onCreate() {
  super.onCreate();
  notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
 }

 @Override
 public void onDestroy() {
  Notification onDestroyNotification;
  String notice;
  
  stopped = true;
  
  if(success){
   notice = "onDestroy with success";
   onDestroyNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle(notice)
   .setContentText(msgFromActivity)
   .setTicker(notice)
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .build();
  }else{
   notice = "onDestroy WITHOUT success!";
   onDestroyNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle(notice)
   .setContentText(msgFromActivity)
   .setTicker(notice)
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .build();
  }
  notificationManager.notify(MY_DESTORY_NOTIFICATION_ID, onDestroyNotification);
  Toast.makeText(getApplicationContext(), notice, Toast.LENGTH_LONG).show();
  
  super.onDestroy();
 }

}


Need to include <service> of "MyIntentService" in AndroidManifest.xml, refer to the post "Send data from IntentService to Activity, via additional Broadcast".


download filesDownload the files.

3 comments:

josemfr said...

Thanks you very much.

JD from Oaktown said...

great post. Thank you!

JD from Oaktown said...

I personally find the terminology/nomenclature to be dissatisfying/misleading.
"onDestroy" and "stopService" might be better understood if they were called "FlagForAndroidOSDestruction" and "FlagForAndroidStopService".

If one downloads/compiles/runs any of the following examples, one can see that even when the OnHandleIntent is finished or stopService has been called, the process and even the service can still hang around! To see this simply launch the example(s) below, and then on your phone/tablet goto
Settings->Apps->Running->Show Running Services
and
Settings->Apps->Running->Show Cached Processes

When you see these, try launching a ton of other apps on the phone and THEN you'll see Android destroying said service & process.


http://developer.android.com/guide/components/services.html#ExtendingIntentService

http://android-er.blogspot.com/2013/03/stop-intentservice.html

http://stackoverflow.com/questions/5523718/how-to-check-all-the-running-services-in-android