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

11 Comentários

  • Reply

    Por joab henrique em 26 de February de 2012 às 3:40

    opa fabio,

    estou tentando desenvolver um aplicativo de venda que a principio usaria webservice para enviar as vendas e receber as cargas de clientes, produtos e etc… o trivial msm
    metodos retornariam os arquivos texto compactados com o dados no formato json e estes seriam processados nas suas respecitivas bases (cargas no lado cliente e vendas no lado servidor)

    como tive problemas ao tentar capturar os dados apos a compactacao dos arquivos (ate postei a duvida aki: http://www.portalandroid.org/comunidade/viewtopic.php?f=5&t=17817)
    resolvi procurar outro caminho e encontrei o seu post que vai servir para minha aplicacao

    ao inves de mandar arquivos textos com os dados, em formato json, dos clientes, produtos e bla bla bla eu mandaria uma lista de uma classe cliente, uma lista de uma classe produto e por ai vai

    comecei a implementar o seu exemplo mas quando executo a aplicacao cliente tenho como resultado a msg ’404 not found’
    a diferenca do meu codigo em relacao ao do exemplo vai ser apenas a porta na constante url nos metodos GetContactsRunnable e SaveContactsRunnable

    obs.: no navegador nao consigo executar a url sem que eu coloque a extensao .svc
    assim obtenho aquela pagina padrao dos servicos .net: http://localhost:2479/ContactService.svc
    assim obtenho uma tela em branco: http://localhost:2479/ContactService.svc/contacts
    assim obtenho o mesmo erro que aparece na aplicacao cliente: Description: HTTP 404. The resource you are looking for…

    ja tentei ate usar a extensao na url mas tambem nao surtiu efeito

    abaixo o webconfig do servico:

    desde ja agradeco
    vlw

  • Reply

    Por Fabio A. Falavinha em 27 de February de 2012 às 17:33

    Olá Joab, vamos primeiro verificar as permissões do serviço WCF. Todo serviço WCF que é configurado com um binding HTTP deve estar liberado na máquina. Um exemplo simples é executar a linha abaixo:

    netsh http add urlacl url=http://+:/ user=

    Outra coisa, acessando a aplicação pela URL que você postou:

    http://localhost:2479/ContactService.svc

    Você consegue fazer a requisição no android usando o contexto WebGet configurado como “/contacts” ??

  • Reply

    Por joab henrique em 28 de February de 2012 às 18:00

    no meu caso nao estou usando o iis e sim o servico de estado, debugando no proprio vs

    se eu uso a url http://localhost:2479/ContactService.svc me retorna ’400 bad request’

    testei o servico utilizando o WCFTestClient.exe disponivel no vs e funcionou

  • Reply

    Por Fabio A. Falavinha em 28 de February de 2012 às 18:23

    Independente do uso de IIS, a permissão deve ser realizada, mas concordo que pode não ser a causa do seu erro.

    Você está utilizando .NET 4.0 ? Se sim, a configuração do seu arquivo Global.asax deve estar assim:

    RouteTable.Routes.Add(new ServiceRoute(“Service1″, new WebServiceHostFactory(), typeof(Service1)));

    Apontando para o serviço que você criou. Para testar basta ir ao navegador e digitar:

    http://localhost:60932/Service1/help

    Isso listará as operações REST configuradas pelo serviço.

    Outra configuração importante é no arquivo App.Config:

    Que deve habilitar o serviço de Route para as rotas HTTP serem controladas pelos objetos criados na factoru exposta no arquivo Global.asax, no meu caso: WebServiceHostFactory.

    Veja se ajuda.

  • Reply

    Por joab henrique em 5 de March de 2012 às 2:46

    opa fabio, consegui fazer funcionar direitinho, dei uma lida nesse link http://msdn.microsoft.com/pt-br/library/dd941696.aspx e pude entender melhor como funciona

    meu web.config estava sem o binding http
    acrescentei as estruturas abaixo e funcionou

    abc vlw

  • Reply

    Por Fabio A. Falavinha em 5 de March de 2012 às 10:22

    Muito bom, Joab! Tinha certeza que faltava alguma configuração no Web.Config.

    Um grande abraço!

  • Reply

    Por joab henrique em 5 de March de 2012 às 16:13

    fabio, vou t pertubar so mais uma x, rs

    digamos que eu ja tivesse minha classe contact implementada dessa maneira

    public class Contact {

    private Long _id;
    private String _name;

    public Long Id()
    {
    return _id;
    }

    public String Name()
    {
    return _name;
    }

    public void setId(Long id)
    {
    _id = id;
    }

    public void setNome(String name)
    {
    _name = name;
    }

    }

    como fazer com que a classe implementada no servico mande os dados pra essa classe no client

    nao sei se estou pensando bobagem mas tipo, ja tenho as classes implementadas com esse encapsulamento e que tambem implementam a interface parcelable porque preciso enviar objetos entre activities, gostaria de reutiliza-las ao invocar os metodos do servico

    abc vlw

  • Reply

    Por Fabio A. Falavinha em 5 de March de 2012 às 16:22

    Olá Joab, fique tranquilo. Estamos aqui para tirar a dúvidas.
    Não importa qual classe seja, se você precisar enviar os dados baseado em um padrão de classes (modelo) no qual já está implementado, basta apenas você implementar do lado cliente (android) um parser específico.
    No caso do artigo, eu usei Gson (google json). Mas se você implementar do lado cliente (android) o mesmo modelo, você conseguirá ter o mesmo binding de atributos. Para você identificar se o nome dos atributos, na mensagem XML, estão corretos, use o Fiddler (http://fiddler2.com/fiddler2/) para investigar o request e o response entre cliente e servidor.

  • Reply

    Por joab henrique em 8 de March de 2012 às 16:48

    opa fabio, vc tem razao, os meus atributos nao estavam iguais, no servico os atributos nao tinha o underline antes do nome

    public int id { get; set; }

    no cliente como o atributo eh uma variavel privada e por padrao eu sempre coloco underline e depois coloco o acesso sem o underline nao deu certo

    private int _id;

    public int id()
    {
    return _id;
    }

    mudei os atributos no servico ficando assim

    public int _id { get; set; }

    aos poucos to pegando o jeito rs
    vlw pela forca
    abc





Desenvolvido por hacklab/ com WordPress