Tutorial Android #16 – Utilizando um IntentService

Padrão

Olá pessoal! No tutorial anterior, utilizamos uma AsyncTask para recuperar o conteúdo do Twitter. Isso foi necessário para que pudéssemos obter comunicação com a rede fora da thread principal do aplicativo e, portanto, evitar lentidão na interface. Outra forma de resolver esse problema é usando um IntentService. Um IntentService é um componente à parte que aceita comandos vindos de uma Activity, executa os comandos em linhas em background e, opcionalmente, responde às atividades ou o usuário. Neste tutorial, vamos configurar como um IntentService como um substituto para a AsyncTask.

Todos prontos?

Primeiramente, crie uma nova classe no pacote net.rafaeltoledo.restaurante chamada TwitterService, estendendo IntentService:

package net.rafaeltoledo.restaurante;</p>
<p>import android.app.IntentService;<br />
import android.content.Intent;</p>
<p>public class TwitterService extends IntentService {</p>
<p>	public TwitterService() {<br />
		super(&quot;TwitterService&quot;);<br />
	}</p>
<p>	@Override<br />
	protected void onHandleIntent(Intent intent) {</p>
<p>	}<br />
}

Em seguida, vamos adicionar um novo nó service lá no arquivo AndroidManifest.xml logo após os nós activity, dentro do nó application.

&lt;service android:name=&quot;.TwitterService&quot;&gt;<br />
&lt;/service&gt;

O método onHandleIntent() da IntentService é chamado sempre em background, razão principal de a utilizarmos. Vamos começar com uma implementação inicial deste método lá na nossa classe TwitterService, importando parte da lógica que tínhamos no método doInBackground():

@Override<br />
protected void onHandleIntent(Intent intent) {<br />
	Twitter t = new TwitterFactory().getInstance();</p>
<p>	try {<br />
		List&lt;Status&gt; resultado = t.getUserTimeline(intent.getStringExtra(PERFIL_EXTRA));<br />
	} catch (Exception ex) {<br />
		Log.e(&quot;ListaRestaurantes&quot;, &quot;Erro manipulando timeline twitter&quot;, ex);<br />
	}<br />
}

Adicione também o atributo da classe referenciado no método, que servirá para obter o perfil do Twitter que obteremos os tweets.

public static final String PERFIL_EXTRA = "net.rafaeltoledo.PERFIL_EXTRA";

Continuando, precisamos agora enviar os tweets para a Activity. Para realizar a comunicação, utilizaremos um Messenger, que servirá para obtermos informações do serviço. Dessa forma, atualize a implementação do método onHandleIntent():

@Override<br />
protected void onHandleIntent(Intent intent) {<br />
	Twitter t = new TwitterFactory().getInstance();<br />
	Messenger messenger = (Messenger) intent.getExtras().get(MESSENGER_EXTRA);<br />
	Message msg = Message.obtain();</p>
<p>	try {<br />
		List&lt;Status&gt; resultado = t.getUserTimeline(intent.getStringExtra(PERFIL_EXTRA));</p>
<p>		msg.arg1 = Activity.RESULT_OK;<br />
		msg.obj = resultado;<br />
	} catch (Exception ex) {<br />
		Log.e(&quot;ListaRestaurantes&quot;, &quot;Erro manipulando timeline twitter&quot;, ex);<br />
		msg.arg1 = Activity.RESULT_CANCELED;<br />
		msg.obj = ex;<br />
	}</p>
<p>	try {<br />
		messenger.send(msg);<br />
	} catch (Exception ex) {<br />
		Log.w(&quot;ListaRestaurantes&quot;, &quot;Erro enviando dados para a Activity&quot;, ex);<br />
	}<br />
}

Para completar, só precisamos adicionar o atributo MESSENGER_EXTRA a nossa classe.

public static final String MESSENGER_EXTRA = "net.rafaeltoledo.MESSENGER_EXTRA";

Por fim, vamos fazer as modificações na TwitterActivity para que ela trabalhe com o TwitterService em vez da TarefaTwitter.

Primeiramente, vamos converter a nossa TarefaTwitter para HandlerTwitter, que estenderá Handler em vez de AsyncTask. Os métodos anexar() e desanexar() serão mantidos para gerenciar as mudanças na configuração. Já o método doInBackground() será removido, já que a lógica foi movida para o serviço. O método onPostExecute() vira handleMessage(), para pegar o objeto Message do TwitterService, chamando os métodos atribuirTweets() ou atirarErro() dependendo do retorno do serviço. O resultado será esse:

private static class HandlerTwitter extends Handler {<br />
	private TwitterActivity activity = null;</p>
<p>	HandlerTwitter(TwitterActivity activity) {<br />
		anexar(activity);<br />
	}</p>
<p>	void anexar(TwitterActivity activity) {<br />
		this.activity = activity;<br />
	}</p>
<p>	void desanexar() {<br />
		activity = null;<br />
	}</p>
<p>	@Override<br />
	public void handleMessage(Message msg) {<br />
		if (msg.arg1 == RESULT_OK) {<br />
			activity.atribuirTweets((List&lt;Status&gt;) msg.obj);<br />
		} else {<br />
			activity.atirarErro((Exception) msg.obj);<br />
		}<br />
	}<br />
}

Como não temos mais a TarefaTwitter, não precisamos mais dele no StatusInstancia. Porém, precisamos guardar nosso Handler como parte de nosso status, de forma que quando o usuário rotacionar a tela, nosso  objeto Messenger ainda possa comunicar-se corretamente com a TwitterActivity. Assim, modifique a classe StatusInstancia:

private static class StatusInstancia {<br />
	List&lt;Status&gt; tweets = null;<br />
	HandlerTwitter handler = null;<br />
}

Assim, também precisaremos modificar o método onRetainNonConfigurationInstance() para acomodar o Handler em vez da tarefa.

@Override<br />
public Object onRetainNonConfigurationInstance() {<br />
	if (status.handler != null) {<br />
		status.handler.desanexar();<br />
	}<br />
	return status;<br />
}

Por fim, vamos modificar o método onCreate() para trabalhar com o TwitterService, criando o Messenger caso o status seja nulo, ou anexando-o caso já exista:

@Override<br />
public void onCreate(Bundle savedInstanceState) {<br />
	super.onCreate(savedInstanceState);</p>
<p>	status = (StatusInstancia) getLastNonConfigurationInstance();</p>
<p>	if (status == null) {<br />
		status = new StatusInstancia();<br />
		status.handler = new HandlerTwitter(this);</p>
<p>		Intent i = new Intent(this, TwitterService.class);<br />
		i.putExtra(TwitterService.PERFIL_EXTRA, getIntent().getStringExtra(PERFIL));<br />
		i.putExtra(TwitterService.MESSENGER_EXTRA, new Messenger(status.handler));</p>
<p>		startService(i);<br />
	} else {<br />
		if (status.handler != null) {<br />
			status.handler.anexar(this);<br />
		}</p>
<p>		if (status.tweets != null) {<br />
			atribuirTweets(status.tweets);<br />
		}<br />
	}<br />
}

Pronto! Já podemos executar novamente o aplicativo, e nenhuma mudança deve ser percebida.

Se nenhuma mudança é percebida, por que tudo isso? Bom, pode ser que neste caso a diferença não seja visível, mas imagine que em vez dos tweets fôssemos baixar um vídeo. Utilizando o IntentService, a operação ocorre sem estar vinculada a nenhuma Activity. Ou seja, o usuário não precisa ficar esperando o download terminar para continuar. O IntentService fará o download por si próprio e se auto-destruirá quando terminar. 🙂

O projeto pode ser baixado aqui. E aguardem o próximo post, pois vai ser bacanudo! 😀

Até logo!