Android meets RESTful Services

Publicado por Fabio A. Falavinha
06/12/2011
Categoria:
Tags: , , , , ,

Fala pessoALL,  este artigo tem como objetivo descrever a comunicação entre aplicações android via serviços REST.

REST significa Representational State Transfer. É um design de arquitetura desenvolvido na tese de doutorado do Dr. Roy Fielding, criador do protocolo HTTP. Os serviços baseados nesta arquitetura são chamados de RESTful Services.

O propósito desta arquitetura não é substituir o uso do protocolo SOAP (Simple Object Access Protocol), e sim ter como alternativa a este protocolo o uso mais simples e flexível em termos de tecnologia. O protocolo SOAP é baseado em ações (actions) e utiliza apenas o verbo POST do protocolo HTTP. Este é o principal problema deste protocolo, pois para a requisição de dados a um Web Service, o uso de POST não suporta cache de dados, o que ocasiona em problemas de performance na transmissão de dados.

O funcinamento de serviços REST são baseadas na chamadas de URI (Uniform Resource Identifier) através dos verbos do protocolo HTTP, veja a figura abaixo.

HTTP Verbs

Por ser uma arquitetura e não uma especificação de um protocolo, REST não define um formato padrão de dados. Portanto o formato é definido pelo atributo Content-Type, no cabeçalho (header) da requisição HTTP:

  • XML (application/xml): formato mais utilizado para representar dados.
  • RSS/Atom (application/rss+xml e application/atom+xml): Atom (Atom Syndication Format) e RSS (Really Simple Syndication) são formatos baseados na representação em XML para publicação de feeds (notícias).
  • JSON (appliation/json): JSON (JavaScript Object Notation) é um formato baseado em texto simples para facilitar a comunicação entre aplicações clientes e serviços. O processo de serialização/deserialização de dados em formato JSON é mais performática que em formato XML.

Criando o primeiro RESTful Service

Para criar o serviço REST vou utilizar a tecnologia WCF (Windows Communication Foundation) versão .NET 4.0. Esta tecnologia nos permite, de forma simples, criar um serviço através de anotações. Portanto, como primeiro exemplo, vou criar um serviço para cadastro de contatos.

namespace AndroidMeetsREST.Example.Contact
{
    [ServiceContract(Namespace = "http://androidmeetsrest.zbra.com.br")]
    public interface IContactService
    {
        [WebGet(UriTemplate = "/contacts", ResponseFormat = WebMessageFormat.Xml)]
        List<Contact> GetContacts();

        [WebInvoke(Method = "PUT", UriTemplate = "/contact/{id}")]
        void SaveContact(long id, Contact contact);
    }

    [DataContract]
    public class Contact
    {
	[DataMember]
	public long Id { get; set; }

	[DataMember]
	public string Name { get; set; }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ContactService : IContactService
    {
        private static readonly List<Contact> repository = new List<Contact>();

        static ContactService()
        {
              repository.Add(new Contact() { Id = 1, Name = "Steve Jobs" });
              repository.Add(new Contact() { Id = 2, Name = "Bill Gates" });
        }

        public List<Contact> GetContacts()
        {
            return repository;
        }

        public void SaveContact(long id, Contact contact)
        {
             var found = repository.SingleOrDefault<Contact>(c => c.Id == id);
             if (found != null)
             {
                 found.Name = contact.Name;
             }
             else
             {
                 var context = WebOperationContext.Current.OutgoingResponse;
                 context.SetStatusAsNotFound();
                 context.SuppressEntityBody = true;
             }
        }

    }

}

Para que um serviço WCF aceite requisições HTTP, baseados na arquitetura REST, basta marcar as operações com as anotações:

  • WebGet: anotação que representa o verbo GET do protocolo HTTP. Com o atributo UriTemplate pode-se definir o contexto da requisição. Caso não seja informado, a requisição será identificada pelo verbo GET. Através do atributo ResponseFormat e RequestFormat (não utilizado no exemplo) pode-se definir o formato dos dados de entrada e saída do serviço como: XML ou JSON.
  • WebInvoke: anotação para representação dos verbos POST, PUT e DELETE através do atributo Method. Pode-se configurar o formato de entrada e saída dos dados através dos atributos RequestFormat e ResponseFormat.

Observem que na operação SaveContact informo o [id] do contato e um objeto Contato com o novo nome. A lógica desta operação é simples mas destaco as linhas 48-50 do código acima. Caso o id informado na URL não for encontrado no repositório de dados, a resposta no protocolo HTTP será NotFound (404).

Todas as operações de um serviço REST devem ter o tratamento adequado para operações com sucesso e, principalmente, em caso de falha. Portanto, utilizem a classe de contexto para informar o cliente sobre possíveis falhas:

      OutgoingWebResponseContext context = WebOperationContext.Current.OutgoingResponse;

Para publicar o WCF deve-se configurar o arquivo de configuração [Web.Config] com WebHttpBinding e do WebHttpBehavior. Esta configuração disponibiliza o serviço para chamadas via protocolo HTTP.

Acessando o serviço com Android

Para acessar o serviço REST vou criar uma aplicação Android que contém uma lista de contatos (ListView). Ao selecionar um contato exibe uma nova tela (Activity) para a alteração do nome. A versão a ser utilizada será Android 3.0. Para acesso aos serviços REST, vou utilizar duas blibliotecas:

  • Spring Android v1.0.0.M4: suporte a chamadas REST, com parse e conversão de dados de forma dinâmica.
  • Google Gson v2.0: implementação de parsers e converters para o formato JSON.

public class Contact implements Serializable {

   public long Id;
   public String Name;

}

public interface ContactListView {

     void setContactList(Contact[] contacts);

}

public class ContactListActivity extends ListActivity implements ContactListView {

    private ContactService contactService;
    private Contact[] contacts;

    @Override
    public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
        setContentView(R.layout.contactList);
        this.contactService = new ContactService(this);
        this.contactService.listContacts();
        final ListView listView = getListView();
	listView.setOnItemClickListener(new OnItemClickListener() {
	       public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                  Contact contact = contacts[position];
                  Intent intent = new Intent(this, ContactInfoActivity.class);
                  intent.putExtra("contact", contact);
                  startActivity(intent);
	       }
        });
    }

    public void setContactList(Contact[] contacts) {
        this.contacts = contacts;
        setListAdapter(new ArrayAdapter<Contact>(this, android.R.layout.simple_list_item_1, contacts));
    }

}

public class ContactService {

		private final ContactListView view;

		public ContactService(ContactListView view) {
			 this.view = view;
		}

		public void listContacts() {
			Thread t = new Thread(new GetContactsRunnable(this.view));
			t.Start();
		}

		public void saveContact(Contact contact) {
			Thread t = new Thread(new SaveContactRunnable(contact));
			t.Start();
		}

		private class GetContactsRunnable implements Runnable {

			private static final String url = "http://10.0.2.2:54351/ContactService/contacts";

			private final ContactListView view;

			public GetContactsRunnable(ContactListView view) {
				this.view = view;
			}

			@Override
			public void run() {
				RestTemplate restTemplate = new RestTemplate();
				restTemplate.getMessageConverters().add(new GsonHttpMessageConverter());
				Contact[] contacts = restTemplate.getForObject(url, Contact[].class);
				for (Contact contact : contacts) {
					Log.d("SERVICE", "contact => " + contact.Id + ", " + contact.Name);
				}
				view.setContactList(contacts);
			}
		}

		private class SaveContactRunnable implements Runnable {

			private static final String url = "http://10.0.2.2:54351/ContactService/contact/";

			private final Contact contact;

			public SaveContactRunnable(Contact contact) {
				this.contact = contact;
			}

			@Override
			public void run() {
				RestTemplate restTemplate = new RestTemplate();
				restTemplate.put(url + contact.Id, contact);
			}
		}

}

O código acima exibe a forma de popular a lista de contatos através do serviço ContactService. Este serviço realiza a chamada REST, via framework Spring Android, e ao finalizar realiza o bind de array de contatos à UI.

Notem que o uso da classe RestTemplate é feito dentro de um contexto de thread, liberando o UI para realizar as operações de renderização. Toda aplicação Android (mobile) que utiliza comunicação via socket deve realizar o processamento dentro de uma Thread. Um modo eficiente é utilizar a classe AsyncTask para qualquer processamento paralelo dentro da plataforma android.

Conclusão

Aplicações mobile devem ser rápidas na comunicação com serviços online. Plataformas como Facebook e Twitter utilizam serviços REST para prover funcionalidade a aplicações em dispositivos móveis como celulares, tablets e até mesmo sistemas embeddeds.

O  Google apoia o uso de REST como plataforma de integração a aplicações Android pelo simples fato de ser mais eficiente do que consumir serviços web via SOAP.

Referências





Desenvolvido por hacklab/ com WordPress