Caso prefira, você encontrará todo esse material, em inglês, no site do Developer Android. A tradução e comentários dos materiais eu faço livremente para ajudar a comunidade que fala português.

quinta-feira, 7 de abril de 2011

Services - Parte 02 - Started Service

Criando um Started Service

Um started service é um que outro componente inicia chamando startService(), resultando numa chamada para o método onStartCommand() do serviço.

Quando um serviço é iniciado, ele tem um ciclo de vida que é independente da do componente que o iniciou e o serviço pode rodar em background indefinidamente, mesmo que o componente que o iniciou seja destruído. Como tal, o serviço deverá ser parado apenas se o seu trabalho está concluído chamando stopSelf() ou outro componente pode parálo chamando stopService().

Um componente de aplicação como uma atividade pode iniciar o serviço chamando startService() e passando um Intent que especifica o serviço e inclui quaisquer dados para o serviço usar. O serviço recebe esse Intent no onStartCommand().

Suponha que uma atividade precise salvar dados em um banco de dados online. A atividade pode iniciar um serviço e entregar dados para salvar passando o intent para o startService(). O serviço recebe o intent no onStartCommand(), conecta-se à internet e faz a transação com o banco de dados. Quando a transação está realizada, o serviço pára e depois se destrói.

Tradicionalmente, existem duas classes que você pode extender para criar um started service.

Service
É a classe base para todos os serviços. Quando você extende essa classe é importante que você crie uma nova thread na qual fará todo o trabalho do service já que o serviço usa a thread principal de sua aplicação por default e que poderia diminuir sobremaneira a performance de quaisquer atividades que sua aplicação esteja rodando.

IntentService
É a subclasse de Service que usa um worker thread para manusear todos as requisições iniciais, um de cada vez. Essa é a melhor opção se você não quer que o seu serviço manuseie multiplas requisições simultâneamente. Todo que você precisa fazer é implementar onHandleIntent() que recebe o intent para cada requisição inicial para que você possa fazer o trabalho de background.

Extendendo a classe IntentService

Como muitos dos serviços iniciados não precisam manusear multiplas requisições simultaneamente (o que poderia ser muito perigoso para um cenário multi-threading), é provavelmente melhor que você implemente seu serviço usando a classe IntentService.

Um IntentService faz o seguinte:

  • Cria uma classe padrão worker thread que executa todos os intents recebidos no onStartCommand() separado da thread principal da aplicação.
  • Cria uma fila de trabalhos que passam um intent a cada vez para a sua implementação de onHandleIntent() para que você nunca precise se preocupar com multi-threading.
  • Pára o serviço após todos as requisições iniciais terem sido processadas para que você nunca precise chamada stopSelf().
  • Prove a implementação padrão de onBind() que retorna null
  • Prove uma implementação padrão de onStartCommand() que envia o intent para a fila de trabalhos e então para sua implementação de onHandleIntent().
Tudo isso adicionado ao fato de que tudo que você precisa implementar é o método onHandleIntent() para fazer o trabalho provido pelo cliente (apesar de você precisar prover um pequeno construtor para o serviço).

Aqui está um exemplo de implementação de IntentService:
public class HelloIntentService extends IntentService {


  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }


  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

Isso é tudo o que é necessário: um construtor e uma implementação de onHandleIntent().

Se você decidir também por fazer um override de outro método callback como onCreate(), onStartCommand() ou onDestroy(), esteja certo de chamar a super implementação para que o IntentService possa processar corretamente a vida do worker thread.

Por exemplo, o onStartCommand() deve retornar para a implementação padrão (que é como o intent é entregue para o onHandleIntent()):
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}
Além de onHandleIntent(), o único método a partir do qual você não precisará chamar a super classe é o onBind() (mas você precisará apenas implementar isso se o seu serviço permitir binding).

Extendendo a classe Service

Como visto na seção anterior, usar o IntentService faz sua implementação de um started service muito simples. Mas e se você quiser que seu serviço faça uso de multi-threading(ao invés de processar requisições iniciais através de uma fila de trabalho), então você pode extender a classe Service para manusear cada intent.

Para efeito de comparação, o exemplo seguinte é uma implementação de uma classe Service que realiza o trabalho exato como o do exemplo acima que usou o IntentService. Ou seja, para cada requisição inicial, ele usa um worker thread para realizar o trabalho e processar apenas uma requisição a cada vez.
public class HelloService extends Service {
  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) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // 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",
            Process.THREAD_PRIORITY_BACKGROUND);
    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, "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();
  }
}
Como pode ver, acima há muito mais código do que havia quando usamos o IntentService.

Mas como você tem de manusear cada chamada para onStartCommand() você mesmo, você pode realizar multiplas requisições simultâneamente. Não é o que o exemplo faz mas se é o que você quer, então você pode criar uma nova thread para cada requisição e rodá-los separadamente (ao invés de esperar a thread anterior terminar para a sua iniciar).

Note que o onStartCommand() deve retornar um inteiro. Um inteiro é um valor que descreve como o sistema deve continuar o serviço no evento deste matá-lo. O valor retornado de onStartCommand() deve ser uma das seguintes constantes:

START_NOT_STICKY
START_STICKY
START_REDELIVER_INTENT

Iniciando um serviço

Você pode iniciar um serviço a partir de uma atividade ou outro componente de aplicação passando um Intent para o startService(). O sistema Android chama o método onStartCommand() do serviço e passa-o para o Intent. Você nunca deverá chamar onStartCommand() diretamente. Deixe o sistema Android fazê-lo por você.

Por exemplo, uma atividade pode iniciar o serviço como abaixo:

Intent intent = new Intent(this, HelloService.class);
startService(intent);

O método startService retorna imediaamente e o sistema Android chama o método onStartCommand() do serviço. Se o serviço não está sendo rodado, o sistema primeiro chama onCreate() e então onStartCommand().

Se o serviço não prove binding, o intent entregue com o startService() é a única maneira de comunicação entre o componente de aplicação e o serviço. Contudo, se você quer que o serviço envie um resultado de volta, então o cliente que inicia o serviço pode criar um PendingIntent para um broadcast (com getBroadcast()) e entregá-lo ao serviço no Intent que inicia o serviço. O serviço pode então usar o broadcast para entregar o resultado.

Multiplas requisições para iniciar um serviço resultam em multiplas chamadas correspondentes para o onStartCommand() do serviço. Mas, apenas uma requisição para parar o serviço (com stopSelf() ou stopService()) é requerida.

Parando um serviço

Um started service deve gerenciar seu ciclo de vida. Ou seja, o sistema não pára ou destrói o serviço a menos que ele precise recuperar memória. Então, o serviço deve parar a si mesmo chamando stopSelf() ou outro componente pode pará-lo chamando stopService().

Uma vez que seja requisitado a parada com o stopSelf() ou stopService(), o sistema destrói o serviço assim que possível.

0 comentários:

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
Related Posts Plugin for WordPress, Blogger...