1
\$\begingroup\$

In the below example I have tried to 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 split 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.

Please review this code and provide me with your valuable feedback.

The Main Activity:

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.Menu;
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);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    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);
        startService(serv);
    }
}

The Service Class

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(this, "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(this, "service done", Toast.LENGTH_SHORT).show();
      }
    }
\$\endgroup\$
3
\$\begingroup\$

I would replace

private static final int CONNECTING = 1;
private static final int CONNECTED = 2;
private static final int DOWNLOADSTARTED = 3;
private static final int DOWNLOADFINISHED = 4;

by an Enum which would add typesafety and also helps your IDE to detect e.g. if a state is not handled in your switch statement.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.