Статьи

Реализация конечного автомата для длительной фоновой задачи, выполняемой в службе Android

В приведенном ниже примере я покажу, как мы можем разбить долго выполняющуюся фоновую задачу, выполняющуюся в службе, на различные состояния конечного автомата и уведомить интерфейс переднего плана о каждом этапе, когда они происходят в службе. Здесь я использовал сервис LongRunningService, который фактически (теоретически) выполняет задачу загрузки большого файла с сетевого сервера (однако для простоты я только что заглушил реальный код загрузки с потоком с задержкой 1000 мс). Эта фоновая задача была разбита на различные состояния в соответствии с конечным автоматом, таким как «Начать соединение», «Соединение установлено», «Начать загрузку» и «Остановить загрузку». Это приложение также демонстрирует концепцию взаимодействия фонового сервиса с интерфейсом внешнего интерфейса через платформу Android Messenger.

Итак, давайте начнем копаться в исходном коде приложения.

Прежде всего, основной класс деятельности.

Как видно из кода, в основном действии есть мессенджер, чья часть обработки сообщений была определена классом MessageHandler (производным от Handler). Это объект мессенджера, через который фоновая служба уведомляет поток пользовательского интерфейса.

Пользовательский интерфейс имеет кнопку. При нажатии на нее запускается служба, и как только она запускается, служба начинает уведомлять о различных состояниях службы через мессенджер.

Это довольно просто. Правильно!!!

Класс 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);
	}
}

Теперь давайте начнем копать класс LongrunningServivce.

Как мы знаем, сервис обычно работает в основном потоке. Следовательно, поток UI может показаться замороженным в случае длительного фонового обслуживания. Чтобы преодолеть это, создается фоновый поток в тот момент, когда запускается служба, и задача выполняется в этом потоке. Это ясно из следующего фрагмента кода.

@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);
	  }

Класс сервиса также имеет класс Handler, называемый ServiceHandler, через который мы отправляем сообщения от сервиса в цикл сообщений потока. Внутри цикла сообщений мы фактически выполняем долгосрочную задачу. Давайте посмотрим на этот класс ServiceHandler

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

Как видно из приведенного выше кода, в этой переопределенной функции HandleMessage обработчика службы мы получаем ссылку на мессенджер основного действия, и он проходит через различные состояния, такие как «Соединение», «Подключен», «Начать загрузку» и «Завершить загрузку». В каждом состоянии разные целочисленные константы передаются в поток пользовательского интерфейса через мессенджер.

В главном потоке пользовательского интерфейса функция-обработчик мессенджера обрабатывает эти сообщения от службы и отображает состояние каждого состояния.

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();
	  }
	}