DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Introduction To Git
  • Auto-Scaling Kinesis Data Streams Applications on Kubernetes
  • Which Is Better for IoT: Azure RTOS or FreeRTOS?

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Introduction To Git
  • Auto-Scaling Kinesis Data Streams Applications on Kubernetes
  • Which Is Better for IoT: Azure RTOS or FreeRTOS?
  1. DZone
  2. Data Engineering
  3. AI/ML
  4. Implementing a State Machine for a Long Running Background Task Running in Android Service

Implementing a State Machine for a Long Running Background Task Running in Android Service

Somenath Mukhopadhyay user avatar by
Somenath Mukhopadhyay
·
Mar. 10, 15 · Interview
Like (0)
Save
Tweet
Share
5.01K Views

Join the DZone community and get the full member experience.

Join For Free

In the below example i will show how we can break a long running background task running in a service into different states of a state machine and notify the front end UI about each and every stage as they occur in the service. Here i have used a service called LongRunningService which actually (theoretically) does the task of downloading a big file from a network server (however, for simplicity i have just stubbed out the actual download code with a thread having delay of 1000 ms). This background task has been splitted into different states according to the state machine like “Start Connection”, “Connection Completed”, “Start Downloading” and “Stop Downloading”. This application also showcases the concept of communicating from a background service to the frontend UI through Android messenger framework.

So lets start digging into the source code of the application.

First of all the main Activity class.

As it is clear from the code that the main activity has a messenger whose message handling part has been defined by a class called MessageHandler (derived from Handler). This is the messenger object through which the background service notifies the UI thread.

The UI has a button. Upon clicking it, it starts the service and as soon as it starts the service the service starts notifying about the different states of the Service through the messenger.

This is pretty simple. Right!!!

The class MainActivity.Java

package com.somitsolutions.android.example.statepatterninservice;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener{
	private static final int CONNECTING = 1;
	private static final int CONNECTED = 2;
	private static final int DOWNLOADSTARTED = 3;
	private static final int DOWNLOADFINISHED = 4;
	
	Button startButton;
	private MessageHandler handler;
	private static MainActivity mMainActivity;
	
	public Messenger mMessenger = new Messenger(new MessageHandler(this));
	private class MessageHandler extends Handler{
		private Context c;
		
		MessageHandler(Context c){
		 this.c = c;	
		}
		@Override
        public void handleMessage(Message msg) {
			switch(msg.what){
			case CONNECTING:
				Toast.makeText(getApplicationContext(), "Connecting", Toast.LENGTH_LONG).show();
				break;
			case CONNECTED:
				Toast.makeText(getApplicationContext(), "Connected", Toast.LENGTH_LONG).show();
				break;
			case DOWNLOADSTARTED:
				Toast.makeText(getApplicationContext(), "Download Started", Toast.LENGTH_LONG).show();
				break;
			case DOWNLOADFINISHED:
				Toast.makeText(getApplicationContext(), "Download Finished", Toast.LENGTH_LONG).show();
				break;
			default:
				super.handleMessage(msg);
					
			}
		}
	}
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMainActivity = this;
        startButton = (Button)findViewById(R.id.button1);
        
        startButton.setOnClickListener(this);
    }
  
    public static MainActivity getMainActivity(){
    	return mMainActivity;
    }


	@Override
	public void onClick(View arg0) {
		// TODO Auto-generated method stub
		Intent serv = new Intent(MainActivity.this, LongRunningService.class);
		//Toast.makeText(getApplicationContext(), "Test", Toast.LENGTH_LONG).show();
        startService(serv);
	}
}

Now lets start digging the LongrunningServivce class.

As we know that a service usually runs in the main thread. Hence the UI thread may seem to be frozen in case of a long background service. To overcome that a background thread is being created the moment one starts the service and the task is executed in that thread. This is clear from the following piece of code.

@Override
	  public void onCreate() {
	    // Start up the thread running the service.  Note that we create a
	    // separate thread because the service normally runs in the process's
	    // main thread, which we don't want to block.  We also make it
	    // background priority so CPU-intensive work will not disrupt our UI.
	    HandlerThread thread = new HandlerThread("ServiceStartArguments",
	            Thread.NORM_PRIORITY);
	    thread.start();

	    // Get the HandlerThread's Looper and use it for our Handler
	    mServiceLooper = thread.getLooper();
	    mServiceHandler = new ServiceHandler(mServiceLooper);
	  }

The service class also has a Handler class called ServiceHandler through which we send messages from the service to the thread’s message loop. Inside the message loop, we actually accomplish the long running task. Lets have a look at this ServiceHandler class

private final class ServiceHandler extends Handler {
	      public ServiceHandler(Looper looper) {
	          super(looper);
	      }
	      @Override
	      public void handleMessage(Message msg) {
	          
	    	
	    	  Messenger messenger= MainActivity.getMainActivity().mMessenger;
			  
		         try {
		        	 
		        	 
		        	 messenger.send(Message.obtain(null, CONNECTING, "Connecting"));
		        	// Normally we would do some work here, like download a file.
			          // For our sample, we just sleep for 10 seconds.
					Thread.sleep(1000);
					// Normally we would do some work here, like download a file.
			          // For our sample, we just sleep for 10 seconds.
					
					messenger.send(Message.obtain(null, CONNECTED, "Connected"));
					// Normally we would do some work here, like download a file.
			        // For our sample, we just sleep for 10 seconds.
					Thread.sleep(1000);
					
					
					messenger.send(Message.obtain(null, DOWNLOADSTARTED, "Download Started"));
					// Normally we would do some work here, like download a file.
			        // For our sample, we just sleep for 10 seconds.
					Thread.sleep(1000);
					
					
					messenger.send(Message.obtain(null, DOWNLOADFINISHED, "Download Finished"));
					
					
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
	          // Stop the service using the startId, so that we don't stop
	          // the service in the middle of handling another job
	          stopSelf(msg.arg1);
	      }
	  }

As it becomes clear from the above code that in this overridden HandleMessage function of the service handler, we acquire a reference to the messenger of the main activity and it falls through different states like “Connecting”, “Connected”, “Start Downloading” and “Finish Downloading”. In each state a different integer constant is being passed to the UI thread through the messenger.


In the main UI thread the handler function of the messenger handles these messages from the service and displays the status of the each state.

package com.somitsolutions.android.example.statepatterninservice;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;

public class LongRunningService extends Service {
	
	private static final int CONNECTING = 1;
	private static final int CONNECTED = 2;
	private static final int DOWNLOADSTARTED = 3;
	private static final int DOWNLOADFINISHED = 4;
	
	private Looper mServiceLooper;
	private ServiceHandler mServiceHandler;	// Handler that receives messages from the thread
	private final class ServiceHandler extends Handler {
	      public ServiceHandler(Looper looper) {
	          super(looper);
	      }
	      @Override
	      public void handleMessage(Message msg) {
	          
	    	
	    	  Messenger messenger= MainActivity.getMainActivity().mMessenger;
			  
		         try {
		        	 
		        	 
		        	 messenger.send(Message.obtain(null, CONNECTING, "Connecting"));
		        	// Normally we would do some work here, like download a file.
			          // For our sample, we just sleep for 10 seconds.
					Thread.sleep(1000);
					// Normally we would do some work here, like download a file.
			          // For our sample, we just sleep for 10 seconds.
					
					messenger.send(Message.obtain(null, CONNECTED, "Connected"));
					// Normally we would do some work here, like download a file.
			        // For our sample, we just sleep for 10 seconds.
					Thread.sleep(1000);
					
					
					messenger.send(Message.obtain(null, DOWNLOADSTARTED, "Download Started"));
					// Normally we would do some work here, like download a file.
			        // For our sample, we just sleep for 10 seconds.
					Thread.sleep(1000);
					
					
					messenger.send(Message.obtain(null, DOWNLOADFINISHED, "Download Finished"));
					
					
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
	          // Stop the service using the startId, so that we don't stop
	          // the service in the middle of handling another job
	          stopSelf(msg.arg1);
	      }
	  }
	
	@Override
	  public void onCreate() {
	    // Start up the thread running the service.  Note that we create a
	    // separate thread because the service normally runs in the process's
	    // main thread, which we don't want to block.  We also make it
	    // background priority so CPU-intensive work will not disrupt our UI.
	    HandlerThread thread = new HandlerThread("ServiceStartArguments",
	            Thread.NORM_PRIORITY);
	    thread.start();

	    // Get the HandlerThread's Looper and use it for our Handler
	    mServiceLooper = thread.getLooper();
	    mServiceHandler = new ServiceHandler(mServiceLooper);
	  }

	  @Override
	  public int onStartCommand(Intent intent, int flags, int startId) {
	      Toast.makeText(getApplicationContext(), "download service starting", Toast.LENGTH_SHORT).show();

	      // For each start request, send a message to start a job and deliver the
	      // start ID so we know which request we're stopping when we finish the job
	      Message msg = mServiceHandler.obtainMessage();
	      msg.arg1 = startId;
	      mServiceHandler.sendMessage(msg);
	       
	      // If we get killed, after returning from here, restart
	      return START_STICKY;
	  }

	  @Override
	  public IBinder onBind(Intent intent) {
	      // We don't provide binding, so return null
	      return null;
	  }

	  @Override
	  public void onDestroy() {
	    Toast.makeText(getApplicationContext(), "service done", Toast.LENGTH_SHORT).show();
	  }
	}


Task (computing) Machine Android (robot)

Opinions expressed by DZone contributors are their own.

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Introduction To Git
  • Auto-Scaling Kinesis Data Streams Applications on Kubernetes
  • Which Is Better for IoT: Azure RTOS or FreeRTOS?

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: