as
A revista do desenvolvedor Web e Wireless
Java - .NET - Games - PalmSource - Mercado - J2ME - Asp.NET
ágin p 4 8
Infra-estrutura web Não seja mais um colador de código!
ASP .NET Recursos para aumentar a performance de uma aplicação ASP.NET
J2ME Trabalhando com persistência em celulares - Parte 1
Edição 03 . Ano 01 . R$ 11,90 . Publicação Bimestral
Smartphone no mundo corporativo
Desenvolva um sistema para Smartphone que conversa com as demais aplicações da empresa
Incrementando um jogo J2ME Como criar um Record online acessível a todos os jogadores através do celular
Tutorial PocketStudio Conheça mais detalhes sobre desenvolvimento de aplicações para PALM - Parte 2
Mini-Curso Pocket PC Como integrar dados e aplicações com o PocketPC através de Web Services
Web Services
Smart Clients
Bluetooth
Veja passo a passo como desenvolver serviços Web em Java com o Apache Axis
Conheça esta novidade do mundo .NET e saiba como utilizá-la
Entenda como funciona esta tecnologia de comunicação entre aparelhos
wm03.indb 1
30/6/2005 17:28:02
J
wm03.indb 2
30/6/2005 17:30:33
WebMobile
Editorial
ANO I . 03ª Edição Publicação Bimestral Junho/Julho . 2005
P
Editor Geral Rodrigo Spinola editorwebmobile@devmedia.com.br
Revisor Gustavo Spinola gustavo.spinola@gmail.com
Editores Técnicos Arilo Cláudio acdn@cos.ufrj.br
Guinther Pauli guinther@devmedia.com.br
Rafael Barcelos Barcelos@cos.ufrj.br
Redatores desta edição Andrey Sanches; André Pedralho; Antônio Gomes; Tomaz Nolêto; Robison Cris Brito; Ademir Roberto Freddo; Guinter Pauli; Marcelo Morgade; Marcio Alexandroni /ClubePDA; Antonio Eloi de Sousa Júnior; Fabio Câmara; Renato Haddad; Carlos Eduardo Lima Borges; Tradução Técnica Raphael Har-Zahav Jornalista Responsável Kaline Dolabella kalined@terra.com.br
Capa e Ilustrações Felipe Natividade Machado Felipe@phdesign.com.br
Diagramação e Direção de Arte Jaime Peters Junior
ense um pouco em como era sua vida há alguns anos atrás e vá guiando seu pensamento até chegar os dias de hoje. Dois pontos que notadamente se destacam nessa caminhada são: informação e comunicação. O enorme avanço da comunicação permitiu acesso rápido e facilitado a muita informação. Independente de onde e em que momento, podemos ver o horário do cinema, consultar nosso saldo bancário, verificar a cotação de bolsas de valores, enviar e receber e-mails. Neste contexto, temos que a comunicação móvel é fundamental para a vida moderna. E é este o foco desta edição da Web Mobile. Dois artigos dividem a capa: “Usando conexões HTTP em um jogo J2ME/MIDP” e “Integrando aplicações para SmartPhone a outras tecnologias .NET”. Para o primeiro, temos a continuidade da excelente série de artigos sobre o desenvolvimento de jogos para celular; desta vez o foco é a comunicação entre celular e servidor via http. Já no segundo artigo, de autoria do Andrey Sanches, é apresentado o desenvolvimento de uma aplicação .NET com foco na questão da mobilidade através do uso de web services. Gostaria de destacar também uma matéria especial sobre Bluetooth onde você conhecerá esta tecnologia que promete grandes benefícios para a comunicação sem fio. Esse artigo faz parte de uma série de artigos que pretende, em um primeiro momento, abordar os conceitos básicos do bluetooth, comparando-a com as soluções existentes, apresentando os principais ambientes em que ela está sendo aplicada e os motivos que a tornou tão popular. Nesta edição também temos a matéria do Eloi de Souza Junior, ganhador do concurso Entre no Mundo Mobile na categoria artigos. É um excelente trabalho sobre persistência em dispositivos móveis com J2ME. Outro excelente trabalho é apresentado por Marcelo Morgade, onde são apresentados os conceitos básicos da web. Leia atentamente essa matéria e saiba por que não ser mais um colador de código. Gostaria de dar as boas vindas ao Fábio Câmara. Em seu primeiro artigo para a Web Mobile, “Desenvolvendo uma aplicação Smart Client”, você ficará sabendo o porquê, na visão do autor, estes últimos anos podem ser considerados uma fase histórica de desperdícios de horas no desenvolvimento de software para web e como smart client lida com isso. Dando continuidade à série de artigos sobre Pocket PC, Renato Haddad apresenta o uso de web services em Pocket PC. Temos também uma excelente matéria sobre ganho de performance em aplicações web com .NET, vale a pena conferir. Além disto, temos também um artigo sobre Palm onde você conhecerá mais detalhadamente o ambiente de desenvolvimento PocketStudio. Por fim, destaco a matéria sobre Java e web services onde é apresentada uma visão prática de como desenvolver web services e aplicações clientes responsáveis pela utilização das funcionalidades desses serviços. Gostaria de convidá-lo a fazer uma visita no site da WebMobile através do site www.devmedia.com.br. Ele está repleto de artigos e vídeo-aulas que complementam o conteúdo da revista – tudo gratuito! Também disponibilizamos o fórum com uma sala dedicada à mobilidade contendo os assuntos J2ME, .NET Compact Framework e Palm. Desejo uma ótima leitura.
Jaime@phdesign.com.br
Um abraço e até a próxima!!! Rodrigo Oliveira Spínola
Mônica Queiroz monica@phdesign.com.br
editorwebmobile@devmedia.com.br
Tarcisio Bannwart Tarcisio@phdesign.com.br
Índice
WEB www.portalwebmobile.com.br
04. Integrando Aplicações para Smartphone a outras tecnologias .NET
Distribuiçãoao Leitor Atendimento
por Andrey Sanches
A DevMedia conta com um departamento exclusivo para o atendimento ao leitor. Se você tiver algum problema no recebimento do seu exemplar ou precisar de algum esclarecimento sobre assinaturas, exemplares anteriores, endereço de bancas de jornal, entre outros, entre
por Alexandroni/ClubePDA
16. Bluetooth: da teoria à prática: Mundo sem cabos - Parte I. por André Pedralho, Antônio Gomes e Tomaz
em contato com:
NolêtoSiqueira Silva
Aline Saldanha – Atendimento ao Leitor webmobile@devmedia.com.br
22. Aprenda a desenvolver Web Service com Apache Axis - Parte I
(21) 2283-9012
40. Desenvolvimento PalmOS Objetivos Visuais no PocketStudio 46. Persistência em aplicativos para dispositivos móveis com J2ME por Antonio Eloi de Sousa Júnior
56. Desenvolvendo uma aplicação Smart Client por Fabio Câmara
por por Robison Cris Brito e Ademir Roberto Freddo
Kaline Dolabella – Gerente de Marketing e Atendimento kalined@terra.com.br (21) 2283-9012
28. ASP.NET Aumentando a performance de suas aplicações Web - Parte I por Guinter Pauli
por Carlos Eduardo Lima Borges
por Marcelo Morgade
Para informações sobre veiculação de anúncio na revista ou no site
por Renato Haddad
65. Usando conexões HTTP em um jogo J2ME/MIDP
34. Infra-estrutura Web
Publicidade
61. Integrando Dados com Web Services
entre em tcontato com:
Ícones Internos
Luiz Cláudio Barreto
Fale com o Editor! É muito importante para a equipe saber o que você está achando
publicidade@devmedia.com.br Para fechar parcerias ou ações específicas de marketing com a
Atenção
Download
Editorial
Figura
Jogos
mais gostou e qual artigo você menos gostou. Fique a vontade para
DevMedia, entre em contato com:
entrar em contato com os editores e dar a sua sugestão!
Gerente de Marketing e Atendimento
Entrevista
Se você estiver interessado em publicar um artigo na revista ou no site WebMobile, entre em contato com os editores, informando o
Kaline Dolabella kalined@terra.com.br
Links
Livros
News
Nota
Tabela
Listagem
Minibriografia
título e mini-resumo do tema que você gostaria de publicar: Rodrigo Spinola - Editor da Revista
Assistente de Marketing Jeff Wendell
Quadro
jeff@devmedia.com.br Realização
da revista: que tipo de artigo você gostaria de ler, que artigo você
Projeto Gráfico
editorwebmobile@devmedia.com.br Alfredo Ferreira - Editor do Site
Tutoriais
alfredo@clubedelphi.net
WebMobile 3 wm03.indb 3
30/6/2005 17:30:42
Integrando aplicações para SmartPhone a outras tecnologias .NET P
or muito tempo sempre estivemos muito preocupados em desenvolver a solução perfeita para o nosso cliente. Essa sempre foi e sempre será a preocupação de qualquer empresa que desenvolva aplicações e integrem soluções utilizando-se de benefícios de uma tecnologia. Neste artigo vamos demonstrar como é fácil desenvolver, em um cenário bem interessante, uma aplicação completa e que também tenhamos como item importante dessa integração, a mobilidade.
O cenário Para demonstrar a integração que aplicações para SmartPhones podem ter em um ambiente corporativo, vamos simular o departamento de compras de uma empresa. Imagine que por um lado, os compradores efetuam o pedido de produtos para os fornecedores, mas antes que o pedido realmente seja enviado aos devidos fornecedores, existe a intervenção do gerente de compras, munido de um SmartPhone, analisando cada compra registrada no sistema para aprová-las ou reprová-las. Para o registro da compra será usado um projeto do tipo Windows Application e para a análise e aprovação/reprovação será usado um projeto do tipo SmartPhone. Para armazenamento dos dados utilizaremos um banco de dados Access (ler 1) que guardará os dados de Fornecedores, Produtos e Compras que serão registradas utilizando a biblioteca OledbConnection.
1. Download do banco de dados Você pode fazer o download do banco de dados Access utilizado neste exemplo a partir do endereço para download deste artigo.
por Andrey Sanches
Para a integração das funções do SmartPhone utilizaremos um web service. Além disso, utilizaremos um Class Library escrito em VB.NET, que será acessado a partir dos projetos C#, demonstrando a completa integração oferecida pelo .NET. O Class Library centralizará as regras de negócio e acesso ao banco de dados.
Iniciando o desenvolvimento Vamos iniciar criando a solution e o primeiro projeto (ClassLibrary), pois as classes serão usadas tanto pelo projeto
4 3º Edição
wm03.indb 4
30/6/2005 17:30:50
Smartphone
Windows Application quanto pelo web service que executará as funções solicitadas pelo SmartPhone. Abra o Visual Studio .NET 2003, selecione File>New>Project e informe os dados como mostra a 1. Em Project Types escolha Visual Basic Projects e em Templates selecione Class Library. Dê o nome (Name) de “CLMobile” para o projeto, defina o Location para “C:\Projetos” e em New Solution Name digite “WebMobile” (para informar o nome da Solution a ser criada, clique no botão “More”). Clique em OK. Nesse momento o VS .NET criará a solution e o projeto ClassLibrary (CLMobile) com uma classe padrão chamada Class1. Altere o nome da classe para Produto e a codifique como mostra a 1. Para criar uma classe que identifique e retorne dados de Fornecedores, clique com o botão direito do mouse no projeto CLMobile e selecione Add>Add Class. No campo Name informe o nome da nova classe (Fornecedor) e a implemente conforme mostra a 2.
1. Tela de criação da solution e do projeto ClassLibrary.
2. Implementação da classe Fornecedor Imports System.Data.OleDb Imports System.Configuration Public Class Fornecedor
Private vID As Integer Private vNome As String
1. Implementação da classe Produto
Public Sub New(ByVal id As Integer, ByVal
Imports System.Data.OleDb
nome As String)
Imports System.Configuration
vID = id
Public Class Produto Private vID As Integer Private vDescricao As String Public Sub New(ByVal id As Integer, ByVal descricao As String)
vNome = nome End Sub
Public ReadOnly Property ID() As Integer Get Return vID
vID = id vDescricao = descricao
End Get
End Sub
End Property
Public ReadOnly Property ID() As Integer
Public ReadOnly Property Nome() As String
Get
Get Return vID
End Get End Property
Return vNome End Get End Property
Public ReadOnly Property Descricao() As String Public Shared Function getFornecedores() As DataTable
Get Return vDescricao End Get End Property
Dim myConn As New OleDbConnection( ConfigurationSettings.AppSettings(“strConexao”)) myConn.Open()
Public Shared Function getProdutos() As DataTable Dim myConn As New OleDbConnection( ConfigurationSettings.AppSettings(“strConexao”)) myConn.Open()
Dim mySql As String = “SELECT forn_id, forn_nome FROM tb_fornecedores” Dim myCommand As New OleDbCommand(mySql, myConn)
Dim mySql As String = “SELECT prod_id, prod_descricao FROM tb_produtos”
Dim myDataTable As New DataTable
Dim myCommand As New OleDbCommand(mySql, myConn)
Dim myDataAdapter As New OleDbDataAdapter( myCommand)
Dim myTable As New DataTable Dim myDataAdapter As New OleDbDataAdapter( myCommand) myDataAdapter.Fill(myTable)
myDataAdapter.Fill(myDataTable) myConn.Close() myConn.Dispose()
myConn.Close() myConn.Dispose()
Return myDataTable
Return myTable End Function End Function End Class
End Class
WebMobile 5
wm03.indb 5
30/6/2005 17:30:51
Seguindo o mesmo procedimento, adicione uma nova classe chamada Compra. Essa classe fará o registro das compras e retornará as compras efetuadas para que o gerente aprove ou reprove. Sua implementação é mostrada na 3.
Text: Produto: Text: Qtd.: DropDownStyle: DropDownList Name: cboFornecedor
ComboBox
O que o usuário vê A interface com o usuário pode ser feita de diversas formas: uma página web, uma aplicação Windows, telas em Palms ou PocketPCs ou até mesmo em visores de celulares. Agora que temos as classes concluídas, precisamos criar a interface que fará o registro das compras pelos compradores. Clique com o botão direito do mouse na solution (WebMobile), selecione a opção Add>New Project e informe os dados como mostra a 2. Em Project Types escolha Visual C# Projects e em Templates selecione Windows Application. Defina o nome (Name) do projeto como “cadCompra” e Location como “C:\Projetos”. Clique em OK . O VS .NET criará o projeto e já por padrão exibirá o formulário Form1.cs. Após a criação do formulário, altere sua propriedade Name para frmCompras e ajuste o layout conforme a 3 (veja os componentes utilizados na 1).
Text: Fornecedor:
Label
DropDownStyle: DropDownList Name: cboProduto Name: txtQtd
TextBox
Text: branco Name: btFechar Text: Fechar
Button
Name: btGravar Text: Registrar Compra
1. Configuração de componentes do formulário.
Após a criação do formulário frmCompras, clique duas vezes no botão “Fechar” e digite o código a seguir: private void btFechar_Click(object sender, System.EventArgs e) { Application.Exit(); }
Para que possamos utilizar as classes da nossa Class Library, é necessário adicionar ao projeto uma referência ao assembly criado. Clique com o botão direito do mouse no projeto “cadCompra”, selecione a opção Add Reference e selecione a DLL como mostra a 4. Adicionada a referência, adicione CLMobile na lista using do formulário, como mostrado a seguir: using CLMobile;
2. Tela de criação do Projeto Windows Application – Aplicação para os compradores.
É necessário também criar um arquivo de configuração (App.config) para armazenar nossa string de conexão com o banco de dados. Para isso, clique com o botão direito no projeto cadCompra e selecione Add>Add New Item. Adicione um Application configuration file conforme mostra a 5. Dê um duplo clique no App.config e digite o seguinte código: <?xml version=”1.0” encoding=”utf-8” ?> <configuration> <appSettings> <add key=”strConexao” value=”Provider= Microsoft.Jet.OLEDB.4.0;Data Source=C:\ Projetos\webMobile\Banco\dbSmartPhone.mdb;”/> </appSettings> </configuration>
3. Tela de Registro de Compras.
Já com a referência adicionada ao projeto e ao formulário, clique duas vezes no botão “Registrar Compra” e digite o código da 4. Na abertura do formulário, precisaremos preencher os
6 3º Edição
wm03.indb 6
30/6/2005 17:30:53
Smartphone
3. Implementação da classe Compra Imports System.Data.OleDb Imports System.Configuration Public Class Compra Private Private Private Private Private Private
vID As Integer vData As DateTime vFornecedor As Fornecedor vProduto As Produto vQtd As Integer vAprovada As Boolean
Public Sub New() vData = New DateTime(Now.Year, Now.Month, Now.Day) End Sub Public Sub New(ByVal id As Integer) vID = id End Sub Public ReadOnly Property ID() As Integer Get Return vID End Get End Property Public Property Qtd() As Integer Get Return vQtd End Get Set(ByVal Value As Integer) vQtd = Value End Set End Property Public ReadOnly Property Data() As DateTime Get Return vData End Get End Property Public Property Produto() As Produto Get Return vProduto End Get Set(ByVal Value As Produto) vProduto = Value End Set End Property Public Property Fornecedor() As Fornecedor Get Return vFornecedor End Get Set(ByVal Value As Fornecedor) vFornecedor = Value End Set End Property ‘Função que retorna as compras não aprovadas Public Shared Function getComprasPendentes() As DataSet Dim myConn As New OleDbConnection( ConfigurationSettings.AppSettings(“strConexao”)) myConn.Open() Dim mySQL As String = “SELECT tb_Compras.*, tb_Fornecedores.*, tb_Produtos.* FROM (tb_compras “ & _ “INNER JOIN tb_produtos ON tb_produtos. prod_id = tb_compras.prod_id) “ & _ “INNER JOIN tb_Fornecedores ON tb_Fornecedores.forn_id = tb_compras.forn_id “ & _ “WHERE tb_compras.comp_aprovada = 0”
Dim myCommand As New OleDbCommand(mySQL, myConn) Dim myDataSet As New DataSet Dim myAdapter As New OleDbDataAdapter(myCommand)
myAdapter.Fill(myDataSet, “Compras”) myConn.Close() myConn.Dispose() Return myDataSet End Function ‘ Função que retorna um dataSet com os dados da compra enviada por parâmetro Public Shared Function getCompra( ByVal idCompra As Integer) As DataSet Dim myConn As New OleDbConnection( ConfigurationSettings.AppSettings(“strConexao”)) myConn.Open() Dim mySQL As String = “SELECT tb_Compras.*, tb_Fornecedores.*, tb_Produtos.* FROM (tb_compras “ & _ “INNER JOIN tb_produtos ON tb_produtos. prod_id = tb_compras.prod_id ) “ & _ “INNER JOIN tb_Fornecedores ON tb_Fornecedores.forn_id = tb_compras.forn_id “ & _ “WHERE tb_compras.comp_id = “ & idCompra
Dim myCommand As New OleDbCommand(mySQL, myConn) Dim myDataSet As New DataSet Dim myAdapter As New OleDbDataAdapter(myCommand) myAdapter.Fill(myDataSet, “Compras”) myConn.Close() myConn.Dispose()
Return myDataSet End Function Public Sub Salvar() Dim myConn As New OleDbConnection( ConfigurationSettings.AppSettings(“strConexao”)) myConn.Open() Dim mySql As String If vID = 0 Then mySql = “INSERT INTO tb_compras (forn_id, prod_id, comp_data, comp_qtd) values “ _ & “ (“ & vFornecedor.ID & “,” & vProduto. ID & “,’” & vData.Year & “/” _ & vData.Month & “/” & vData.Day & “’,” & vQtd & “)” Else mySql = “UPDATE tb_compras set comp_aprovada = “ & vAprovada & “ where comp_id = “ & vID End If
Dim myCommand As New OleDbCommand(mySql, myConn) myCommand.ExecuteNonQuery() myConn.Close() myConn.Dispose()
End Sub Public Sub Reprovar() vAprovada = False Salvar() End Sub Public Sub Aprovar() vAprovada = True Salvar() End Sub End Class
WebMobile 7
wm03.indb 7
30/6/2005 17:30:54
ComboBoxes com dados sobre os Fornecedores e Produtos. Para tal, digite o código da 5 no evento Load do formulário (método frmCompras_Load).
Desenvolvendo o web service
4. Adicionando a referência da Class Library ao projeto.
5. Criando o arquivo de configuração (App.config).
O web service terá a função de prover informações de compras efetuadas pelos compradores ao SmartPhone do gerente de compras, executar aprovação/reprovação das mesmas, prover informações para outros projetos que poderiam ser criados futuramente ou até mesmo (e por que não?) disponibilizá-los na internet. Para criar o web service, clique com o botão direito do mouse na solution (WebMobile) e selecione a opção Add>New Project, informando os dados como mostra a 6. Em Project Types escolha Visual C# Projects e em Templates selecione ASP.NET Web Service. Defina a opção Name como “WSWebMobile” e Location como “http://localhost/WSWebMobile”. Clique em OK. O VS .NET criará o projeto e já por padrão exibirá um web service chamado Service1.asmx. Clique com o botão direito no arquivo service1.asmx e selecione a opção View Code. O VS.NET exibirá o famoso WebMethod “Hello World”, exclua-o e digite o código da 6. Para ter acesso às classes é necessário fazer referência à DLL no projeto. Clique com o botão direito do mouse no projeto WSWebMobile, selecione a opção Add Reference e selecione a DLL como mostra a 4, depois adicione a referência no using como mostrado anteriormente. Veja que no web service temos alguns métodos que serão invocados pela aplicação SmartPhone. O WebMethod getComprasPendentes executa a devida função na classe Compra para que retorne um objeto DataSet. A função getCompra também retorna um DataSet, com os dados da compra enviada por parâmetro. Já os métodos reprovarCompra e aprovarCompra não retornam nenhum valor, somente instanciam a classe Compra
4. Implementação do botão “Registrar Compra” private void btGravar_Click(object sender, System. EventArgs e)
5. Evento Load do formulário
{ Compra objCompra = new Compra(); objCompra.Produto = new Produto((int)cboProduto. SelectedValue,cboProduto.Text); objCompra.Qtd = int.Parse(txtQtd.Text);
private void frmCompras_Load(object sender, System. EventArgs e) {
objCompra.Fornecedor = new Fornecedor((int)cboFornecedor.
//preenche fornecedores
SelectedValue,cboProduto.Text);
cboFornecedor.DataSource = Fornecedor.
try {
getFornecedores(); objCompra.Salvar();
cboFornecedor.DisplayMember = “forn_nome”;
MessageBox.Show(“Compra gerada com sucesso !!!”);
cboFornecedor.ValueMember = “forn_id”;
} catch (Exception ex)
//preenche produtos
{
cboProduto.DataSource = Produto.getProdutos(); MessageBox.Show(“Ocorreu o seguinte erro: “ +
cboProduto.DisplayMember = “prod_descricao”;
ex.Message); cboProduto.ValueMember = “prod_id”;
} }
}
8 3º Edição
wm03.indb 8
30/6/2005 17:30:55
Smartphone
e executam os métodos de acordo com cada ação solicitada, no caso, aprovações/reprovações. Note também que a regra de negócio para executar cada função não se encontra no web service e sim no componente de classes que criamos anteriormente, em VB.NET. Continuando com a codificação, no arquivo de configuração gerado na criação do web service (Web.config) precisamos adicionar a sessão appSettings (que guarda as configurações relativas à aplicação) entre as sessões <configuration> e <system.web> conforme código a seguir: <configuration>
<appSettings> <add key=”strConexao” value=”Provider=Microsoft.Jet.
6. Criando o Web Service que vai prover informações ao Smartphone do gerente de compras.
OLEDB.4.0;Data Source=C:\Projetos\webMobile\ Banco\dbSmartPhone.mdb;”/> </appSettings>
<system.web>
6. Web Methods que serão invocados pela aplicação SmartPhone [WebMethod]
Para testar o web service é necessário compilar o projeto (Menu Build>Build WSWebMobile), depois abrir o Internet Explorer e digitar a seguinte url: http://localhost/WSWebMobile/Service1.asmx. Aqui vale uma ressalva: para testar suas aplicações smartphone localmente, é necessário criar um novo device (loopBack) para que o emulador smartphone funcione corretamente. Para isso, vá ao Painel de Controle, selecione Adicionar Hardware, selecione “Sim, eu tenho o Hardware conectado” e clique Próximo. Na listagem selecione a opção “Adicione um novo dispositivo de Hardware” e clique Próximo, selecione a segunda opção “Instale o HardWare que eu ...”, Próximo, na próxima listagem selecione “Adaptador de Rede” e clique Próximo. Em seguida selecione “Microsoft Loopback Adapter” e instale o dispositivo. Após a instalação, edite as configurações de IP configurando-o para 192.168.0.1 e máscara de subnet para 255.255.255.0. Será exibida uma página com os WebMethods do web service. Clicando em cada WebMethod é possível ver o resultado em formato XML. Pronto, nosso ambiente já está quase todo montado. Já temos o web service que utilizará a Class Library para prover informações ao SmartPhone e executar ações. Temos também a aplicação dos compradores que efetuarão os pedidos de compra. Vamos agora criar a aplicação do gerente de compras.
public DataSet getComprasPendentes() //método responsável por buscar as compras pendentes para aprovação { return Compra.getComprasPendentes(); }
[WebMethod] public DataSet getCompra(int idCompra) //método responsável por buscar informações da compra { return Compra.getCompra(idCompra); }
[WebMethod] public void reprovarCompra(int idCompra) //método responsável por reprovar as compras { Compra objCompra = new Compra(idCompra); objCompra.Reprovar(); }
[WebMethod] public void aprovarCompra(int idCompra)
A informação em qualquer lugar Seguindo a mesma linha de raciocínio, temos que pensar que o nosso gerente de compras vai querer acessar as informações de compras de qualquer lugar em que esteja. Temos que oferecer a ele uma tecnologia que permita acesso à informação de qualquer lugar. O ideal seria ter uma aplicação rodando no próprio celular
//método responsável por aprovar as compras { Compra objCompra = new Compra(idCompra); objCompra.Aprovar(); }
WebMobile 9
wm03.indb 9
30/6/2005 17:30:56
do gerente, onde ele possa usar a conexão provida pela companhia telefônica e que seja cobrado somente o tempo em que esteve conectado solicitando informações das compras. Para isso, vamos utilizar os SmartPhones que são um novo modelo de celular, com sistema operacional próprio e o .NET Compact Framework já instalado. A Microsoft também criou ferramentas para o desenvolvimento de aplicações nesse formato. O Visual Studio .NET 2003 já tem suporte à criação de aplicações para PocketPC e SmartPhones. Para desenvolver aplicações para SmartPhones é necessário instalar o Kit SDK SmartPhone 2003, que pode ser encontrado para download gratuito em www.microsoft.com/ downloads/details.aspx?FamilyID=a6c4f799-ec5c-427c-807c-4c0f9 6765a81&displaylang=en. Vamos então começar o desenvolvimento da aplicação que vai rodar no SmartPhone do gerente. Para criar o projeto para SmartPhones, clique com o botão direito do mouse na solution (WebMobile), selecione a opção Add>New Project e informe os dados como mostra a 7. Em Project Types escolha Visual C# Projects e em Templates escolha Smart Device Application. Defina a opção Name como “smartPhone” e Location como “C:\Projetos\webMobile”. Clique em OK. O VS .NET apresentará a tela em que é possível selecionar qual projeto de Smart Device Application você deseja: Pocket PC, Windows CE ou SmartPhone (ler 2). Selecione SmartPhone na opção platform e Windows Application em project type ( 8).
2. Pré-requisito
7. Criando o projeto que vai rodar no celular do gerente de Compras.
8. Selecionando a plataforma e o tipo de projeto.
Para que a opção SmartPhone apareça na lista, é necessário ter instalado o kit SDK SmartPhone citado no início desse tópico.
No lado direito dessa tela serão exibidos os emuladores de SmartPhones instalados na máquina. Quando você instala o SDK SmartPhone são adicionados por padrão os três emuladores mostrados. Clique em OK. O VS .NET criará o projeto e por padrão exibirá um formulário chamado form1.cs. Note que o formulário exibido é muito semelhante a um formulário de um projeto do tipo Windows Application, mas com o tamanho reduzido ( 9) (ler 3). Nos projetos para celulares temos a área de visualização reduzida em comparação a um projeto de PocketPC (por exemplo). 9. Tela inicial de criação do projeto SmartPhone, o controle mainMenu1 controla o fluxo de decisões.
10 3º Edição
wm03.indb 10
30/6/2005 17:30:57
Smartphone
3. Tamanho do formulário É aconselhável que não se altere a largura do formulário, pois a largura padrão que é exibida na criação do formulário é a ideal para o desenvolvimento de uma aplicação SmartPhone.
O formulário que vamos criar será a tela onde nosso gerente de compras irá visualizar as compras que ainda estão pendentes para aprovação. Para começar a desenvolver nosso formulário, exiba a caixa de ferramentas selecionando a opção ToolBox do menu View (ou pressione Ctrl + Alt + X). A barra de ferramentas será exibida com a sessão Device Controls já selecionada, exibindo os controles específicos para SmartPhone. Note que a barra de ferramentas é ainda mais limitada que a de um projeto PocketPC, mas dá suporte para os principais controles para o desenvolvimento. Podemos perceber também que o VS.NET adicionou um controle mainMenu na criação do formulário, com o nome mainMenu1. Esse controle é responsável por manipular decisões que serão tomadas pelo usuário. Em aplicações para celulares só é possível criar dois níveis horizontais de menu (um ao lado do outro), pois serão usados pela tecla esquerda e direita do celular. Antes de começar a montar o primeiro menu, altere o nome do formulário para frmAprovacao (não se esqueça de alterar também na declaração no ViewCode do arquivo Form1.cs). Quem já criou menus em aplicações Windows Application não sentirá grandes problemas, pois é o mesmo procedimento. Clique uma vez no objeto mainMenu1 e no controle no formulário digite “Encerrar” no item da esquerda, onde (por padrão) vem grafado Type Here. No item da direita digite “Detalhar”. O formulário deve ficar como mostra a 10. Continuando com a criação, vamos incluir no formulário frmAprovacao o objeto que exibirá as compras para que o gerente possa aprovar ou reprovar. Para isso, arraste da ToolBox para o formulário o controle ListView. Esse controle tem uma collection de colunas e linhas para que possamos adicionar itens à lista. Clique no objeto ListView e tecle F4 para abrir a caixa de propriedades. Selecione a propriedade Columns e clique em (...) para adicionar as colunas. A 11 mostra a tela onde é possível adicionar as colunas para o listView. Após adicionar as duas colunas clique em OK Configure as propriedades dos controles conforme 2. Seu formulário deve ficar como mostra a 12. Para que as compras re10. Menu criado em modo design. gistradas pelos comprado-
11. Tela para criação/Edição de colunas do listView.
Text: Compras Form
Name: frmAprovacao BackColor: Silver Menu: mainMenu1 Name: lstCompras FullRowSelect: True HeaderStyle: NonClickable
ListView
View: Details Coluna1: Width: 90 Text: IDCompra Coluna2: Width: 98 Text: Produto
Label
Text: Lista de Compras Pendentes
2. Configurando as propriedades dos controles.
12. Design da tela onde o gerente de compras visualizará as compras.
WebMobile 11
wm03.indb 11
30/6/2005 17:30:58
res sejam exibidas no listView, é necessário adicionar item por item no controle. Para isso, precisamos fazer referência ao web service WSWebMobile que criamos anteriormente. Clique com o botão direito do mouse em nosso projeto smartPhone e selecione a opção Add Web Reference, que exibirá a tela apresentada na 13. Digite no campo URL o texto http://localhost/ WSWebMobile/service1.asmx. Serão exibidos os WebMethods que estão contidos no web service. Clique em Add Reference e o VS.NET irá criar a referência para o web service. Agora que já temos a referência para o web service, vamos codificar o evento Load do formulário (método frmAprovacao_Load), que adicionará os itens no listView. Digite o código da 7. Criamos uma instância do web service onde usaremos o método getComprasPendentes() para retornar um objeto do tipo DataSet.
Essa função retornará um DataTable com os dados das compras que estão pendentes para aprovação. Observe que criamos um objeto do tipo ListViewItem, chamado “t”, usando o método construtor e já instanciando o valor que será retornado quando um item for selecionado. A seguir usamos o método Add do objeto SubItems para adicionar o texto que será exibido para o usuário. Por fim, como normalmente é feito em combos e listas, usamos o método Add do objeto lstCompras para adicionar o item ao controle ListView. Essa parte de código está entre um laço foreach para que seja executado para cada item encontrado em nossa collection de DataRows. Não podemos esquecer de codificar também os menus que darão ao usuário o poder de selecionar uma compra e ver os detalhes da mesma. Clique duas vezes no menu Detalhar e digite o código da 8, que também já mostra o código que deve ser usado para o menu “Encerrar”. Para que seja possível visualizar detalhes de cada compra, precisaremos criar outro formulário. Clique com o botão direito do mouse no projeto e escolha Add>Add Windows Form, dando a ele o nome frmDetalhar.cs. Clique em Open. No novo formulário, vá até o código fonte (F7), localize o método frmDetalhar e implemente-o conforme mostra a 9. Veja que o método construtor do formulário foi alterado para que seja enviado por parâmetro o id da compra selecionada na tela anterior (frmAprovacao). Para isso, também criamos uma variável de formulário do tipo int que receberá o parâmetro passado no construtor. Vamos modelar o formulário frmDetalhar conforme mostra a 14. Configure as propriedades dos controles como mostra a 3.
13. Criando a web reference para o projeto SmartPhone.
8. Manipuladores para os eventos de menus private void menuItem2_Click(object sender, System.
7. Código que será carregado na abertura do formulário private void frmAprovacao_Load(object sender, System.
EventArgs e) { if (lstCompras.SelectedIndices.Count == 0)
EventArgs e)
MessageBox.Show(“Informe a compra para visualizar
{ localhost.Service1
o detalhe !!!”,”Visualizar Detalhe”,
ws = new localhost.Service1();
MessageBoxButtons.OK,MessageBoxIcon.Hand,
DataSet myDataSet;
MessageBoxDefaultButton.Button1); else
myDataSet = ws.getComprasPendentes();
{
foreach (DataRow myDataRow in myDataSet.Tables[0].
int idCompra = int.Parse(lstCompras.
Rows)
FocusedItem.Text);
{
frmDetalhar detalhe = new frmDetalhar(idCompra);
ListViewItem t = new ListViewItem(myDataRow[
detalhe.Show();
“comp_id”].ToString()); }
t.SubItems.Add(myDataRow[ “prod_descricao”].ToString()); lstCompras.Items.Add(t); } }
} private void menuItem1_Click(object sender, System. EventArgs e) { Application.Exit();
12 3º Edição
wm03.indb 12
30/6/2005 17:31:00
Smartphone
this.Close();
9. Código do construtor da classe private int vIDCompra; public frmDetalhar(int idCompra) { // // Required for Windows Form Designer support // vIDCompra = idCompra; InitializeComponent();
Note que para cada operação do menu, fazemos a chamada para o WebMethod correspondente do web service. Após a chamada será exibida uma mensagem com a confirmação da operação. Pronto, já temos nossa aplicação e agora precisamos testá-la. Para iniciarmos o teste é necessário teclar F5 e aguardar o processamento. O VS.NET exibirá a tela da 15, solicitando que seja escolhido o emulador que será usado. Selecione o Default e clique na opção Deploy.
// // TODO: Add any constructor code after
10. Código que será carregado na abertura do formulário
// InitializeComponent call //
private void frmDetalhar_Load(object sender, System. } EventArgs e) { localhost.Service1
Não se esqueça de arrastar da ToolBox o objeto mainMenu e configurá-lo utilizando o mesmo procedimento do formulário anterior, deixando-o com a aparência apresentada na 14. Continuando a codificação, clique duas vezes no formulário e substitua o código do método frmDetalhar_Load pelo mostrado na 10. O código anterior faz acesso ao web service, recebendo um DataSet como valor de retorno da função getCompra. A seguir, extraímos do DataSet alguns valores para serem exibidos nos Labels do formulário. Lembre-se de adicionar o namespace System. Data na lista using do formulário para ter acesso à classe DataSet. Agora clique duas vezes sobre o menu Aprovar e digite o código da 11. Faça o mesmo para o menu Reprovar, digitando o seguinte código da 12. Por fim, para o menu “Voltar” 14. Formulário de detalhe da Compra digite o seguinte: com opções de Aprovar / Reprovar.
Labels
Labels
Name: lblDetalhe
Text: branco
Name: lblProduto
Text: branco
Name: lblQtd Name: frmDetalhar Form
BackColor: Silver Menu: mainMenu1
myDataSet = ws.getCompra(vIDCompra); lblDetalhe.Text = myDataSet.Tables[0].Rows[0][“comp_ id”].ToString(); lblProduto.Text = myDataSet.Tables[0].Rows[0][“prod_ descricao”].ToString(); lblFornecedor.Text = myDataSet.Tables[0]. Rows[0][“forn_nome”].ToString(); lblQtd.Text = myDataSet.Tables[0].Rows[0][“comp_qtd”]. ToString(); }
15. Selecionando o emulador.
Produto: / Forn: / Qtd:
Name: lblFornecedor
ws = new localhost.Service1();
DataSet myDataSet;
11. Código para o menu Aprovar localhost.Service1
Text: branco Text: branco
ws = new localhost.Service1();
ws.aprovarCompra(vIDCompra); MessageBox.Show(“Compra nº. “ + vIDCompra + “ aprovada com sucesso !!!”,”Aprovação da Compra”,MessageBoxButto ns.OK,MessageBoxIcon.Asterisk,MessageBoxDefaultButton. Button1); frmAprovacao aprova = new frmAprovacao(); aprova.Show();
3. Componentes de FrmDetalhar.
WebMobile 13
wm03.indb 13
30/6/2005 17:31:01
12. Código para o menu Reprovar localhost.Service1
ws = new localhost.Service1();
ws.reprovarCompra (vIDCompra); MessageBox.Show(“Compra nº. “ + vIDCompra + “ reprovada
WebMethod aprovarCompra do web service, passando por parâmetro o id da compra selecionada. Após a execução, será exibida uma mensagem notificando sobre a aprovação. Selecionando-se a opção Reprovar, o WebMethod executado será o reprovarCompra e da mesma forma, será exibida a mensagem de reprovação da compra.
com sucesso !!!”,”Reprovação da Compra”,MessageBoxButto ns.OK,MessageBoxIcon.Asterisk,MessageBoxDefaultButton. Button1); frmAprovacao aprova = new frmAprovacao(); aprova.Show();
Conclusão Vimos neste artigo como é simples a implementação de aplicações integradas ao SmartPhone e como elas se comunicam, gerando assim uma aplicação de qualidade em um cenário interessante,
Já com o emulador em execução, será exibida a confirmação da instalação do arquivo System.SR.ENU.phone.cab. Clique em OK (botão esquerdo do celular simulador) para exibir a tela inicial do SmartPhone ( 16). Após compilar e executar a aplicação, a tela do simulador agora será semelhante à 17. Clique na seta para baixo do emulador e navegue normalmente entre as compras pendentes para aprovação, selecione uma das compras e clique no botão direito do emulador (Detalhar) para ver detalhes da compra selecionada (caso não selecione nenhuma compra, o sistema enviará uma mensagem ao usuário solicitando a seleção de ao menos uma compra). Na próxima tela ( 18) podemos ver os detalhes da compra selecionada. Clicando no botão direito do emulador visualizamos as opções Aprovar/Reprovar. Caso a opção selecionada seja Aprovar, será feita a chamada ao
17. Tela inicial da aplicação.
18. Exibindo os detalhes da compra com opções de Aprovar/Reprovar.
Andrey Sanches (andrey.sanches@uol.com.br) é Analista de Sistemas na Atos Origin Brasil – SP (www.atosorigin.com.br) e trabalha com desenvolvimento de sistemas na tecnologia .NET desde sua versão beta. MCP (Microsoft Certified Professional) e Co-líder no grupo de usuários codificando.net (www.codificando.net), palestrante Microsoft em diversos eventos do meio e escreve artigos para diversos sites e revistas como Portal WebMobile e MSDN Magazine. Ele acredita que a tecnologia móvel .NET pode agregar bastante valor às aplicações e no modo de vida das pessoas. The Spoke: br.thespoke.net/MyBlog/ andreysanches/MyBlog.aspx.
Faça o download no site: www.devmedia.com.br/webmobile/downloads 16. Tela inicial do emulador do SmartPhone.
14 3º Edição
wm03.indb 14
30/6/2005 17:31:02
Smartphone
JBuilder 2005 A Evolução no Desenvolvimento JAVA O JBuilder representa um conceito revolucionário em ferramentas de desenvolvimento, onde se integram tecnologias de produtividade corporativa e individual, resultando em uma produção acelerada e de máxima qualidade. Um ambiente completo, com suporte consistente para todas as plataformas Java™ - seja Standard, Enterprise ou Micro Edition.
www.borland.com.br WebMobile 15
wm03.indb 15
30/6/2005 17:31:25
oferecer aos leitores um “Welcome to the Bluetooth World”.
Como tudo começou...
Bluetooth: da teoria à prática O mundo sem cabos – Parte I por André Pedralho, Antônio Gomes e Tomaz Nolêto
Em 1994, a empresa sueca Ericsson Mobile Communications iniciou pesquisas sobre a possibilidade do desenvolvimento de uma tecnologia que permitisse a comunicação sem fio entre seus dispositivos móveis e acessórios com um baixo consumo de energia e que causasse um grande interesse financeiro em seus clientes. Esta tecnologia deveria substituir outras já difundidas e bem aceitas, como o infravermelho, conexão via interfaces USB e outros padrões que utilizam cabo. Todas elas apresentam principalmente uma limitação: mobilidade. Visto que os objetivos da Ericsson eram comuns aos objetivos de outras grandes companhias do ramo, um grande e poderoso grupo de pesquisas para desenvolver tal tecnologia foi formado. Esse grupo foi chamado de Bluetooth Special Interest Group – SIG, e dentre as mais de 2100 companhias envolvidas podese citar a própria Ericsson, seguida das renomadas IBM, Intel, Microsoft, Motorola, Nokia e Toshiba. A tecnologia foi batizada com o nome de Bluetooth, em homenagem a Harald Bluetooth, um rei dinamarquês a quem foi dado esse sobrenome devido a uma coloração azul em seus dentes. Assim como Harald Bluetooth unificou os países nórdicos, a tecnologia vem unificando os mundos dos computadores e das telecomunicações. Surgindo, assim, um número incontável de aplicações para ela.
Como Bluetooth funciona
Q
uantas vezes você se sentiu preso ao emaranhado de fios dos periféricos conectados ao seu computador? Ou achou incômodo ter que ficar parado com o seu celular apontando fixamente para outro enquanto está jogando em multiplayer? Provavelmente, estas e outras situações semelhantes causam uma inconveniência desnecessária a muitas pessoas. Visando reduzir o desconforto e limitação física causada pelo uso de cabos, além da pouca flexibilidade do infravermelho em certas situações cotidianas, um consórcio de grandes empresas nas áreas de hardware, software e telecomunicações desenvolveram o Bluetooth, um padrão para comunicação sem-fio, de baixo custo e de curto alcance que proporciona mobilidade, agregado à segurança e relativa rapidez na transmissão de dados entre dispositivos. Esse artigo faz parte de uma série de artigos que pretende, em um primeiro momento, abordar os conceitos básicos dessa tecnologia, comparando-a com as soluções existentes, apresentando os principais ambientes em que ela está sendo aplicada e os motivos que a tornou tão popular. Após isso, em um segundo artigo, será mostrado uma aplicação prática dessa tecnologia através da descrição do passo a passo do desenvolvimento de um pequeno sistema J2ME que utiliza esse tipo de comunicação. Através dessa série de artigos pretendemos
Bluetooth é uma tecnologia sem fio para transmissão de dados e voz entre dispositivos. Foi projetado visando prover uma interface universal para equipamentos, além da remoção dos cabos e diminuição do custo de conexões, tudo isso através da utilização de sinais de radiofreqüência gratuitos para estabelecer a comunicação entre os dispositivos. A banda de radiofreqüência utilizada pelo Bluetooth opera na faixa entre 2.4 GHz e 2,48 GHz, mesma utilizada por instrumentos industriais, científicos e médicos, denominada ISM (Industrial, Scientific and Medicine), deixando-o teoricamente suscetível a interferências. Entretanto, o protocolo Bluetooth divide a banda passante em 79 canais e altera a freqüência aproximadamente 1600 vezes por segundo, tornando improvável (apesar de não impossível) que dispositivos terceiros utilizando também a banda ISM interfiram em uma conexão (ler 1).
1. Especificações técnicas do Bluetooth 1.x Alcance: 10m Freqüência: ~2,4GHz Velocidade máxima de transmissão: 1Mbps Potência de transmissão: 1mW a 100mW
16 3º Edição
wm03.indb 16
30/6/2005 17:31:40
Bluetooth
A maioria dos dispositivos atuais que suporta Bluetooth implementa a versão 1.x (entre 1.0 e 1.2) da especificação, cujo alcance máximo de uma conexão é 10m, transmitindo dados a até 1 Mbps. A versão 2.0 foi definida e apresentada em novembro de 2004 pelo SIG. Por ser recente, poucos dispositivos dão suporte a ela (somente em janeiro de 2005 foi lançado o primeiro PDA com Bluetooth 2.0). Esta especificação apresenta como novidade uma funcionalidade chamada Enhanced Data Rate (EDR), que permite uma conexão alcançar a velocidade de 2.1 Mbps durante uma transferência. Entretanto, a razão entre velocidade de transmissão e consumo de energia em uma conexão é maior que no 1.x. Isto significa que uma transmissão no 2.0 consome mais energia por unidade de tempo, considerando a mesma quantidade de tráfego, mas a velocidade da taxa reduz bastante o tempo total de transmissão, diminuindo efetivamente o consumo final. A conexão entre dispositivos Bluetooth é efetuada através de redes locais chamadas de Personal Area Networks (PANs) ou piconets. Em uma rede piconet, uma unidade Bluetooth pode assumir o papel de mestre ou escravo, na qual o mestre é o dispositivo que inicia a rede e os demais que se conectam a ela são os escravos. Infelizmente, o número máximo de conexões a um dispositivo mestre é oito, o que limitaria bastante a criação de redes de comunicação e trocas de dados, não fosse a possibilidade de conexão em scatternets. As scatternets são redes mais abrangentes, na qual um dispositivo escravo em uma piconet pode exercer o papel de mestre em outra. Ou seja, a especificação Bluetooth permite que duas ou mais piconets sejam integradas formando uma única rede, através dos próprios dispositivos Bluetooths. Parece confuso, mas a 1 mostra bem o que seria uma piconet e um scatternet. Como pode ser visto na 1, uma scatternet, na sua essência, é um caso especial de rede ad-hoc (ler 2). Assim, o dispositivo “7” pode estabelecer comunicação com o dispositivo “1” utilizando os nodos “4” e “6” como roteadores (imaginem o número de aplicações possíveis para isso). Jogos multiplayers em dispositivos móveis, por exemplo, podem utilizar Bluetooth para comunicação e sincronização de informações no contexto da aplicação, através de uma piconet (ou scatternet) entre os dispositivos.
O funcionamento básico de uma conexão Bluetooth consiste em 2). Quando um dispotrês estados: scan, page e inquiry ( ver sitivo não está conectado em uma piconet, ele está no modo scan. Neste modo, o dispositivo procura por mensagens a cada 1.28 segundos em até 32 freqüências. Quando um dispositivo deseja estabelecer conexão com outro, ele envia 16 mensagens page idênticas divididas em 16 freqüências. Caso não haja resposta do escravo, o mestre retransmite as mensagens page para outras 16 freqüências. Se o mestre não conhece o endereço do escravo, antes de enviar a mensagem page ele envia mensagens de inquiry, fazendo uma busca em seu raio de alcance até encontrar o dispositivo. Quando o mestre encontra o escravo, ele envia as mensagens page e inicia a transmissão de dados e/ou voz.
Consumo de energia Bluetooth foi projetado desde o início para ser utilizado em dispositivos que utilizam baterias e que devem consumir o mínimo possível de energia em suas transmissões, como PDAs, celulares, Smartphones, dentre outros. Sucintamente, a relação entre o mecanismo de consumo de bateria e o dispositivo Bluetooth segue a seguinte regra geral: minimizando a atividade da interface, o consumo de energia é reduzido. Para isso, Bluetooth utiliza um sistema inteligente de potência de sinal, que aumenta o sinal de acordo com a distância entre os dispositivos. Dentro dos 10 metros (Bluetooth 1.x) o consumo é relativamente menor que outras tecnologias sem fio. O Wi-Fi, por exemplo, para manter sua taxa de transmissão e alcance relativamente mais altos que o Bluetooth, exige muito mais da bateria do
2. Redes ad-hoc No contexto de redes sem fio de comunicação de dados, ad-hoc é uma arquitetura de rede onde dispositivos se conectam e se comunicam diretamente com o destino, sem a presença de pontos centralizadores, como pontos de acesso – que ocorrem em redes Wi-Fi, ou os roteadores – de redes ethernet. Na scatternet, o que ocorre é exatamente o contrário: todos os dispositivos dentro do raio de ação de outro podem se descobrir e se conectar de modo peer-to-peer ou através de um dispositivo que faça a ponte entre a origem e o destino.
1. Arquitetura básica de uma scatternet. O dispositivo 4 é escravo na Piconet 2, mas mestre na Piconet 1.
WebMobile 17
wm03.indb 17
30/6/2005 17:31:43
dispositivo que o Bluetooth.
Bluetooth x outras tecnologias Até este ponto, foi dito que o Bluetooth substitui ou melhora outras tecnologias popularizadas para trocas de dados, como o USB e o infravermelho, por exemplo. Então, vem à tona a seguinte pergunta: “As desvantagens de se utilizar os padrões existentes para comunicação entre dispositivos (USB, Wi-Fi, etc.) são tantas a ponto de exigir o desenvolvimento de uma tecnologia para substituí-los?”. A resposta é “não”. O Bluetooth foi desenvolvido com o objetivo de diminuir custos e aumentar o conforto e a comodidade de seus usuários, e não para corrigir problemas de suas predecessoras. Porém, suas características confrontam diretamente com as de outras tecnologias, fazendo a usabilidade do Bluetooth as superar em diversas situações. A seguir, iremos apresentar a seguir um paralelo entre principais padrões de comunicação e o Bluetooth, explorando aspectos como diferentes formas de utilização, vantagens e desvantagens.
USB O Universal Serial Bus (USB) consiste em um projeto de barramento padrão para conexão de periféricos ao PC, visando unificar as diferentes portas de um micro convencional. De certa forma, seu objetivo foi alcançado, pois a maioria dos micros atuais vem com mais de uma porta USB, praticamente substituindo as suas predecessoras serial e paralela. Graças à sua flexibilidade, suportando até 127 conexões em um único barramento, e rapidez, com uma velocidade de transferência de até 480 Mbps, o USB se consolidou como a interface preferida por fabricantes e usuários para troca de dados à curta distância. De fato, sua única limitação é física, restringindo a distância entre os dispositivos ao tamanho do fio. Estas limitações podem ter seu fim se periféricos que não exigem muita velocidade na transferência de dados (mouses, teclados ou impressoras, por exemplo) tiverem seus cabos substituídos
por transmissores Bluetooth. Isto aumentaria a comodidade e a mobilidade do usuário, além de melhorar a estética do ambiente, acabando com a macarronada de fios que normalmente cercam um computador.
Infravermelho A forma mais popular de conectividade sem fio ainda é o infravermelho (IrDA – Infrared Data Association), utilizado em milhões de dispositivos no mundo inteiro. Sua taxa de transmissão é relativamente rápida (4 Mbps) e consome pouca energia. Entretanto, há a necessidade do dispositivo ter obrigatoriamente seu sensor no campo de visão linear do outro e seu alcance é de apenas 1m. Além disto, o infravermelho só permite conexão entre dois dispositivos simultâneos, limitando bastante seu potencial. Em dispositivos como celulares e PDAs, por exemplo, o Bluetooth pode ser uma escolha bem mais adequada do que infravermelho quando utilizados para transmissão de dados pequenos (fotos, mensagens multimídia, etc) ou em jogos multiplayer. Graças à utilização de freqüência de rádio em vez de transmissor óptico para interface, o usuário não precisa se preocupar com o posicionamento e barreiras físicas entre os dispositivos, tornando o Bluetooth bem mais conveniente que o infravermelho.
Wi-Fi Bluetooth e Wi-Fi são duas tecnologias que causam uma certa confusão a muitos, já que ambas possuem a característica comum de serem padrões de rede sem fio que provêm conectividade por ondas de rádio, utilizando a mesma faixa de freqüência. Entretanto, foram desenvolvidas com propósitos diferentes. Surgindo em 1998 como a abreviação de Wireless Fidelity, o Wi-Fi foi o nome dado aos dispositivos certificados pela Wireless Ethernet Compatibility Alliance que seguem a especificação do padrão da indústria IEEE 802.11b. O Wi-Fi atualmente provê acesso a redes locais sem fio a até 11 Mbps. A distância alcançada por uma conexão pode chegar a até 100m de distância de um ponto de acesso (os chamados Hot Spots). É um padrão bastante útil principalmente para usuários de PDAs e laptops, pois permite que uma rede seja acessada de qualquer lugar, desde que esteja no raio de alcance de algum ponto de acesso. A grande diferença do Wi-Fi para o Bluetooth é que o primeiro cobre uma distância maior e possui throughputs (quantidade de dados transmitidos em uma unidade de tempo) mais altos, mas requer um hardware mais caro e com maior consumo de energia. Enquanto o Bluetooth substitui cabos em diversas aplicações, o Wi-Fi substitui cabos apenas para redes locais (LANs).
Por que Bluetooth está se tornando popular? 2. Processo para um dispositivo estabelecer uma conexão Bluetooth com outro.
A resposta é simples: a aplicabilidade do Bluetooth é enor-
18 3º Edição
wm03.indb 18
30/6/2005 17:31:47
Bluetooth
me. Apesar de ser uma tecnologia relativamente recente e ainda pouco disseminada (pelo menos no Brasil), é cada vez maior a quantidade de produtos com Bluetooth. Aparelhos como celulares, PDAs, câmeras digitais e periféricos de computador já possuem modelos com a tecnologia embutida de fábrica. Existem também pequenos adaptadores que podem ser conectados na porta USB de computadores desktop e laptops, que permitem a estes também utilizarem conectividade Bluetooth. Dentre a ampla gama de possibilidades que o Bluetooth oferece, pode-se citar como formas de utilização mais comuns e já desenvolvidas: Interligar um telefone celular a um headset (fone de ouvido + microfone) para efetuar chamadas e ouvir músicas em mp3 ou rádio (ver 3). Sincronização entre dispositivos (celulares, PDAs, computadores, etc) para envio e recebimento de arquivos. Conexão de um laptop com um telefone celular para conexão com a Internet via CDMA ou GPRS, permitindo ao usuário navegar na web, enviar e-mails, etc. Jogos em modo multiplayer, onde várias pessoas conectam seus celulares e interagem entre si. Estes são apenas alguns dos exemplos mais utilizados de aplicações para Bluetooth. De fato, o enorme conjunto de possibilidades do Bluetooth é restringido apenas pela imaginação do usuário e dos desenvolvedores de aplicações, apesar de alguns fatores como distância e taxa de transmissão também poderem limitar a utilidade do Bluetooth. É possível, por exemplo, criar uma aplicação para diminuir gastos telefônicos, chamada de telefone três-em-um. Dois usuários de telefones com Bluetooth conversam entre si por conexão direta, sem intermédio de operadora, caso estejam no raio de alcance um do outro. Caso um celular estiver perto de uma linha fixa também equipada com Bluetooth, ele funciona como um telefone fixo portátil. Na terceira situação, o telefone não satisfaz nenhuma destas condições e funciona apenas como celular.
Segurança A segurança que protocolos de conexão fornecem é um dos principais critérios para sua aceitação ou não na comunidade tecnológica (foram citados outros critérios como largura de banda, confiabilidade de recebimento/envio de dados e tratamento de erros, dentre outros). O Bluetooth implementa segurança de três maneiras: “Pseudo-random frequency hopping” (saltos aleatórios pelas freqüências): dificulta que “alguém escute atrás da porta”, ou 3. Fone de ouvido Bluetooth da Nokia. seja, utilize a mesma freqüência
que uma determinada conexão, e passe a “escutar” o que está passando por ela. Autenticação: permite que os usuários limitem a conectividade a dispositivos específicos (ler 3). Criptografia: usa um mecanismo de chaves secretas, tornando os dados entendíveis somente para autorizados (dispositivos que possuam uma chave correspondente para descriptografar os dados).
3. Não entenda mal O método de autenticação provido pelo Bluetooth autentica dispositivos e não proprietários ou usuários de um determinado dispositivo. Há mecanismos aplicados na chamada “camada de aplicação” que autorizam dispositivos específicos de diversas maneiras. Então, tome cuidado e evite que seu dispositivo caia em mãos erradas, evitando a autorização indevida de outros.
Desvantagens do Bluetooth Apesar de termos citado várias utilidades e benefícios do Bluetooth, ele não é uma tecnologia que veio para liquidar de vez as já existentes. O Bluetooth tem alguns pontos fracos que o impede de ser o supra-sumo das interfaces de comunicação: Embora o Bluetooth possibilite a conexão entre mais de dois aparelhos ao mesmo tempo, a quantidade máxima de dispositivos simultâneos em uma mesma rede ainda é pequena: apenas oito. Como foi dito, esta limitação pode ser contornada com a utilização de scatternets. Existem situações em que a melhor escolha para conexão de periféricos não é o Bluetooth, como dispositivos externos de armazenamento (pen-drives, por exemplo), que normalmente demandam altas taxas de transferências para troca de dados com um computador. Até mesmo o infravermelho pode ser mais adequado em casos onde a velocidade da conexão é o mais relevante. Dependendo do objetivo, o Bluetooth freqüentemente não é a melhor escolha para implantação de uma WLAN. Com Wi-Fi, por exemplo, pode-se conseguir uma banda passante bem maior e, conseqüentemente, taxas de transferências mais altas. O preço dos dispositivos de transmissão é que levaria alguém a utilizar Bluetooth nesses casos.
Conclusões O Bluetooth é definitivamente uma tecnologia que inovou as formas de conectividade. Além de ser uma opção excelente para substituição de cabos em diferentes situações, permite também a implantação de redes locais sem fio, com possibilidade de transferência simultânea de dados e voz, equilibrando baixo consumo de energia e boa velocidade de transmissão. Pelo grande número de aplicações que vêm surgindo e por todas as vantagens já citadas, pode-se dizer que Bluetooth possui boas perspectivas de disseminação em um mercado exigente e dinâmico, que busca sempre soluções inovadoras, inteligentes e baratas. Isto já pode ser per-
WebMobile 19
wm03.indb 19
30/6/2005 17:31:51
cebido em feiras de tecnologias que exibem os mais modernos produtos a serem utilizados por um grande número de grupos de pessoas, desde donas de casa e seus eletrodomésticos até gerentes de grandes lojas de departamentos ansiosos por saber o saldo atual de seus caixas. Portanto, caros leitores, basta aguardar e verão as aplicações citadas neste artigo colocadas em prática! Entretanto, se quiserem participar mais diretamente da criação e implementação destas, não deixe de ler o artigo da próxima edição da Web Mobile. Nele será explicado em detalhes como criar sua própria aplicação utilizando Bluetooth e Java – J2ME para ser mais específico. Em breve!
Tomaz Nolêto Silva Júnior (tomaz.noleto@indt.org.br) é Bacharel em Ciência da Computação pela Universidade Federal do Amazonas, trabalhando no Laboratório de Linux Embarcado do Instituto Nokia de Tecnologia em projetos open source, como o navegador Minimo (Mini Mozilla).
Antônio Gomes de Araújo Netto (antonio.gomes@indt.org.br) é Bacharel em Ciência da Computação pela Universidade Federal do Amazonas. Atualmente, trabalha como Analista de Sistemas Laboratório de Linux Embarcado do Instituto Nokia de Tecnologia. Atua no desenvolvimento de softwares e bibliotecas para sistemas embarcados, como o Mínimo Open Source Web Browser e o Necko.
André de Souza Pedralho (andre.pedralho@indt.org.br) é Bacharel em Ciência da Computação pela Universidade Federal do Amazonas. Participou de projetos de desenvolvimento de aplicações utilizando Bluetooth e Java – J2SE e J2ME. Trabalha no Laboratório de Linux Embarcado no Instituto Nokia de Tecnologia. Atua diretamente no aperfeiçoamento do navegador MANaOS, e no navegador para sistemas embarcados Minimo, parte do Projeto Mozilla.
20 3º Edição
wm03.indb 20
30/6/2005 17:31:52
Bluetooth
Thiago, programador de Web, agora também hospeda os sites dos seus clientes.
Revenda LocaWeb. A LocaWeb é a melhor empresa de hospedagem de sites do Brasil. E agora você pode oferecer toda esta infra-estrutura para seus clientes como se fosse sua. Basta contratar a Revenda LocaWeb e usufruir de tecnologia, segurança e liberdade para que possa administrar como quiser os sites, contas de e-mail, bancos de dados, espaço em disco e outras ferramentas. Você e seus clientes ganham. Hospedagem de sites é o nosso negócio e agora é o seu também. • Domínios e contas de e-mail ilimitadas • Painel Plesk 7.5 Reloaded • Infra-estrutura no Brasil • Link de 1 Gbps com a Embratel • Servidores com DNS próprio e muito mais. Acesse www.locaweb.com.br WebMobile 21
wm03.indb 21
30/6/2005 17:32:14
Aprenda a desenvolver Web Service com Apache Axis – Parte I por Robison Cris Brito e Ademir Roberto Freddo
Leitura Obrigatória Web Mobile 1 Introdução às tecnologias Web Services: SOA, SOAP, WSDL e UDDI – Parte 1. Web Mobile 2 Introdução às tecnologias Web Services: SOA, SOAP, WSDL e UDDI – Parte 2.
W
eb service é, provavelmente, a tecnologia mais interessante para a troca de informações na internet. Ele permite que diferentes empresas, mesmo utilizando tecnologias e plataformas distintas, conectem-se de maneira padrão e executem procedimentos remotos através da utilização do protocolo padrão da internet – HTTP. Tudo isso com grande velocidade e facilidade. O uso de web service é simplesmente fantástico, você pode acessar rotinas de validação de cartão de crédito, endereçamento postal (CEP), calcular valores de fretes dos sites de comércio eletrônico, news de empresas. Devido à popularização dessa abordagem, uma gama gigantesca de produtos já estão prontos em algum lugar, você só precisa ir lá e acessar, de maneira rápida e fácil, aliviando o processamento na máquina cliente, já que toda a lógica de negócio fica no servidor que hospeda os serviços. Outra diferença em relação às abordagens de desenvolvimento “tradicional”, baseadas principalmente no uso de rotinas locais, é que os web services oferecem facilidades na disponibilização e na atualização de suas funcionalidades. Em relação à fácil disponibilização, a empresa que desenvolve um módulo específico, por exemplo, uma rotina de validação de
22 3º Edição
wm03.indb 22
30/6/2005 17:32:23
Web Services cartão de crédito, não precisa ficar distribuindo para todos os clientes este módulo, ela necessita somente publicar esse serviço em um local específico e os interessados em utilizá-lo ficarão responsáveis por realizar as devidas configurações para acessar esse serviço. No contexto das facilidades para a atualização das funcionalidades do serviço, basta que o web service seja substituído e todos os clientes passam a acessar a última versão. Outra vantagem é a transparência para o firewall da empresa cliente, pois como as mensagens são strings XML (texto puro) e podem ser transportados pelo protocolo HTTP através a porta 80 - padrão dos browsers - não é preciso realizar configurações no firewall do cliente para que ele tenha acesso às funcionalidades disponibilizadas pelo web service. Será apresentada neste artigo uma visão prática de como desenvolver web services e aplicações clientes responsáveis pela utilização das funcionalidades desses serviços.
Um pouco de conceito Um web service é um serviço remoto disponibilizado em um determinado servidor. Para ter acesso às funcionalidades que ele disponibiliza, a aplicação cliente deve estabelecer com o servidor uma simples comunicação onde os dados que serão trocados estarão em formato texto, padrão XML. Devido ao uso desse tipo de comunicação, os dados podem ser facilmente lidos pelos mais diversos dispositivos, sistemas operacionais e até linguagens de programação. Essa característica é muito interessante, já que permite também uma maneira padronizada de todos terem acesso aos dados. Chamamos isso de interoperabilidade. O segundo ponto interessante nesse processo é o SOAP, nome do “envelope” que irá carregar toda a comunicação entre a aplicação web service e o cliente. É importante deixar claro que SOAP não é um protocolo. Apesar de ser um acrônimo de Simple Object Access Protocol, na verdade ele é uma especificação que padroniza o conteúdo do XML tornado-o uma maneira comum para o envio e recebimento de requisições de uma aplicação. Envelopes SOAP podem trafegar sobre outros protocolos, como o HTTP, o SMTP (e-mail) ou algum outro desejado. O terceiro conceito é o protocolo de rede. O mais utilizado na comunicação com o web service – e utilizado no exemplo deste artigo – é o HTTP, protocolo de transferência de hipertexto. Esse protocolo é usado na visualização de páginas da internet pelo navegador (browser), e no contexto de web services, é através dele que são enviados os envelopes SOAP. Um quarto conceito importante é o de WSDL (Web Service Descriptor Language), um documento XML que descreve o web service de uma maneira estruturada. Ele contém todas as informações essenciais para que possamos entender a aplicação web service que ele representa. Dentre elas: quais os métodos acessíveis no web service; onde se pode acessar o serviço; documentação de como utilizá-lo, e;
o que o serviço faz. É através da interpretação deste documento que podemos gerar o cliente para acessá-lo.
O ambiente A plataforma J2EE oferece API’s e ferramentas para trabalharmos com web services, entre elas destacam-se o Java Web Services Developer Pack (Java WSDP) e o JAX-RPC (ler 1). Embora com estas API’s seja possível o desenvolvimento de web services sem saber como funciona o SOAP, o desenvolvimento utilizando-as não é tão simples assim; a curva de aprendizado é grande e devese conhecer com detalhes várias classes das API’s citadas. Para facilitar o uso dessa tecnologia foi desenvolvido o Axis, um framework para construção de web services mantido pela Apache. Ele fornece as ferramentas necessárias para trabalhar com web service de forma fácil e simplificada. Considerado por muitos o melhor framework da categoria, o Axis é facilmente integrado a uma série de containers web, entre eles o Tomcat e JBoss. Neste artigo utilizaremos o Axis integrado com o web container Jakarta Tomcat.
1. Web services em outras linguagens de programação É possível trabalhar com web services em outras linguagens de programação: Em www.devmedia.com.br/clubedelphi/ existem alguns artigos que falam da utilização de web services com Delphi; O grupo Apache disponibiliza também uma versão do Axis para C++ (http://ws.apache.org/axis/cpp/index.html).
Para codificação, pode ser utilizada qualquer IDE de desenvolvimento Java, tais como Eclipse, NetBeans ou JBuilder. Para este artigo foi escolhida como IDE de desenvolvimento o Gel (www. gexperts.com), por sua facilidade de utilização e tamanho reduzido. Entretanto, está é uma IDE nativa do Windows, não possuindo versão para Linux.
Configurando o ambiente para desenvolvimento Inicialmente, será instalado o Apache Tomcat no servidor. Para fins didáticos, o servidor e o cliente web service serão instalados na mesma máquina. O download do Tomcat 5.0.28 pode ser feito no link http://ftp.pucpr.br/apache/jakarta/tomcat-5/v5.0.28/bin/ jakarta-tomcat-5.0.28.exe. No nosso caso, o servidor não requer configurações especiais, então basta aceitar as configurações sugeridas pelo instalador. A instalação padrão irá configurar o Tomcat na porta 8080, com o usuário admin e senha em branco. Nesse artigo, o diretório onde o Tomcat foi instalado será referenciado por home_tomcat. Em seguida, é necessário iniciar o servidor. Clique com o botão direito no ícone do Tomcat localizado ao lado do relógio, na barra de tarefas, e escolha a opção Start Service. Se por algum motivo o ícone não estiver na barra, será necessário iniciar o monitor do Tomcat, através do botão Iniciar do Windows – Programas
WebMobile 23
wm03.indb 23
30/6/2005 17:32:27
– Apache Tomcat 5.0 – Monitor Tomcat. Para certificar se o serviço foi iniciado, abra o browser e digite a seguinte URL: http://127.0.0.1:8080. Deverá ser exibida a tela da 1. Como vimos, o Axis é um conjunto de ferramentas para desenvolver web services. Suas principais características são: Implementar o protocolo SOAP; Implementar classes que agilizam a comunicação e a publicação de web services; Utilizar containers JSP para disponibilizar os web services na rede.
Com isso, basta reiniciar o serviço do tomcat para que o Axis seja iniciado. Para testar o ambiente, abra uma janela do browser e digite o seguinte endereço: http://127.0.0.1:8080/axis/. O resultado deverá ser apresentado como na 3. Esta página possui dois links importantes: Validate: serve para validar a instalação. Clicando-se nele, será apresentada uma lista de componentes necessários (“Needed Components”). Caso algum desses não seja encontrado, ele irá solicitar a instalação. Com as versões trabalhadas neste artigo, o Tomcat e o Axis estarão completos, portanto a validação OK; View: serve para visualizar os web services já instalados.
A versão do Axis utilizada neste artigo será a 1.1, e seu download pode ser feito através do link http://ftp.pucpr.br/ apache/ws/axis/1_1/axis-1_1.zip. Após o download deste arquivo, será necessário descompactá-lo e copiar a sub-pasta axis que se encontra dentro da pasta webapps, do arquivo compactado para dentro da pasta do webapps do tomcat. A estrutura de diretórios ficará como na 2.
3. Página com informações do Apache-AXIS.
1. Página de configuração do Apache Tomcat 5.0.28.
1. WebServiceCalculo.java - Servlet responsável por receber dados via http 01. public class WebServiceCalculo { //declaração da classe WebServiceCalculo 02.
//lembramos que o fatorial de um número é o //próprio número
03.
//multiplicado por todos seus números inteiros //menores
04.
//ex: Fatorial de 5 = 5 x 4 x 3 x 2 x 1 = 120
05.
public long fatorial( int n ) { //declaração do método fatorial
06. 07.
long fat = 1;
08. 09.
for ( int i = n; i >= 1; i-- ) {
10. 11.
fat = fat * i; }
12. 13.
return fat;
14. 15.
} //fim do método fatorial
16. } //fim da classe WebServiceCalculo
2. Estrutura do diretório após a instalação do framework Axis.
24 3º Edição
wm03.indb 24
30/6/2005 17:32:29
Web Services
Clicando-se nele, serão exibidos dois web services e, clicando no link (wsdl), você verá o arquivo de especificação de ambos. Se, ao clicar, não aparecer nenhuma informação, não se preocupe, alguns navegadores não exibem XML, outros exibem como HTML, sendo necessário abrir o código fonte da página para ver o seu conteúdo. Mas ele estará lá, e funcionando.
Desenvolvimento do web service Com o objetivo de mostrar as principais funcionalidades desse framework e o que pode ser feito via web services, descreveremos nesse artigo um exemplo didático onde um programa cliente solicita a um web service o cálculo fatorial de um número. Sendo assim, o processamento será feito no servidor e o resultado será devolvido ao cliente. Uma das vantagens de utilizar o Axis é que a classe Java não precisa herdar funcionalidades de outra classe. Inicia-se o editor de código Gel e através do comando File | New... - Java File é criado uma classe chamada WebServiceCalculo.java, que deve ser gravado na pasta /home_tomcat/webapps/axis/. O código fonte do web service segue na 1. Após ter implementado a funcionalidade desejada, como se
fosse uma classe Java normal, o próximo passo para a criação de um web service é alterar a extensão da classe WebServiceCalculo, de .java para .jws (Java Web Service). Estando o servidor Tomcat iniciado, o web service estará automaticamente disponibilizado para acesso dos clientes. O framework Axis se baseia no arquivo .jws para criar o arquivo de definição wsdl. Sendo assim, todos os métodos públicos existentes nesta classe serão automaticamente disponibilizados para terceiros. Gerar o arquivo wsdl de forma automática é uma característica presente em poucos frameworks, por este motivo o Axis se destaca. Para visualizar o arquivo WSDL do web service, basta abrir o navegador e digitar: http://localhost:8080/axis/WebServiceCalculo.jws. O WSDL será gerado automaticamente pelo Apache Axis, com base na classe desenvolvida. O WSDL da classe WebServiceCalculo ficará idêntico à 2. Saber interpretar este arquivo é muito importante para realizar acessos ao web service. Informações como localização, nome dos métodos, parâmetros de acesso e tipo dos parâmetros se encontram nesse XML. Na linha 09 da 2 podemos verificar o nome do método, e também, o nome do parâmetro esperado. Já na linha 30 da 2
2. WebServiceCalculo.jws?wsdl - Arquivo WSDL da classe WebServiceCalculo 01. <wsdl:definitions targetNamespace=”http://127.0.0.1:8080/axis/WebServiceCalculo.jws”> 02. 03.
<wsdl:message name=”fatorialRequest”> <wsdl:part name=”n” type=”xsd:int”/>
04.
</wsdl:message>
05.
<wsdl:message name=”fatorialResponse”>
06.
<wsdl:part name=”fatorialReturn” type=”xsd:long”/>
07.
</wsdl:message>
08.
<wsdl:portType name=”WebServiceCalculo”>
09. 10. 11. 12.
<wsdl:operation name=”fatorial” parameterOrder=”n”> <wsdl:input message=”impl:fatorialRequest” name=”fatorialRequest”/> <wsdl:output message=”impl:fatorialResponse” name=”fatorialResponse”/> </wsdl:operation>
13.
</wsdl:portType>
14.
<wsdl:binding name=”WebServiceCalculoSoapBinding” type=”impl:WebServiceCalculo”>
15.
<wsdlsoap:binding style=”rpc” transport=”http://schemas.xmlsoap.org/soap/http”/>
16.
<wsdl:operation name=”fatorial”>
17.
<wsdlsoap:operation soapAction=””/>
18.
<wsdl:input name=”fatorialRequest”>
19.
<wsdlsoap:body encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”
20.
namespace=”http://DefaultNamespace” use=”encoded”/>
21.
</wsdl:input>
22.
<wsdl:output name=”fatorialResponse”>
23.
<wsdlsoap:body encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”
24. 25. 26.
namespace=”http://127.0.0.1:8080/axis/WebServiceCalculo.jws” use=”encoded”/> </wsdl:output> </wsdl:operation>
27.
</wsdl:binding>
28.
<wsdl:service name=”WebServiceCalculoService”>
29. 30. 31. 32.
<wsdl:port binding=”impl:WebServiceCalculoSoapBinding” name=”WebServiceCalculo”> <wsdlsoap:address location=”http://127.0.0.1:8080/axis/WebServiceCalculo.jws”/> </wsdl:port> </wsdl:service>
33. </wsdl:definitions>
WebMobile 25
wm03.indb 25
30/6/2005 17:32:31
podemos observar o caminho para fazer acessos a este web service. O Axis tem uma característica interessante, ele permite acessar o web service via requisições HTTP-GET, portanto, podemos testálo digitando no browser a seguinte URL:
É importante observar que, na quinta linha do XML, é apresentado o valor de retorno da requisição, igual a 120 (fatorial de 5 é igual a 5x4x3x2x1).
Desenvolvendo o cliente para acessar o web service http://127.0.0.1:8080/axis/WebServiceCalculo. jws?method=fatorial&n=5
onde: http é o protocolo utilizado; 127.0.0.1 é o IP no qual será efetuada a conexão (localhost). O IP 127.0.0.1 só é válido para testes. Na prática, é necessário que o Tomcat, o Axis e o web service desenvolvido estejam instalados em um servidor que possua IP real, podendo nesse caso ser chamado pelo nome da máquina na internet; :8080 porta padrão do Apache Tomcat; /axis é a pasta em que foi instalado o apache Axis; /WebServiceCalculo.jws é o nome do Java Web Service; ? inicia a passagem de parâmetro para o Java Web Service; method=fatorial irá chamar o método publico fatorial, e; n=5 passará como parâmetro o valor 5 para o cálculo do fatorial.
A classe desenvolvida para acessar o web service é simples, pois utiliza recursos básicos da linguagem Java. A única preocupação é relativa ao uso de recursos das classes Service e Call, do Apache Axis. Para evitar problemas de compilação e fazer o programa cliente funcionar, é necessário acrescentar
3. ClienteWS.java - Classe que fará acesso ao WebServiceCalculo 01. import org.apache.axis.client.Call; 02. import org.apache.axis.client.Service; 03. 04. public class ClienteWS { 05. 06.
throws Exception { 07. 08. 09.
O resultado devolvido pelo web service será o arquivo no formato XML apresentado na 4.
public static void main(String[] args)
String local = “http://localhost:8080/axis/ WebServiceCalculo.jws”;
10. 11.
Call call = (Call) new Service().createCall();
12. 13.
call.setTargetEndpointAddress(local);
14. 15.
call.setOperationName(“fatorial”);
16. 17. 18.
Object[] param = new Object[]{ new Integer(5) };
19. 20.
Long ret = (Long)call.invoke(param);
21. 22.
System.out.println(“Resultado do fatorial : “ +
ret); 23. 24.
4. Resposta do web service, ao calcular o fatorial do número 5.
5. Configuração do Classpath na IDE GEL.
} // fim do método public static void main
25. } //fim da classe ClienteWS
6. Console do GEL, exibindo o resultado do programa cliente chamando o web service de fatorial.
26 3º Edição
wm03.indb 26
30/6/2005 17:32:32
Web Services
ao ClassPath os arquivos .jar da pasta /home_tomcat/webapps/ Axis/WEB-INF/lib. Para fazer isso através do ambiente Gel, utilize o menu Tools - Options... - Item JDK - Guia ClassPath - Add File e selecione os .jar do caminho citado anteriormente, conforme 5. Com o ambiente de desenvolvimento Gel configurado, codificaremos o programa cliente (ver 3). As linhas 01 e 02 importam classes do Axis (Call e Service), responsável pela invocação do web service. Na linha 09 é declarada uma variável do tipo String, valorizada com o endereço do web service. Já na linha 11 foi criado um objeto call que será o serviço para acesso ao web service; o método setTargetEndpointAddress tem a função de indicar qual o endereço que deverá ser chamado para fazer o acesso. A linha 15 informa qual método será invocado. Já na linha 18 foi criado um array de Object com apenas uma posição, pois o método fatorial implementado na 1 possui um único parâmetro inteiro (criado pelo comando new Integer( 5 ) ). O array foi criado por que, mesmo possuindo um único parâmetro, ele deve ser passado através de um array de objetos. Em seguida, na linha 20, o método fatorial é invocado através do método invoke do objeto call, passando o vetor de parâmetros (param) e o resultado é armazenado na variável ret. Concluindo, na linha 22 é impresso na console o valor retornado pelo web service. O resultado da execução deste programa pode ser visualizado na 6.
dware, de sistema operacional, e de linguagem de programação. No próximo artigo serão abordadas novas maneiras de desenvolver e acessar web services utilizando Java, inclusive em J2ME. Aguardem. Robison Cris Brito (robison@pb.cefetpr.br) é especialista em desenvolvimento web e professor do Centro Federal de Educação Tecnológica do Paraná Unidade do Sudoeste Campus Pato Branco/PR - CEFET, onde ministra aulas de Java e Sistemas Distribuídos, foi palestrante do JustJava 2004 e é um dos líderes do DukeDuck - Grupo de Usuários Java de Pato Branco/PR.
Ademir Roberto Freddo (freddo@pb.cefetpr.br) é mestre em Engenharia Elétrica e Informática Industrial e professor do Centro Federal de Educação Tecnológica do Paraná Unidade do Sudoeste Campus Pato Branco/PR - CEFET, onde ministra aulas de Multimídia e Tecnologia de Orientação a Objetos.
Conclusões Neste artigo foi apresentado o desenvolvimento de web services com Axis de forma clara e simples. Web services são uma alternativa para aplicações distribuídas. É uma tecnologia recente que precisa melhorar em termos de segurança e gerenciamento de transações, mas possui várias vantagens como interoperabilidade de har-
Sobre o container web Tomcat jakarta.apache.org/tomcat Sobre o Gel - editor de código Java www.gexperts.com Sobre o framework para desenvolvimento de web service Axis ws.apache.org/axis/ Artigos e tutoriais sobre Java e web services www.devmedia.com.br/webmobile Tutoriais e códigos de web services www.portaljava.com.br
WebMobile 27
wm03.indb 27
30/6/2005 17:32:33
ASP.NET Aumentando a performance de suas aplicações Web Parte I – Usando Connection Pooling, DataReaders e DataSets em Cache por Guinter Pauli
E
ste é o primeiro de uma série de artigos que mostrará como otimizar aplicações ASP.NET. Veremos como a tecnologia oferece poderosos recursos para tornar suas aplicações Web robustas e escaláveis, usando o mínimo de esforço possível. Conheceremos os poderosos recursos de cache de dados, uso efetivo de stored procedures, connection pooling, ajustes de configuração e outras técnicas avançadas. Nesta primeira parte, você aprenderá como usar DataSets em memória para evitar consultas desnecessárias ao servidor SQL e otimizar assim o tráfego de dados. Você também conhecerá um pouco sobre o interessante recurso de connection pooling do ADO.NET.
Criando a aplicação Para construir os exemplos desta série, utilizarei o Visual Studio .NET e o SQL Server 2000 como banco de dados. As aplicações serão feitas usando C#, mas podem ser facilmente escritas em VB.NET, caso queira utilizar essa linguagem. No Visual Studio, clique em File>New>Project (ou aperte Shift+Ctrl+N) e na janela New Project escolha ASP.NET Web Application no item Visual C# Projects ( 1). Na opção Location dê um nome para aplicação e a seguir clique em Ok. Neste momento, o Visual Studio criará um diretório virtual no IIS, localizado por padrão em c:\Inetpub\wwwroot\<NomeDaAplicacao>.
1. Criando uma nova aplicação ASP.NET no Visual Studio .NET
28 3º Edição
wm03.indb 28
30/6/2005 17:32:38
ASP.NET Configurando a conexão com ADO.NET O ADO.NET é a tecnologia de acesso a dados usada no .NET Framework. Para acessar um servidor de banco de dados fazemos uso de um provider. Cada provider está representado no formato de um conjunto de componentes, que implementam interfaces predefinidas pelo ADO.NET. O .NET Framework 1.1 é distribuído com quatro providers: SQL Server, OleDB, ODBC e Oracle. Para acessar o SQL Server, poderíamos usar qualquer um dos três primeiros providers, mas já vou deixar claro aqui a primeira regra para otimização ASP.NET: para acesso ao SQL Server, use o provider específico para SQL. O ganho de performance chega a 40% comparado aos demais, visto que esse provider usa o TDS (Tabular Data Stream), um formato nativo do SQL Server para comunicação. Os outros dois providers (OleDb e ODBC) exigem a utilização de uma camada extra de comunicação. A única vantagem em usar esses providers seria poder trocar de banco de dados (por exemplo, para Access) sem causar um efeito colateral muito grande no código da aplicação. Sabendo disso, vamos configurar uma conexão ADO.NET para o SQL Server. A partir da ToolBox, coloque um SqlConnection no Web Form. Selecione o componente e na janela Properties escolha New Connection na propriedade ConnectionString. No editor que aparece ( 2), informe o nome ou endereço IP do servidor na primeira caixa de entrada (digite “localhost” se o SQL Server estiver rodando na mesma máquina). Em User name e Password informe o usuário e senha padrão para acesso ao banco (“sa” e senha em branco, lembrando que essa senha pode ter sido alterada durante a instalação do SQL Server). E finalmente, escolha o banco de dados Northwind e clique em Ok. Caso queira, clique no botão Test Connection para verificar se os parâmetros estão corretos.
Pronto, já temos nossa conexão configurada! Antes de continuar, vamos agora examinar um importante recurso do ADO.NET, o uso de Connection Pooling.
Connection Pooling Observe que em User name e Password informamos um usuário e senha padrão para acesso ao banco, mas poderíamos ter usado a autenticação integrada. No entanto, aqui vai a segunda dica valiosa para otimização: forneça um usuário e senha fixos, de forma que todos os usuários que conectem à aplicação utilizem as mesmas credenciais. Se for necessário restringir acesso a determinado usuário, defina isso na forma de autorizações no Web.Config. Fornecer um usuário fixo fará o ADO.NET utilizar de forma efetiva o recurso de Connection Pooling, sem ter perda de desempenho. Connection Pooling é o mecanismo que permite ao ADO.NET “reaproveitar” conexões ao banco de dados. Imagine a seguinte situação: um usuário acessa a aplicação, conectamos ao BD para extrair informações e a exibimos no formulário. A seguir, fechamos a conexão e devolvemos o resultado ao browser. Como aplicações Web são state-less (sem estado), se esse mesmo ou outro usuário se conectar à aplicação, uma nova conexão precisará ser restabelecida. Conectar ao BD a cada requisição de usuário é literalmente um “suicídio” em ambiente Web, onde uma aplicação pode ter centenas e até milhares de conexões simultâneas. O ADO.NET resolve isso de forma bastante elegante: após a página ser enviada ao browser, a conexão com o BD não é liberada, mesmo que você tenha chamado explicitamente o método Close do SqlConnection. O ADO.NET guarda automaticamente a conexão em pool (imagine isso como uma espécie de cache de conexões). Ou seja, a conexão fica aberta com o banco de dados e persiste entre requisições. Quando outro usuário conectar na aplicação, o ADO. NET verifica se existe uma conexão disponível no pool e caso encontre, a utiliza. Com isso, todo o tempo necessário para localizar o servidor de BD, estabelecer uma conexão, autenticar um usuário e verificar permissões não será mais consumido a cada requisição. E o melhor de tudo, você não precisa fazer nada para usar esse recurso, pois ele já é ativado por padrão. Criar um mecanismo de Connection Pooling “no braço” via código é algo extremamente complicado (infelizmente já tive que passar por esse esforço em uma ocasião). No ADO.NET já temos isso pronto no próprio framework! Produtividade é um dos pontos fortes do .NET (ler 1).
1. Connection Pooling O Connection Pooling só pode ser usado em ambiente multi-threading (uma aplicação Web, por exemplo), onde temos várias threads simultâneas processando solicitações clientes. Não faz sentido, por exemplo, usar Connection Pooling em uma aplicação Windows Forms tradicional (duas camadas).
2. Configurando uma conexão ao SQL Server.
Você pode ainda controlar como o ADO.NET trabalha com Connection Pooling, fazendo alguns ajustes na propriedade ConnectionString do SqlConnection. Podemos especificar alguns
WebMobile 29
wm03.indb 29
30/6/2005 17:32:40
Parâmetro
Valor Default
Descrição
Connection Lifetime
0
Tempo de vida, em segundos, que uma conexão deve ficar no pool desde a sua criação
Max Pool Size
100
Número máximo de conexões que podem ficar em pool
Min Pool Size
0
Número mínimo de conexões que devem ficar em pool
Pooling
True
Indica se o Connection Pooling está habilitado
1. Parâmetros do Connection Pooling
parâmetros, veja os principais na 1. Veja um exemplo de como poderíamos usar alguns desses parâmetros na string de conexão da nossa aplicação (você pode fazer essa modificação diretamente na propriedade ConnectionString, a partir da janela Properties): workstation id=ASUS;packet size=4096;user id=sa;data source=ASUS;persist security info=False;initial catalog=Northwind ;Pooling=True;Min Pool Size=50;Connection Lifetime=120;
Atenção: sempre use a mesma ConnectionString (com os mesmos valores para todos os parâmetros) em todos os objetos de conexão, para que compartilhem o mesmo Connection Pooling.
de um SqlCommand. Você pode então usar o objeto de retorno para varrer o resultset obtido, usando o seu método Read. Aqui não foi necessário fazer essa varredura manualmente, visto que usamos o recurso de DataBind do ASP.NET para vinculação de dados com o DataGrid, através da propriedade DataSource. Observe que todo o código de execução é envolvido em um bloco try...finally. O código dentro do bloco finally sempre será executado, incondicionalmente, mesmo que uma exceção ocorra (por exemplo, se o comando SQL digitado fosse inválido). Com isso garantimos que o Close do SqlConnection sempre seja chamado após a execução do código dentro do try, devolvendo a conexão para o pool.
Usando um DataReader para exibir dados em um DataGrid Seguindo nosso exemplo, veremos em um primeiro momento como exibir dados de uma tabela do SQL Server em um controle DataGrid, usando nossa conexão previamente configurada. A maneira mais rápida de extrair dados do banco é usando um DataReader (um SqlDataReader, no caso do provider para SQL Server). Um DataReader é responsável por fazer a leitura dos dados retornados por uma consulta SQL, usando um cursor de dados. Ele é rápido pelos seguintes motivos: • é unidirecional: a navegação pelos registros é feita de forma seqüencial (forward-only). Basicamente lemos um registro, fazemos alguma coisa com ele (exibimos dados em um controle, por exemplo) e navegamos para o próximo registro; • é somente-leitura: não é possível alterar registros de um DataReader; • não faz cachê: após um registro ser lido, ele é descartado da memória. O código da 1 mostra como usar um DataReader (coloque o código no Page_Load do Web Form). Aqui abrimos a conexão com o banco usando o método Open do SqlConnection. A seguir, usamos um SqlCommand para executar um comando Select na tabela Products. Para executar a consulta, chamamos o método ExecuteReader do SqlCommand (chamado aqui de cmd), cujo valor de retorno é atribuído à propriedade DataSource do DataGrid. Mas onde está o objeto SqlDataReader? Um DataReader nunca é instanciado diretamente. Você sempre obterá a referência a um objeto desse tipo através da chamada ao método ExecuteReader
1. Utilizando o DataReader // Declare System.Data.SqlClient no using private void Page_Load(object sender, System.EventArgs e) { sqlConnection1.Open(); try { SqlCommand cmd = new SqlCommand(“select * from Products”,sqlConnection1); DataGrid1.DataSource = cmd.ExecuteReader();; DataGrid1.DataBind();
} finally { sqlConnection1.Close(); } }
3. Usando um DataReader como fontes de dados para um DataGrid.
30 3º Edição
wm03.indb 30
30/6/2005 17:32:41
ASP.NET
A
3 mostra o resultado obtido.
Usando DataSets em Cache Sem dúvida o principal componente do ADO.NET é o DataSet. Ele pode ser usado com qualquer um dos providers para ADO.NET, representando uma estrutura de dados em memória. Aqui o termo “memória” se refere ao fato de que podemos criar uma consulta a uma tabela do banco de dados, extrair informações e usá-las após a conexão ser fechada. Por esse motivo, aplicações que usam esse componente são conhecidas como “desconectadas”. Enquanto um DataReader exige que uma conexão esteja ativa para que uma leitura de dados seja feita, um DataSet pode obter seus dados uma única vez e guardá-los em memória para uso posterior. Isso garante ainda mais a escalabilidade de aplicações ASP.NET. Depende de você detectar quando é melhor usar um DataReader ou um DataSet. Para ficar mais claro, vamos imaginar uma situação: você trabalha para uma universidade e precisa construir a página de inscrição para o concurso vestibular promovido pela instituição. Nessa página, existe um formulário para cadastro do candidato, onde constam alguns TextBoxes para entrada de campos como Nome, Endereço, Data Nasc etc. A “única” informação dinâmica (que vem de um BD) está em um DropDownList, onde o candidato pode escolher o curso no qual deseja se inscrever. Essas informações são obtidas a partir de uma tabela do banco de dados, que possui todos os cursos com vagas disponíveis. Agora responda uma coisa: para que conectar ao banco de dados para preencher o DropDownList, a cada vez que um usuário abre a página, se os cursos nunca mudam? Para aumentar a performance, dá até vontade de digitar estaticamente os dados na propriedade Items, não?! Em uma situação como essa, a tabela de cursos seria modificada no BD provavelmente uma vez por ano ou semestre. Esse é um exemplo típico onde podemos usar um DataSet ao invés de um DataReader. Vamos ver como usar o recurso na prática. Inicie uma nova aplicação ASP.NET, seguindo os mesmos passos do exemplo anterior. Coloque um SqlDataAdapter no formulário e no assistente que abrirá escolha a conexão que criamos anteriormente. Utilize a seguinte instrução SQL para obter dados da tabela Categories (que armazena informações sobre categorias de produtos): SELECT CategoryId, CategoryName
DataSource para uma função chamada dsCatProd (vista na 3), que retorna um DataSet. DataMember indica o nome do DataTable (Categories), DataTextField indica qual o campo será usado para exibir os itens no controle e DataValueField o campo que contém o valor para a chave primária. Ou seja, o usuário escolherá uma categoria pelo nome, e nós recuperamos apenas o seu código. Implemente a função dsCatProd conforme mostrado na 3. No código anterior, testamos se existe na Cache uma variável com o nome que estipulamos (“dsCatProd”). Se existir, é porque o DataSet já está em memória (provavelmente um usuário já realizou a consulta anteriormente). Caso contrário, refazemos a cache de dados, chamando o método Fill do SqlDataAdapter para preencher os dados do DataSet. E finalmente, colocamos o DataSet em memória (objeto Cache). O objeto Cache é usado no ASP.NET para compartilhar dados entre todos os usuários da aplicação. Mesmo após a requisição ser processada, as informações permanecem na memória do servidor e podem ser utilizadas posteriormente. Você pode colocar quantos objetos quiser em memória, bastando fornecer um nome diferente para cada um, mas tome cuidado para não exagerar na quantidade de informações que você vai armazenar no servidor (ler 2).
2. Código do evento Page_Load private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { DropDownList1.DataSource = dsCatProd(); DropDownList1.DataMember = “Categories”; DropDownList1.DataTextField = “CategoryName”; DropDownList1.DataValueField = “CategoryId”; DropDownList1.DataBind(); } }
3. Função dsCatProd private DataSet dsCatProd() { if (Cache[“dsCatProd”] == null)
FROM
{
Categories
DataSet ds = new DataSet(); sqlDataAdapter1.Fill(ds,”CATEGORIES”);
Coloque um DropDownList no formulário e no Page_Load do Web Form digite o código apresentado na 2. Aqui estamos simplesmente testando se a página está sendo carregada pela primeira vez (IsPostBack) para então inicializar o DropDownList. Observe que atribuímos o valor da propriedade
Cache[“dsCatProd”] = ds; } return (DataSet)Cache[“dsCatProd”]; }
WebMobile 31
wm03.indb 31
30/6/2005 17:32:42
2. Cache O ASP.NET permite que você use um state server (servidor de estado), permitindo que as informações colocadas em cache e sessão residam em um processo diferente do aspnet_wp.exe (o processo usado pelo ASP.NET para rodar as aplicações Web). É possível, inclusive, especificar um servidor dedicado (que tenha mais memória, por exemplo) apenas para armazenar objetos de cache e sessão. Uma outra alternativa é persistir as sessões no próprio banco de dados, para poupar memória. Falaremos sobre ambas as técnicas em futuros artigos.
A 4 mostra a aplicação em execução. Faça um teste, abra um segundo browser e acesse a mesma página, porém antes pare o servidor de banco de dados. Observe que os dados serão mostrados mesmo que o SQL Server esteja desativado, indicando que as informações realmente não foram obtidas a partir do BD, mas a partir da cache que já estava armazenada na memória do servidor.
Usando DataViews Vamos aprimorar o exemplo. Vamos permitir que o usuário veja os produtos relacionados quando escolher uma determinada categoria. Ao invés de criarmos uma nova consulta ao BD após a seleção, vamos filtrar uma consulta que já estará residente em memória. Ou seja, vamos guardar todos os produtos em cache e usar um DataView para filtragem. Coloque um ListBox no formulário e um segundo SqlDataAdapter, configurando a seguinte instrução SQL, que retorna todos os registros da tabela de produtos (Products):
parâmetro para o método Fill de ambos os SqlDataAdapters. Inclua então o seguinte código, logo abaixo do Fill do SqlDataAdapter1, que está no método dsCatProd: ... sqlDataAdapter2.Fill(ds,”PRODUCTS”);
Agora, quando a página for aberta pela primeira vez, o DataSet conterá em memória dados sobre todos os produtos e categorias do banco de dados. Tudo o que precisamos fazer é manipular essas informações da melhor forma, sem a necessidade de consultar o BD a cada requisição. Por exemplo, para exibir os produtos relacionados à categoria selecionada, altere o Page_Load como mostrado na 4. O código é semelhante ao usado para preencher o DropDownList, exceto que agora estamos usando um DataView como fonte de dados para o controle ListBox. A função dvCatProd recebe o código de uma categoria por parâmetro e retorna os produtos relacionados, através de um DataView, obtidos a partir do DataSet de memória. Seu código é mostrado na 5.
4. Código do evento Page_Load atualizado private void Page_Load(object sender, System.EventArgs e) { if (!IsPostBack) { // código do DropDownList ... } else
SELECT
{
CategoryId, ProductName
ListBox1.DataSource =
FROM
dvCatProd(DropDownList1.SelectedValue);
Products
ListBox1.DataTextField = “ProductName”; ListBox1.DataBind();
Ao invés de utilizarmos um segundo DataSet para guardar os produtos, vamos usar o mesmo criado anteriormente. Isso é possível de ser feito no ADO.NET, basta passar o mesmo DataSet como
} }
5. Função dvCatProd private DataView dvCatProd(object CategoryId) { DataView dv = new DataView(); dv.Table = ((DataSet)Cache[“dsCatProd”]).Tables[“PRODUCTS”]; dv.RowFilter = “CategoryId = “ + CategoryId. ToString(); return dv; }
4. Usando um DataSet em Cache.
32 3º Edição
wm03.indb 32
30/6/2005 17:32:43
ASP.NET
5. Usando DataViews a partir de um DataSet em memória
Um DataView é ideal para se usar com DataSets de memória. Ele permite que você filtre e ordene resultsets, fornecendo diferentes “visões” de um mesmo conjunto de dados, sem usar nenhum tipo de instrução SQL ou comunicação extra com o banco. Vale lembrar que você pode ter vários DataViews atuando sobre um mesmo DataSet. O último passo é configurar o AutoPostBack do DropDownList para True, para que seja feito o postback no servidor quando o usuário escolher um item no controle. A 5 mostrar o exemplo em execução. É claro, se os dados forem modificados no banco de dados, eles não serão refletidos no DataSet da cache. É sua obrigação refazer a cache quando necessário. O ASP.NET possui alguns recursos prontos para facilitar esse processo. Podemos, por exemplo, especificar um critério de expiração para a cache, ou criar um mecanismo de dependência. Falaremos sobre isso nos próximos artigos.
Relations Uma última dica é sobre a utilização de Relations. Em nosso exemplo, temos dois DataTables em memória dentro de um único DataSet. Podemos facilmente relacionar os dois resultsets, adicionando a seguinte linha após a chamada aos métodos Fill:
6. Usando DataRelations.
os registros da tabela “mestre”, nesse caso Categories. Para cada categoria, uso o método GetChildRows para obter os registros relacionados (tabela Products) e dar a saída no browser. O resultado é mostrado na 6.
Conclusões Utilizando as técnicas vistas neste artigo, você poderá agora otimizar suas aplicações ASP.NET para que tenham uma melhor performance e escalabilidade, deixando seu usuário final muito mais satisfeito com o tempo de resposta da sua aplicação. Na continuação desta série, vamos aprender mais recursos interessantes relacionados à performance de soluções Web com ASP.NET. Um abraço a todos e até lá!
Guinther Pauli é Bacharel em Sistemas de Informação (Unifra – RS), autor de mais
ds.Relations.Add(“CatProd”, ds.Tables[“Categories”].Columns[“CategoryID”], ds.Tables[“Products”].Columns[“CategoryID”]);
de 100 artigos publicados e do livro “Delphi – Programação para Banco de Dados e Web”. Detém três certificações Microsoft (MCP): C# / ASP.NET,
Isso relaciona ambas as tabelas pelo campo CategoryID. Agora, podemos facilmente tirar proveito desse relacionamento, que é feito totalmente em memória e de forma otimizada, como, no código a seguir:
VB.NET / ASP.NET e C# / Windows. Detém quatro certificações Borland: Delphi Advanced pela Borland dos Estados Unidos, Delphi Web Development, Delphi 6 Product e Kylix. É editor geral da revista ClubeDelphi e Editor Técnico da Revista WebMobile Magazine. Pode ser contatado pelos endereços guinther_pauli@hotmail.com e
foreach (DataRow r1 in dsCatProd().Tables[0].Rows) { Response.Write(“<h3>” + r1[“CategoryName”].ToString() +
guinther@clubedelphi.net – Blog: br.thespoke.net/ MyBlog/Guinther/MyBlog.aspx.
“</h3>”); foreach (DataRow r2 in r1.GetChildRows(“CatProd”)) Response.Write(“<li>” + r2[“ProductName”].ToString() + “<br>”);
Faça o download no site: www.devmedia.com.br/webmobile/downloads
}
Nesse código estou usando um foreach para percorrer todos
WebMobile 33
wm03.indb 33
30/6/2005 17:32:44
O
Infraestrutura
web por Marcelo Morgade
início do aprendizado de uma linguagem ou framework de programação web é sempre uma experiência gratificante. Todos se sentem realizados ao sair da “velha” e “ultrapassada” arquitetura cliente-servidor para o novo e promissor mundo dos sistemas com interface web. Porém, na ânsia de ganhar experiência rapidamente neste novo campo, alguns programadores queimam etapas importantes do processo de aprendizagem e encaram os recursos de programação para web simplesmente como uma nova biblioteca qualquer de uma linguagem que este já está familiarizado. Ele usa objetos para criar interfaces, mas não possui muito conhecimento sobre a infra-estrutura que estes objetos representam e que torna a internet possível. Certamente esta visão limitada em relação às adaptações necessárias para a utilização de uma linguagem de programação para web é um dos problemas que mais causam dificuldades para quem está começando. O desenvolvedor descobre que deve manipular objetos de nomes estranhos como request, response e session, e começa a utilizá-los sem conhecer o significado e o funcionamento desses novos objetos. A partir daí, ele se torna uma figura conhecida como “colador de código”. “Colar código” parece funcionar no início da utilização deste novo paradigma de desenvolvimento, mas logo o programador começa a enfrentar requisitos um pouco mais complexos. Neste momento o desenvolvedor irá se perguntar desesperado qual parte do estudo pulou. Ele percebe, assim, que conhecer um pouco do funcionamento da web em um nível um pouco mais baixo é essencial para que possa evoluir de um simples “colador de código” para um programador eficiente. Ao longo deste artigo, iremos descrever os principais conceitos relacionados ao desenvolvimento de uma aplicação web, e como estes conceitos estão presentes em uma linguagem de programação.
Um pouco de história No protocolo HTTP temos definidas as fases de comunicação entre clientes (os browsers) e servidores web, os formatos das mensagens trocadas entre estes dois programas, além de um padrão de endereçamento de documentos chamado hoje de Uniform Resource Locator (URL). O HTTP passou a ser usado amplamente no meio acadêmico para troca de informação científica e, com a especificação da Hyper-Text Markup Language (HTML), sagrou-se rapidamente como o serviço mais popular da internet. O HTTP tornou-se um padrão de fato em 1996 com a publicação da RFC1945 (ver o que são RFCs na 1) pelo Internet Engineering Task Force (IETF). Este documento descreve todos os detalhes da primeira versão do protocolo de aplicação utilizado pela web.
1. Request for Comments. As RFCs são documentos publicados por grupos de trabalho do IETF com o objetivo de padronizar a implementação de serviços, protocolos ou procedimentos (existe, por exemplo, uma RFC que cria
34 3º Edição
wm03.indb 34
30/6/2005 17:32:45
Web
um padrão de escrita de RFCs!). Elas são extremamente importantes, já que o estabelecimento de padrões de comunicação foi um dos passos básicos para o crescimento da internet. As RFCs são a fonte de informações primordial dos desenvolvedores de programas, componentes de comunicação ou equipamentos que fazem uso da internet.
Há algum tempo atrás, esta RFC era a única fonte de estudo de um programador que estivesse entrando no mundo web (www.ietf.org/rfc/). Mas, atualmente não é mais necessário saber toda esta especificação. Hoje temos linguagens e frameworks direcionados para o desenvolvimento de aplicações web que já implementam tudo aquilo que está padronizado pelo HTTP. Aqueles objetos de nomes estranhos como request, response e session nada mais são do que pacotes de software através dos quais podemos interagir com o HTTP. É por isso que para deixar de ser somente um “colador de código”, o programador deve conhecer um pouco sobre o funcionamento do HTTP, pois é com ele que estará sempre interagindo e é ele quem determinará o que é possível fazer numa aplicação web.
O Hyper-Text Transfer Protocol Logo no seu início, a RFC descreve o HTTP de modo geral como um protocolo baseado no paradigma requisição/resposta que não mantém informações sobre estado. Estas informações de estado poderiam envolver dados sobre o tempo total que alguém leva num site, quantos links alguém clicou ou qual a seqüência de páginas acessada por um determinado usuário. A aquisição destes dados foi simplesmente ignorada, com o objetivo de tornar o HTTP algo extremamente simples. Assim, a principal função do HTTP é simplesmente gerenciar pedidos simples de requisição de dados e resposta. Pode parecer pouco, mas este conhecimento sobre o HTTP envolve as informações mais importantes para entendermos como funciona a web. Isso permite entender o que acontece quando um usuário digita um endereço em um browser e pressiona a tecla <enter> ou quando clica em um link qualquer. Eis o que acontece: 1. O browser processa a parte inicial da URL de forma a descobrir o endereço do servidor. Ele então se conecta com este servidor usando o protocolo TCP/IP e envia uma mensagem (requisição) baseada na parte final da URL junto com outros parâmetros. 2. O servidor web recebe e processa esta mensagem e seus parâmetros. A depender da requisição, envia uma resposta diferente contendo um status, outros parâmetros e um conteúdo qualquer (a resposta). O HTTP se resume a estes dois passos acima. Ele sai de cena depois que o browser recebe a resposta, cabendo a este fazer o processamento dos parâmetros e conteúdo da resposta do servidor. Toda esta simplicidade ajuda no desempenho do servidor, uma
vez que ele não precisa armazenar em sua memória informações sobre cada uma das requisições anteriores feitas por um browser e não precisa manter a conexão aberta após responder a uma requisição. Existem protocolos mais complexos como o FTP (File Transfer Protocol), que exige o armazenamento na memória do servidor de uma série de informações sobre cada cliente (como login, senha, e diretório do usuário), além de necessitar de um fluxo contínuo de troca de mensagens e uma conexão ininterrupta entre cliente e servidor. Isso faz com que servidores FTP aceitem apenas uma quantidade limitada de usuários simultaneamente. A simplicidade do HTTP permite que muito mais usuários se comuniquem com o servidor ao mesmo tempo. Observaremos a partir de agora como aqueles objetos estranhos presentes nas linguagens começam a fazer sentido quando olhamos as características básicas do HTTP.
Objetos request e requisições HTTP Uma requisição ocorre cada vez que um link é clicado, um formulário é submetido, uma imagem é carregada ou qualquer outra informação presente no servidor é necessária. Cada arquivo que compõe uma página que você visualiza na web veio de uma requisição diferente do seu browser ao servidor. Estas requisições HTTP são nada mais que mensagens em texto ASCII enviadas ao servidor que contêm um nome de arquivo a ser devolvido e um conjunto de parâmetros. A RFC1945 define o formato destas mensagens e o papel de alguns parâmetros. Através da definição destes formatos, todos os browsers e servidores podem processar corretamente requisições e respostas, não importando se rodam em Linux, Windows ou em um Palmtop. Observe um exemplo de uma mensagem de requisição na 1. Nesta requisição, o browser informa que o servidor deve processar o arquivo rfc.html ( 1 - Linha 1), além de informar sua versão ( 1 - Linha 1), sistema operacional ( 1 - Linha 2) e ainda qual o idioma preferido do usuário ( 1 - Linha 3). Um teste interessante é simularmos um browser através do telnet. Para isto, conecte-se via telnet no servidor do IETF na porta 80 (a porta padrão dos servidores web). Na maioria dos sistemas operacionais você pode fazer isso usando o seguinte comando: telnet www.ietf.org 80
Uma vez conectado, digite exatamente o texto da requisição apresentada na 1 sem errar nada (se errar algo, o servidor não reconhece a mensagem). Depois da última linha, aperte a tecla <enter> duas vezes. Se você fez tudo certo, o servidor mandará uma resposta contendo alguns parâmetros e o conteúdo do arquivo rfc.html. Você acaba de ver na prática como funciona a comunicação browser-servidor web. Analisando a primeira linha do exemplo apresentado ( 1), observa-se a palavra GET. A primeira palavra de uma requisição é chamada de método. O método define o que o servidor deve
WebMobile 35
wm03.indb 35
30/6/2005 17:32:47
fazer com a requisição. Na RFC é possível encontrar a definição de diversos métodos (GET, POST, PUT, DELETE, HEAD). Porém, os browsers enviam na maioria das vezes apenas os métodos GET, POST e HEAD. Os dois primeiros requisitam o conteúdo do arquivo pedido, porém passam parâmetros de maneiras diferentes. O HEAD algumas vezes é usado para checar se certo arquivo foi modificado ou se um cache local pode ser usado. Observe que os objetos request das linguagens web normalmente disponibilizam a informação sobre qual método de requisição foi usado. Logo após o método, o browser deve passar o caminho para o arquivo requisitado no servidor. É neste trecho da requisição que o servidor deve decidir se irá enviar o conteúdo de um arquivo HTML, uma imagem, ou ainda se deverá executar algum script ou programa que gerará o conteúdo a ser devolvido na resposta. Para o browser, pouco importa como o conteúdo da resposta foi construído. A única preocupação do browser nesse momento é mostrar na tela de forma correta o conteúdo desta resposta. No final da primeira linha, é informada qual a versão do HTTP é usada. A versão 1.1 do HTTP é especificada na RFC2068 e define algumas novas extensões. Alguns cabeçalhos muito úteis são especificados e outras funcionalidades adicionadas, mas nada que modifique a natureza simples do protocolo. Novamente, os objetos request das linguagens web também disponibilizam qual versão do HTTP está sendo utilizada. Após a primeira linha da requisição ( 1 – Linhas 2 e 3), é enviado um conjunto de dados chamado de cabeçalhos (headers) HTTP. Nos cabeçalhos podemos recuperar informações muito úteis sobre o cliente que fez a requisição. Observe que podem existir diversas linhas de cabeçalho, sempre seguindo o formato: <Nome do Cabeçalho>: <valor>
Existem muitos cabeçalhos de requisição definidos no HTTP, cada um com a função de enviar informações ou preferências do cliente ao servidor. São estas informações que permitem certas aplicações web fazerem “mágicas” como mostrar o conteúdo em inglês ou português a depender do idioma preferido pelo usuário. Observe que esta “mágica” pode ser feita apenas testando o valor do cabeçalho Accept-Language que os browsers passam nas requisições. Agora que você sabe como a “mágica” é feita, certamente já sabe onde deve buscar essa informação na sua linguagem web. Em seu objeto request deve existir uma maneira de descobrir o valor de um determinado cabeçalho HTTP e assim utilizar estas informações para tomar decisões que a princípio não pareciam possíveis. Você pode também, por exemplo, obter o valor do cabeçalho Referer via o objeto request para descobrir de qual página o usuário de sua aplicação veio. A RFC2068 é uma boa fonte de pesquisa para descobrir outros cabeçalhos de requisição com informações que podem ser úteis à sua aplicação.
1. Exemplo de mensagem e requisição HTTP – Método GET 1. GET /rfc.html HTTP/1.0 2. User-Agent: Mozilla/4.0 Linux 3. Accept-Language: pt-br
2. Exemplo de mensagem de requisição HTTP – Método POST. POST /servlet/respostaDoForm HTTP/1.0 User-Agent: Mozilla/4.0 Linux Accept-Language: pt-br
param1=valor1&param2=valor2
O HTTP também define a forma como os parâmetros de requisição são passados, através de uma string de consulta (query string) após o arquivo: “/arquivo?param1=valor1&pa ram2=valor2…” (usada com o método GET). Outra forma de passar parâmetros é através do método POST, que codifica estes dados uma linha após a lista de cabeçalhos da requisição. No exemplo da 2, observamos uma requisição feita através do método POST, provavelmente feita a partir de um formulário HTML que contém campos de dados chamados param1 e param2 cujos valores eram valor1 e valor2 no momento da requisição. A recuperação dos parâmetros de requisição é, normalmente, o recurso mais usado dos objetos request das linguagens web, uma vez que é o método mais comum para recuperar dados de entrada enviados pelos usuários através de links ou formulários HTML.
Objetos response e respostas HTTP Ao receber uma requisição HTTP, o servidor web deve processar todos os seus dados ou deve entregá-los a uma aplicação web empacotados na forma de um objeto request. O servidor ou a aplicação deve então devolver ao browser uma resposta apropriada para a requisição. Se você conseguiu simular a requisição de browser via telnet, observou como é o formato de uma resposta HTTP (ver 3). Observe na linha 1 ( 3), que o servidor informa novamente a versão do HTTP e logo após passa um número chamado de status de resposta. É através deste número que o servidor informa se a requisição obteve sucesso ou se ocorreu algum problema na requisição. O status 200 informa que a requisição foi atendida e o conteúdo da resposta virá em seguida. Alguns outros números de status são bem conhecidos, como o 404 (informa que a página ou aplicação não foi encontrada) e o 500 (informa que houve um erro no servidor ou na aplicação web). Um número de status que
36 3º Edição
wm03.indb 36
30/6/2005 17:32:48
Web
3. Exemplo de mensagem de resposta HTTP. 1.
HTTP/1.1 200 OK
2.
Date: Mon, 30 Aug 2004 20:58:29 GMT
3.
Server: Apache/2.0.46 (Red Hat)
4.
Last-Modified: Fri, 02 May 2003 19:13:31 GMT
5.
ETag: “414159-cf2-35634cc0”
6.
Accept-Ranges: bytes
7.
Content-Length: 3314
8.
Connection: close
9.
Content-Type: text/html; charset=UTF-8
10. <HTML> ... conteúdo da página 11. </HTML>
muitos utilizam sem saber é o 302, que informa ao browser para redirecionar a requisição para outro arquivo. Normalmente, o número de status é configurado automaticamente pelo servidor web. Mas, também podemos manipulá-lo em nossas aplicações. Os objetos response geralmente possibilitam configurar manualmente o status da sua resposta. Após a primeira linha da resposta ( 3 – Linhas 2 a 9), é enviado um conjunto de cabeçalhos HTTP no mesmo formato visto durante a requisição. Desta vez, estes cabeçalhos vão fornecer ao browser informações adicionais sobre o servidor e a resposta recebida. Usando o seu objeto response você pode setar o valor de alguns cabeçalhos da sua resposta para que o browser efetue funcionalidades específicas. Você pode, por exemplo, pedir que o browser refaça a requisição de tempos em tempos configurando o cabeçalho:
código fonte do programa e assim se perde a noção de que o objeto response é o verdadeiro responsável por administrar este conteúdo da resposta. Um detalhe importante no formato da mensagem de resposta HTTP é o fato do conteúdo da resposta ser mandado sempre depois dos cabeçalhos e do status. Isso é a causa de um erro comum cometido pelos programadores, que tentam configurar o status ou valores de cabeçalhos após já terem mandado parte do conteúdo da resposta. É por este motivo que só se pode redirecionar o browser para outra página se nenhum conteúdo de resposta tiver sido enviado.
Cookies Agora que você conhece os recursos básicos do HTTP, fica mais claro entender outros recursos das linguagens web. O envio de cookies é um destes recursos que se tornou um mito, pois sempre foi visto como uma brecha de segurança para os browsers. Mas os cookies são apenas mais um conjunto de cabeçalhos que pode ser enviado nas requisições e respostas HTTP. Este novo conjunto de cabeçalhos foi especificado na RFC2109, que define mecanismos capazes de implementar gerenciamento de estado através do HTTP. Esse gerenciamento de estados implementado usando cookies permite saber, por exemplo, quantas vezes uma determinada pessoa esteve em seu site, gravando esta informação no computador cliente. Quando você pede para seu objeto response enviar um cookie com o número de visitas para ser gravado no browser, o objeto apenas adiciona um cabeçalho na resposta HTTP com o seguinte padrão: Set-Cookie: numerodevisitas=129;expires=Friday, 31-Dec-2006 23:59:59 GMT; path=/minhaAplicacaoWeb
Refresh: 3
Você também pode dizer ao browser que a resposta que sua aplicação envia não é HTML, mas sim um arquivo de dados separado por vírgula que deve ser salvo no disco e não mostrado para o usuário. Para isso, basta definir os cabeçalhos:
Se o browser que receber este cabeçalho tiver o recebimento de cookies habilitado, esta informação sobre o número de visitas será gravada em disco e associada ao seu site e aplicação. Da próxima vez que o browser fizer uma requisição qualquer para sua aplicação (mesmo que seja uma semana depois da última visita), ele adicionará o seguinte cabeçalho:
Content-Type: text/comma-separated-values Content-Disposition: inline; filename=meuarquivo.csv
Cookie: numerodevisitas=”129”; $Path=”/minhaAplicacaoWeb”
Mais uma vez, uma consulta às RFCs é a fonte mais completa de informações sobre o que é possível definir nos cabeçalhos de resposta HTTP. Finalmente, depois dos cabeçalhos vem o conteúdo da resposta. Neste ponto normalmente se usa algum recurso dos objetos response que permita mandar dados seqüencialmente para o browser. Em linguagens ou frameworks Web baseados em scriptlets (ASP, PHP, JSP), o HTML é misturado com o
Quando sua aplicação recebe uma requisição com este cabeçalho, você pode pedir ao objeto request o valor do cookie “número de visitas” e terá como resposta o valor 129. É neste mecanismo simples de troca de cabeçalhos que os cookies são baseados. Observe que existem outros detalhes que você pode pesquisar na RFC como a data de expiração dos cookies, comentários, opções de segurança, etc. Tudo isso é implementado nos cabeçalhos Cookie e Set-Cookie.
WebMobile 37
wm03.indb 37
30/6/2005 17:32:49
Os objetos session Um programador iniciando no mundo web descobre a importância dos objetos session logo no início do seu estudo. O objetivo principal de existência desses objetos é semelhante ao objetivo dos cookies, ou seja, implementar o gerenciamento de estados que falta no HTTP. Você já deve também ter ouvido falar que os objetos session são implementados através de cookies (ou como vimos, apenas mais cabeçalhos HTTP). A principal diferença entre estes dois recursos está no local onde as informações de estado são guardadas. Vimos que usando cookies, dados podem ser enviados diretamente para o browser a fim de serem armazenados na máquina cliente. Utilizando os objetos session, estes dados relacionados a um browser são guardados por um tempo na memória do servidor, e não nos discos dos seus usuários. Os cookies são usados neste caso apenas para identificar os browsers e associá-los ao objeto session correspondente na memória do servidor. Estes cookies são chamados cookies de sessão e não são gravados no disco do cliente. O browser apenas os guarda na memória enquanto está executando e simplesmente os descarta depois que são fechados. Para um servidor implementar um objeto session, ele precisa enviar um cookie de sessão na primeira requisição que o browser faz à sua aplicação. O servidor cria uma área na sua memória para um objeto session nesta primeira requisição e define um valor chamado SESSIONID que é o identificador deste objeto na memória. Este SESSIONID é o único dado passado ao browser em forma de cookie: Set-Cookie: SESSIONID=To1010mC961569297620153At;Path=/
Nas requisições seguintes, o browser incluirá este cookie com o SESSIONID recebido no cabeçalho HTTP. Com esta informação, o servidor pode recuperar o objeto session específico do browser criado nas requisições anteriores e recuperar todas as informações armazenadas pela aplicação que estão relacionadas ao usuário “dono” da sessão. Isso permite implementar funcionalidades interessantes, como os famosos carrinhos de compra encontrados nos sites de comércio eletrônico. Estes “carrinhos” são módulos responsáveis por guardar a lista de produtos que você escolhe durante a visita a um site de compras. Quando você clica para comprar um produto, ele é adicionado no seu carrinho de compras e você pode continuar buscando por outros produtos no site. Na hora do pagamento, o site é capaz de mostrar todos os produtos que você colocou no carrinho durante suas compras. Tudo isso é possível com o uso do objeto session, que pode armazenar de forma segura as informações sobre os produtos escolhidos por cada usuário durante uma compra. Observe que os dados obtidos de um objeto session são muito mais confiáveis que os dados vindos de um cookie. Naquele exemplo do número de visitas, um usuário pode abrir o arquivo
do cookie gravado em sua máquina em modificar o dado manualmente. Usando o session, o máximo que o usuário pode fazer é modificar o valor do seu SESSIONID. Isso só o levaria a perder todos os dados da sua sessão no servidor (a não ser que ele tenha muita sorte e acerte o valor do SESSIONID de um objeto criado para outro browser). Objetos session podem ocupar muita memória no servidor e por isso precisam ser bem gerenciados. Como o HTTP é baseado no esquema de requisição-resposta, o servidor não tem como saber se um cliente associado a um determinado objeto session saiu do site ou ainda está simplesmente lendo o conteúdo da resposta. Devido a essa característica, normalmente se trabalha com um tempo de limite de sessão. Cada objeto session na memória do servidor tem um prazo de duração, que é renovado sempre que o browser dono do objeto faz uma requisição. Se o cliente fica muito tempo sem fazer uma requisição e o tempo do seu objeto se esgota, o servidor assume que o cliente não interagirá novamente e remove o objeto da memória. Neste caso, normalmente diz-se que a sessão expirou. Este tempo normalmente é configurável através do objeto session, e deve ser bem estudado de acordo com a aplicação. Um tempo de sessão muito curto pode irritar os usuários mais “lentos”, que podem perder suas sessões por levarem muito tempo lendo uma página. Um tempo muito longo pode fazer com que o servidor fique com a memória sobrecarregada de objetos session desnecessários, diminuindo o desempenho da sua aplicação.
Conclusão Essa visão rápida do HTTP serve para mostrar de forma geral como os recursos das linguagens e frameworks web são implementados. Com este conhecimento, você obterá uma visão mais ampla e apurada sobre como sua aplicação funciona e quais recursos que ela utiliza, permitindo solucionar os desafios técnicos do dia-a-dia com muito mais facilidade e te dando segurança para poder dizer ao seu cliente ou gerente de projeto o que é e o que não é possível fazer em matéria de interfaces para sistemas web.
Marcelo Burgos Morgade Cortizo (morgade@fja.edu.br) é Mestre em Redes de Computadores pela Universidade Salvador e Bacharel em Ciência da Computação pela Universidade Salvador. Atualmente é professor de Técnicas e Linguagens de Programação e Sistemas Distribuídos do curso de Sistemas de Informação das Faculdades Jorge Amado (Salvador – BA) e Analista de Sistemas da Empresa Baiana de Águas e Saneamento (EMBASA).
38 3º Edição
wm03.indb 38
30/6/2005 17:32:49
Web
WebMobile 39
wm03.indb 39
30/6/2005 17:32:52
Desenvolvimento PalmOS Objetos Visuais no PocketStudio por Marcio Alexandroni / ClubePDA
N
a edição anterior, vimos uma introdução ao desenvolvimento PalmOS e ao PocketStudio, a ferramenta de desenvolvimento Palm mais usada no Brasil na atualidade. No exemplo que criamos, utilizamos vários objetos visuais do PalmOS. Neste artigo, vamos conhecer um pouco mais sobre o desenvolvimento de aplicações usando esses tipos de objetos, como Alerts, Buttons e Labels dentre outros.
Objetos visuais no PalmOS O conjunto de objetos visuais nativos do sistema operacional PalmOS não é extenso como no Windows. Existem poucos objetos visuais e muitas vezes operam com características diferentes do que estamos acostumados no desenvolvimento Windows. Por exemplo, o objeto Field (campo de edição) não tem algumas propriedades como máscara e cor e alguns eventos como o OnChange (existe um evento OnChange, mas não está relacionado ao mesmo modo de operação que existe nos objetos no Windows). Certamente muitos desenvolvedores sentirão falta de alguns objetos que estão acostumados a utilizar, como TreeView, ListView entre outros. No entanto, algumas propriedades em objetos ou mesmo a falta de alguns objetos visuais comumente utilizados em ambiente Windows podem não fazer falta no PalmOS. Aliás, acredito que esse seja um dos motivos pelo qual os desenvolvedores escolhem o Palm para escrever aplicações: a simplicidade. O uso de menos objetos visuais leva a uma menor complexidade e mais padronização, pois todas as aplicações terão o mesmo tipo de funcionamento, diferente do desenvolvimento Windows, onde muitos componentes de terceiros são acrescentados à interface visual dos sistemas. Nesse caso, a mudança de versão da ferramenta de desenvolvimento impacta diretamente no upgrade dos componentes de terceiros também. Simplicidade não implica em obsolescência ou em menor aplicabilidade da plataforma, pelo contrário, qualquer tipo de aplicação em tecnologia móvel pode ter como ferramenta os equipamentos Palm, desde aplicações básicas de coleta de dados até aplicações que acessam dados remotamente, on-line e em tempo real. Tudo isso pode ser feito com os equipamentos Palm, com muita qualidade! Por ser simples e utilizar o mesmo conjunto de objetos visuais em todas as versões do sistema operacional, o Palm permite compatibilidade entre versões, onde uma aplicação pode funcionar em qualquer Palm, desde os mais antigos, preto-e-branco, até os modelos mais novos, com processadores velozes e com milhares de cores. E para isso não é necessário nem mesmo fazer a
40 3º Edição
wm03.indb 40
30/6/2005 17:32:56
PDA’s
recompilação do executável, desde que não sejam utilizados recursos específicos de um equipamento, como a câmera que acompanha alguns modelos.
Objetos visuais nas units do PocketStudio No PocketStudio, cada formulário é gerado em uma unit .PAS individual e o código dos objetos visuais de cada formulário é gerado em sua própria unit. À medida que os objetos visuais são modificados no editor de formulários, o código de declaração do objeto visual é modificado automaticamente no código. Para comprovar isso, basta que você mude o nome de um objeto visual e confira sua declaração no código da unit, utilizando a tecla F12 e procurando a entrada resource, localizada no início da unit, logo após a cláusula implementation. A 1 mostra um exemplo dessa declaração. É possível também alterar as propriedades dos objetos visuais diretamente na sua declaração, no código da unit, e ela será imediatamente refletida no editor de formulários. Se for necessário, é possível também utilizar os recursos de copiar e colar do Windows para copiar objetos de uma unit para outra, diretamente no código da declaração do objeto. Ao editar visualmente o formulário, as declarações dos objetos são colocadas automaticamente pelo PocketStudio.
Respectivamente, os tipos de Alert são: Confirmação, Erro, Informação e Aviso. Na realidade, o funcionamento de todos os tipos de Alert mostrados anteriormente é exatamente igual, são caixas de diálogo que exibem uma mensagem, um ou mais botões e que retornam o índice do botão selecionado pelo usuário. A única diferença entre eles é o ícone que aparece na caixa de diálogo. Para declarar um Alert na aplicação, abra a unit onde você quer criar o Alert e no editor de código localize a entrada resource, que define o formulário e seus objetos dentro da unit (ver 1). Abra uma nova linha logo após o “END;” da declaração do formulário, selecione o Alert do tipo Information na barra de componentes do PocketStudio e clique com o mouse na nova linha. O protótipo da declaração do Alert será criado automaticamente (ver 2). A entrada const declara o nome do objeto Alert, que está como Alert1. O AutoID associado ao nome Alert1 se deve ao fato de que os nomes dos objetos são tratados internamente pelo PalmOS como números. Atribuímos o identificador AutoID ao objeto visual e o número interno será determinado automaticamente pelo compilador do PocketStudio; o desenvolvedor não necessita conhecer esse número para utilizar o objeto. Para mudar o nome do objeto Alert, localize o nome Alert1 na entrada const e troque para um nome que lhe agrade, por exemplo, AlertInforma. Logo abaixo da entrada const, é declarado o
Alerts Alerts são “caixas de diálogo” que o desenvolvedor utiliza para mostrar uma mensagem ao usuário. Existe um comando no PocketStudio chamado ShowMessage(‘Mensagem’) que apresenta uma caixa de diálogo padrão, mas é interessante que o desenvolvedor crie suas caixas de diálogo personalizadas. Veja na 2 um exemplo de uma caixa de diálogo. Para criar um novo Alert, localize na barra de componentes do PocketStudio a aba Alert. Há quatro objetos disponíveis que representam os tipos de Alerts que podem ser criados, mostrados na 3.
1. Unit onde será criado o alert resource FORM FrmPrincipal AT (0 0 160 160) NOFRAME BEGIN TITLE ‘Hello World’ BUTTON BtHello ‘Hello!’ AT (49 45 50 12) BUTTON BtSair ‘Sair’ AT (49 73 50 12) END;
2. Alert adicionado const Alert1 = AutoID;
resource
1. Declaração dos objetos visuais na unit do formulário.
ALERT Alert1 {HELP String1} {DEFAULTBUTTON 0} INFORMATION BEGIN TITLE ‘Title’ MESSAGE ‘Message’ BUTTONS ‘Button1’ ‘Button2’ ‘Button3’
2. Alert com dois botões solicitando confirmação.
3. Tipos de Alerts disponíveis no PalmOS.
END;
WebMobile 41
wm03.indb 41
30/6/2005 17:32:59
Alert na cláusula resource. Vamos examinar a estrutura da nova declaração: A entrada ALERT identifica a declaração de um novo objeto do tipo Alert. Note que para declarar um formulário, a entrada é FORM; O nome Alert1 é o nome do Alert, associado na entrada const. Como sugerimos anteriormente a mudança para o nome AlertInforma, o mesmo nome deve ser colocado na declaração do objeto; {HELP String1} é um comentário que o PocketStudio acrescentou exemplificando o uso de um help para o objeto Alert. Quando um help é utilizado, um símbolo “i” é apresentado no canto superior direito do Alert para que o usuário possa selecionar e obter uma descrição para a mensagem na caixa de diálogo. Esse texto deve ser configurado em um objeto do tipo String. Um objeto do tipo String pode ser criado da mesma maneira que um objeto Alert, através da aba Other da barra de componentes do PocketStudio, selecionando-se o objeto String e clicando-se sobre o editor de códigos. Observe que se você quiser usar um objeto do tipo String para o Alert, deve colocar sua declaração antes da declaração do Alert no código, isso é uma condição de qualquer compilador Pascal. Caso você vá utilizar o help para o Alert, remova o comentário da entrada Help e coloque o nome da String que você criou, por exemplo: HELP HelpAlertInforma. Você pode remover essa entrada comentada se não quiser usar o help; {DEFAULTBUTTON 0} indica o botão default que será acionado no Alert no caso da aplicação ser finalizada. Você pode remover essa entrada comentada se não quiser informar o botão default; INFORMATION indica o tipo do Alert. Essa cláusula é colocada automaticamente, dependendo do tipo de Alert que for escolhido na barra de componentes; BEGIN indica o início da definição do objeto; TITLE indica o título que será apresentado na caixa de diálogo. Como exemplo, mude a string Title para Informação; MESSAGE é a mensagem que será apresentada para o usuário na caixa de diálogo. Essa mensagem pode ser configurada de três maneiras: Um texto fixo: a string é informada diretamente. Nesse caso é preciso criar um objeto Alert para cada mensagem usada na aplicação, que precisaria ter muitos objetos Alert, aumentando o código desnecessariamente; Um item dinâmico: é possível colocar na propriedade MESSAGE apenas o símbolo “^1”, indicando que a mensagem será preenchida por uma função especial (vista a seguir) que apresenta o Alert; Combinação de texto fixo e itens dinâmicos: é possível também utilizar um texto fixo com itens dinâmicos na propriedade MESSAGE e usar a função especial de apresentação apenas para informar o texto para os itens dinâmicos. Até
três itens dinâmicos podem ser utilizados na mensagem, por exemplo: MESSAGE ‘Erro número: ^1. Mensagem: ^2’
Os dois itens dinâmicos “^1” e “^2” serão preenchidos via código na apresentação do Alert. BUTTONS são os botões que serão apresentados na caixa de diálogo. Apenas um botão é obrigatório. Se você quiser utilizar mais botões, eles serão colocados um ao lado do outro. No protótipo que o PocketStudio coloca automaticamente, três botões são definidos. Exemplo: BUTTONS ‘Button1’ ‘Button2’ ‘Button3’
Veja na 3 uma definição completa de um objeto Alert. Para apresentar um objeto Alert, utiliza-se a função FrmCustomAlert: FrmCustomAlert(AlertInforma, ‘Hello World!’, ‘’, ‘’);
FrmCustomAlert é uma função da API do PalmOS que apresenta o objeto visual Alert na tela do Palm. Note que a função começa com o prefixo Frm, indicando que faz parte do conjunto de funções da API dos formulários. FrmCustomAlert recebe quatro parâmetros: O nome do objeto Alert que será apresentado; A primeira string que será apresentada no lugar do item dinâmico ^1; A segunda string que será apresentada no lugar do item dinâmico ^2; A terceira string que será apresentada no lugar do item dinâmico ^3. Mesmo que um ou mais itens dinâmicos (^1, ^2 ou ^3) não sejam utilizados, é necessário informar o parâmetro na função como strings vazias (‘’). Se você configurar a mensagem do Alert
3. Alert const AlertInforma = AutoID;
resource ALERT AlertInforma INFORMATION BEGIN TITLE ‘Informação’ MESSAGE ‘^1’ BUTTONS ‘Ciente’ END;
42 3º Edição
wm03.indb 42
30/6/2005 17:33:00
PDA’s
com um texto fixo, pode usar a função FrmAlert ao invés de FrmCustomAlert:
O uso da rotina SetLeft para o objeto Button1 seria: PSButton.SetLeft(Button1, NovaCoordenada);
FrmAlert(AlertInforma);
O retorno das funções FrmAlert e FrmCustomAlert é o índice do botão selecionado pelo usuário, sendo que o primeiro botão tem índice zero. Assim, se você criar um Alert com dois botões, as funções retornarão “0” se o usuário selecionar o primeiro botão e “1” se o usuário selecionar o segundo botão. Poderíamos usar um código bem simples para testar a seleção do usuário, um comando IF da linguagem Pascal: if FrmCustomAlert(AlertConfirma, ‘Deseja realmente sair?’, ‘’, ‘’) = 0 then // Executa os comandos de fechamento da aplicação
PSLibrary A PSLibrary é uma das grandes novidades do PocketStudio introduzida na versão 1.1. É um conjunto de bibliotecas em forma de arquivos fonte .PAS que visa simplificar o trabalho do desenvolvedor, pois traz funções para manipulação de objetos visuais e bancos de dados. Por exemplo, para manipular objetos Label, existe a unit PSLabel.pas; para manipular objetos Field, existe a unit PSField.pas, para manipular bancos de dados, existe a unit PSDatabase.pas. Para utilizar a PSLibrary, você deve ter certeza que o diretório PocketStudio\Lib\PSL está configurado na lista Path to units no menu Tools>Environment Options>Compiler da IDE. Quando o PocketStudio é instalado, o diretório PSL é colocado automaticamente na lista, mas se por alguma razão você mudou a lista de diretórios, certifique-se que manteve o diretório PSL na lista. Para usar as funções das units da PSLibrary, basta adicionar “PSL” na cláusula uses da unit. Existem funções que estão presentes em todas as units que manipulam objetos visuais. Para utilizá-las, é preciso identificar primeiramente a unit do objeto visual que será manipulado, seguido do nome da rotina. Por exemplo, para utilizar a rotina SetLeft para uma Label, devemos usar:
Onde: PSButton é a unit da PSLibrary que manipula objetos visuais do tipo Button; SetLeft é o nome da procedure da unit PSButton que configura a posição X do objeto; Button1 é o nome do botão; NovaCoordenada é um número inteiro representando a nova posição X do objeto no formulário. Acostume-se com a seguinte regra para as rotinas da PSLibrary: A configuração dos objetos é feita utilizando-se procedures com o prefixo Set, portanto o nome da procedure será SetXXXX, onde XXXX é a propriedade que será configurada, com: SetLeft, SetTop etc; A recuperação do valor da propriedade é feita utilizando-se funções que possuem o próprio nome da propriedade, como: Left, Visible, Top etc. Dessa forma: PSLabel.SetLeft: configura a propriedade Left do objeto; PSLabel.Left: recupera o valor da propriedade Left do objeto; PSButton.SetTop: configura a propriedade Top do objeto; PSButton.Top: recupera o valor da propriedade Top do objeto; Listamos as principais rotinas a seguir: function Index(Nome_do_Objeto): UInt16;
Retorna o índice do objeto dentro do formulário. Lembre-se que índices começam em zero. function Ptr(Nome_do_Objeto): Pointer;
Retorna um ponteiro para o objeto. Ponteiros para objetos são usados em algumas chamadas da API do PalmOS, portanto a função Ptr será utilizada ocasionalmente quando fizermos uso dessas APIs.
PSLabel.SetLeft(Label1, NovaCoordenada); procedure Hide(Nome_do_Objeto);
Onde: PSLabel é a unit da PSLibrary que manipula objetos visuais do tipo Label; SetLeft é o nome da procedure da unit PSLabel que configura a posição X do objeto; Label1 é o nome da Label; NovaCoordenada é um número inteiro representando a nova posição X do objeto no formulário.
Torna o objeto invisível. procedure Show(Nome_do_Objeto);
Torna o objeto visível. procedure Draw(Nome_do_Objeto);
WebMobile 43
wm03.indb 43
30/6/2005 17:33:00
Redesenha o objeto no formulário. function Left(Nome_do_Objeto):UInt16;
Recupera a posição X do objeto no formulário. procedure SetLeft(Nome_do_Objeto);
Configura a posição X do objeto no formulário. function Top(Nome_do_Objeto):UInt16;
Recupera a posição Y do objeto no formulário. procedure SetTop(Nome_do_Objeto);
Configura a posição Y do objeto no formulário.
Labels O objeto Label é identificado pelo símbolo na barra de componentes do PocketStudio e seu funcionamento é praticamente idêntico ao objeto de mesmo nome no Windows, exceto por uma limitação na configuração da propriedade Caption via código, que explicaremos mais adiante. Para adicionar Labels em nossas aplicações, simplesmente selecione o objeto Label na barra de componentes do PocketStudio e clique no formulário. O objeto Label é mostrado na 4. As propriedades de um objeto Label são: Caption: o texto que será apresentado na Label; Font: a fonte do PalmOS que será aplicada para apresentar o texto da Label (falaremos sobre fontes no PalmOS em uma edição futura);
4. Objeto Label no formulário.
Left: a posição X da Label no formulário; Name: o nome do objeto Label; Top: a posição Y da Label no formulário; Visible: True ou False, indicando se a Label deve ou não ser exibida.
PSLabel As principais rotinas da unit PSLabel.pas para manipulação de objetos Label são (ler 1): PSLabel.SetCaption(Nome_da_Label, ‘NovoCaption’);
Configura a propriedade Caption. PSLabel.Caption(Nome_da_Label);
Recupera o valor da propriedade Caption.
1. Uso do label Quando usar a função PSLabel.SetCaption nunca use mais caracteres do que o definido na propriedade Caption em tempo de design. Se você precisar mudar o Caption da Label durante a execução da aplicação, e o novo texto tiver que ser maior do que o texto configurado inicialmente, altere sua aplicação colocando espaços à direita do texto configurado na propriedade Caption no Object Inspector. Essa é uma limitação do PalmOS e não do PocketStudio, documentada no PalmOS Reference Book, que descreve todas as funções do sistema operacional e está disponível para download em www.palmsource.com.
Botões Assim como as Labels, a funcionalidade dos botões é praticamente igual ao que já estamos acostumados a trabalhar na plataforma Windows. Na plataforma Palm, além do botão tradicional, há outros tipos de botões, com características especiais: Button : o botão tradicional; Repeat Button : é um botão que dispõe de um evento OnRepeat que gera eventos seqüenciais quando o usuário mantém a caneta pressionada sobre ele; Push Button : é um botão com bordas retangulares que mudam de estado para pressionado/normal, quando o usuário o seleciona; SelectorTrigger : é um botão com bordas pontilhadas que se expande/contrai dependendo do texto apresentado em seu Caption. Veja na 5 os botões disponíveis no PalmOS. As principais propriedades dos botões são: Bitmap: Configura um bitmap para ser mostrado no botão (mostraremos o uso de bitmaps no próximo artigo). Quando um bitmap é selecionado, a propriedade Caption fica inutilizada, pois o PalmOS não aceita Bitmap e Caption ao mesmo tempo no botão. Se você configurar a propriedade Bitmap, deverá obrigatoriamente
44 3º Edição
wm03.indb 44
30/6/2005 17:33:01
PDA’s
Simula a seleção de um botão via código. PSButton.SetDown(Button1);
Configura o botão em modo pressionado (PushButtons). if PSButton.Down(Button1) then ...
Verifica se o botão está pressionado (PushButtons). PSButton.SetEnabled(Button1);
5. Palm tem quatro tipos de botões.
configurar a propriedade SelectedBitmap também, mesmo que seja para selecionar o mesmo Bitmap, pois isso é obrigatório para a compilação. A imagem selecionada na propriedade SelectedBitmap é apresentada quando o botão é selecionado. Caption: o texto que aparecerá no botão; Enabled: indica se o botão está habilitado ou não; Font: a fonte do PalmOS que será aplicada para apresentar o texto do botão; Left: a posição X do objeto no formulário; Name: o nome do objeto botão; Top: a posição Y do objeto no formulário; Visible: indica se o botão deve ou não ser exibido; Width, Height: os tamanhos horizontal e vertical do botão, em pixels; Frame: propriedade disponível para os objetos Button e PSButton. Pode ser definido como: frSingle para uma linha simples em torno do botão; frNone para nenhum borda; frBold para uma borda mais espessa no botão.
PSButton As principais rotinas da unit PSButton.pas para manipulação de botões são: PSButton.SetCaption(ResourceID_do_Botão, ‘NovoCaption’);
Configura a propriedade Caption. Ao configurar o Caption de um botão com o conteúdo de uma variável string, tenha certeza que a variável é global; se utilizar uma variável string local, o Caption do botão mostrará caracteres inválidos quando a variável sair do escopo.
Torna um botão habilitado via código. if PSButton.Enabled(Button1) then ...
Verifica se o botão está habilitado. PSButton.SetFont(Button1, ID_da_Fonte);
Troca a fonte do botão via código. Os comandos vistos acima podem ser usados com os objetos Button, RepeatButton, PushButton e PopupTrigger pois todos fazem parte da família dos botões do PalmOS
Conclusões O PocketStudio suporta todo o conjunto de objetos visuais nativos do sistema operacional PalmOS. Nesta edição, iniciamos com alguns deles e nas próximas edições estaremos explicando o funcionamento de outros objetos visuais do PalmOS. Até a próxima!
Marcio Alexandroni (marcio@clubepda.com.br) é Analista de Sistemas com 15 anos em experiência em TI. Pós-graduado em Oracle pela FIAP/SP, atua no segmento de tecnologia móvel desde 1998 criando soluções para empresas e ferramentas para desenvolvedores pela Cialogica (www.cialogica.com), empresa que dirige. Pelo ClubePDA (www.clubepda.com.br) ministra cursos, palestras e consultorias na ferramenta PocketStudio para empresas e desenvolvedores.
PSButton.Caption(ResourceID_do_Botão);
Recupera o valor da propriedade Caption.
Faça o download no site: www.devmedia.com.br/webmobile/downloads
PSButton.Tap(Button1);
WebMobile 45
wm03.indb 45
30/6/2005 17:33:01
A
Persistência em aplicativos para dispositivos móveis com
J2ME
por Antonio Eloi de Sousa Júnior
capacidade de persistir dados ou armazenar informações é sem dúvida um dos recursos mais importantes em qualquer linguagem de programação. Armazenar dados para uma posterior recuperação é uma constante na maioria dos ambientes computacionais, seja para persistência simples de parâmetros de configurações de algum sistema ou persistência de informações digitadas pelo usuário para alimentar algum banco de dados. No que diz respeito à persistência em ambientes computacionais, o complicador é quando esse mesmo ambiente tem recursos de armazenamento restrito e, ainda, uma arquitetura de hardware e software bem diferente da encontrada em desktops ou grandes servidores, como é o caso dos dispositivos móveis. Essas diferenças podem ser observadas tanto do ponto de vista do usuário (ergonomia de hardware e software), quanto do ponto de vista do desenvolvedor (ferramentas de software, APIs e recursos). Os telefones celulares conseguiram alcançar uma popularidade quase tão grande quanto a observada na utilização de computadores pessoais a partir da década de 80. Mas, assim como todos os dispositivos móveis, eles também trazem consigo algumas dificuldades, como, problemas relacionados à ergonomia do teclado, uma interface visual simples porém limitada e a dependência de baterias que requerem recarga constante. Nesse artigo, serão apresentadas as APIs que tratam da persistência de dados disponíveis no J2ME. Inicialmente, alguns fundamentos serão abordados e a seguir o pacote javax.microedition. rms, responsável pelo gerenciamento de registros, será detalhado através de definições e do uso de exemplos. A descrição do funcionamento de uma classe, quatro interfaces e cinco exceções, é tudo do que trata esse artigo. Comprovando, por incrível que pareça, toda a simplicidade e eficiência do pacote RMS.
J2ME e perfil MIDP O Java 2 Micro Edition (J2ME) foi desenvolvido para contemplar toda a diversidade computacional existente nos dispositivos móveis. A tecnologia J2ME conseguiu abstrair conceitos e técnicas para homogeneizar o desenvolvimento em dispositivos móveis de forma completamente transparente. O perfil de informação de dispositivos móveis, conhecido como MIDP (Mobile Information Device Profile) surgiu como solução para diferenciar alguns dispositivos que apesar de possuirem características semelhantes, ainda assim são tecnologicamente diferentes. O perfil MIDP contempla os aparelhos celulares e é o responsável pela definição das APIs necessárias para a persistência de dados.
RMS O conjunto de classes responsáveis por armazenar e recuperar dados é conhecido como Record Management System (RMS) ou sistema de gerenciamento de registros. O RMS permite manter os dados persistentes entre várias chamadas de um MIDlet (aplicação baseada no MIDP). Segundo a especificação MIDP, deve haver, disponível no dispositivo, pelo menos 8 kbytes de memória não-
46 3º Edição
wm03.indb 46
30/6/2005 17:33:03
Java
volátil (ROM) para que os aplicativos persistam dados. Exemplos de memória não-volátil seriam ROM, flash e etc. Em teoria, todo o espaço livre na memória ROM, ou flash de um dispositivo móvel, estaria disponível aos aplicativos para persistirem seus dados. A unidade básica de dados mantida pelo RMS é conhecida como RecordStore ou repositório de registro (RR). Um RR pode ser comparado a uma tabela ou entidade no modelo relacional e é identificado por um nome de até 32 caracteres. Cada registro é composto por um identificador único e um array de bytes, onde os dados do registro serão armazenados. Um RR mantém em sua estrutura um conjunto de registros que podem ter tamanhos variáveis. Um MIDlet é um aplicativo executado em um dispositivo móvel. Para isso, ele precisa ser empacotado em um arquivo Java (JAR). Um MIDlet pode, ainda, ser empacotado junto com outros MIDlets em um mesmo arquivo JAR, formando um conjunto. Tanto um MIDlet quanto um conjunto de MIDlets, formam uma aplicação J2ME única e completa. Cada conjunto de MIDlets ou um MIDlet, pode criar e manter diversos RRs, podendo, inclusive, compartilhá-los entre si, com o detalhe de que os nomes atribuídos aos RRs precisam ser únicos. A versão 1.0 do MIDP não permitia o compartilhamento de RRs entre MIDlets empacotados em diferentes arquivos JAR. A versão 2.0 do MIDP corrigiu essa limitação, permitindo assim o compartilhamento de um RR por todas os MIDlets instalados no dispositivo. As APIs do RMS não fornecem recurso para travamento de registros. A implementação de um RR garante que a operação de persistência será realizada de forma indivisível e síncrona evitando eventuais inconsistências no caso de acessos múltiplos. Se for necessário que um MIDlet utilize múltiplas threads para acessar um RR, é necessário toda uma atenção para manter a consistência dos dados. Também, é responsabilidade da implementação no dispositivo fazer todo o possível para garantir a integridade e a consistência dos RRs durante operações normais ao seu uso como reinicialização, troca de baterias e etc. Durante a desinstalação de um MIDlet do dispositivo, os armazéns de dados pertencentes a ele são removidos automaticamente.
Classe RecordStore Qualquer operação de inserção, atualização e exclusão de registros em um RR provocam a atualização automática do seu número de versão e da data em que ocorreu a mudança. O número da versão de um RR pode servir como referencial, por exemplo, para algoritmos de replicação. É uma maneira interessante de detectar quantas vezes um RR foi modificado. Esses dois valores, o número da versão e a data da atualização, podem ser recuperados através do uso dos métodos getVersion() e getLastModified() respectivamente. A 1 lista mais algumas funcionalidades da classe RecordStore. A 1 contém um exemplo simples que cria um RR, preenche-o com dois registros e a seguir obtém e apresenta algumas informações sobre o RR. A 1 apresenta a mesma aplicação sendo executada no emulador. O método startApp() é chamado automaticamente pelo gerenciador de aplicativos do dispositivo quando um MIDlet mudar para o estado ativo. Um MIDlet fica ativo quando ele passa a ser executado no dispositivo. A mudança do estado ativo para inativo ou o contrário pode ocorrer, por exemplo, quando da alternância de aplicativos ou recursos em execução. A janela da aplicação é apresentada nesse método. Alguns exemplos desse artigo apresentam informações na janela do dispositivo e serão acrescentados ao código desse método como poderão perceber nas 1. Informações sobre um RecordStore. próximas seções.
Funcionalidade
Método Correspondente
Para fechar o RR.
rr.closeRecordStore()
Para excluir o RR inteiro.
RecordStore.deleteRecordStore(“produto”)
Para obter uma lista com todos os RRs presentes no conjunto de MIDlets.
String[] Arm = RecordStore.listRecordStores()
Para obter o nome do RR.
String Nome = rr.getName()
Para saber a data da última atualização no RR. O detalhe aqui é que essa data está no formato long e pode ser convertido para o tipo Data.
long UltimaMudanca = rr.getLastModified()
Para obter a versão do RR.
int vs = rr.getVersion()
Para obter o número de registros existentes no RR.
int nr = rr.getNumRecords()
Para obter o total de bytes ocupado pelo RR.
int tb = rr.getSize()
Para obter o espaço total ainda disponível para o RMS.
int getSizeAvailable()
1. Alguns métodos da classe RecordStore.
WebMobile 47
wm03.indb 47
30/6/2005 17:33:04
Um RR é implementado através da utilização de um objeto da classe RecordStore definido no pacote javax.microedition.rms.*. Para utilizá-lo, primeiro define-se um objeto do tipo RecordStore. Veja a linha 12 da 1. A seguir ele pode ser aberto, através do método estático OpenRecordStore() que possue dois parâmetros: o primeiro é o nome do RR e o segundo é um valor boleano que indica se o repositório será criado caso ele não exista. Observe a linha 26 da 1.
1. Exemplo de uso de métodos para obter informações sobre o RR
Manipulação de registros Como já foi dito, cada registro em um RR é formado por dois campos: um identificador único, gerado automaticamente pelo RMS que representa o papel da chave principal, e um array de bytes que contém os dados. Veja um exemplo de inserção de registros na 1, linhas 29 a 34. Somente é possível substituir um registro por um novo array de bytes. Nesse exemplo, por motivo
50: 51:
public void startApp()
1: /* ExemploRMS.java
52:
{
2: */
53:
try
3: import javax.microedition.rms.*;
54:
{
4: import javax.microedition.midlet.*;
55:
if (RecordStore.listRecordStores() != null) // Se tem um repositório aberto então
5: import javax.microedition.lcdui.*; 6:
56:
7: public class ExemploRMS extends MIDlet implements
57:
{
// obtém as informações abaixo. String[] RRs = RecordStore.listRecordStores();
CommandListener
58:
lsLista.append(“RR:” + /*RRs[0]*/rr.getName(), null);
private List lsLista; // Lista de Informações
59:
lsLista.append(“Versao: “ + rr.getVersion(), null);
60:
lsLista.append(“Nr.de Registros: “
private RecordStore rr = null;
61:
lsLista.append(“Tamanho: “ + rr.getSize()
14:
public ExemploRMS()
62:
lsLista.append(“Espaço Disp.: “
15:
{
//RRs[0]=rr.getName()
8: { 9: 10:
private Command cmSair;
+ rr.getNumRecords(), null);
11: 12:
+ “ bytes”, null);
13:
+ rr.getSizeAvailable() + “ bytes”, null);
16:
cmSair = new Command(“Sair”, Command.EXIT, 1);
63:
}
17:
lsLista = new List(“Informações RR”, List.IMPLICIT);
64:
}
18:
lsLista.addCommand(cmSair);
65:
catch (Exception e) // Tratamento simples de exceções do rms
// Janela com apenas um comando...Sair 19:
lsLista.setCommandListener(this); // Registrando um receptor para o evento Sair
66:
{
67:
System.err.println(“Erro: “ + e.toString());
68:
20: 21:
String str;
69:
22:
byte[] Registro;
70:
23:
try
71:
24:
{
} Display.getDisplay(this).setCurrent(lsLista); }
72:
public void pauseApp()
25:
// Cria o armazém de registros
73:
{
26:
rr = RecordStore.openRecordStore(“produto”, true);
74:
}
75:
27: 28:
// String: #1 registro
76:
public void destroyApp( boolean unconditional )
29:
str = “LONA DE FREIO”;
77:
{
30:
// Converte string em bytes
78:
try
31:
Registro = str.getBytes();
79:
{
32:
// Adiciona o registro a partir da posiçao inicial 0
80:
rr.closeRecordStore(); // Fecha o repositório de registros
// do array de bytes 33:
// com tamanho igual ao seu tamanho total (length)
81:
34:
rr.addRecord(Registro, 0, Registro.length);
82:
35:
// Apaga o repositório de registro RecordStore.deleteRecordStore(“produto”);
83:
}
36:
// String: #2 registro
84:
catch (Exception e)
37:
str = “AMORTECEDOR”;
85:
{
38:
// Converte string em bytes
86:
39:
Registro = str.getBytes();
87:
}
40:
// Adiciona o registro a partir da posição inicial 0
88:
// Notifica o gerenciador de aplicativos que a MIDlet
System.err.println(“Erro: “ + e.toString());
// pode ser desligada
// do array de bytes 41: 42:
// com tamanho igual ao seu tamanho total (length)
89:
rr.addRecord(Registro, 0, Registro.length);
90:
notifyDestroyed(); }
91:
43: 44:
}
92:
public void commandAction(Command cm, Displayable dp)
45:
catch (Exception e)
93:
{
// Tratamento simples de exceções do rms 46:
System.err.println(“Erro: “ + e.toString());
48: 49:
if (cm == cmSair) // Botão sair pressionado!
95:
{
47:
94:
}
96:
destroyApp(true); }
97:}
}
48 3º Edição
wm03.indb 48
30/6/2005 17:33:05
Java
de simplificação, estamos armazenando apenas uma informação ou campo em cada registro. O objeto String contém a informação a ser gravada, que é convertida em bytes através do método getBytes() (linha 31 da 1) para ser armazenado no array de bytes já devidamente declarado. O método addRecord() é responsável por armazenar o conteúdo do array de bytes no RR. Nesse caso, estamos armazenando todo o conteúdo do array (linha 34 da 1). O método addRecord() requer, além do array de bytes que representa o registro, mais dois parâmetros que identificam o byte inicial a partir do qual será feita a leitura do array e o número de bytes a serem lidos, considerando a posição desse byte inicial. No nosso exemplo, é gravado o array inteiro (da posição zero ao tamanho total do array). Para modificar um determinado registro, primeiro é necessário obter o seu identificador único. Uma forma de obter esse identificador é através do método addRecord() que retorna o identificador do registro que ele acabou de inserir. Para exemplificar a alteração de um registro, modifique a linha 34 da 1 para: 34:
Navegação simples unidirecional Um outro recurso básico e indispensável é o de navegar pelos registros de um RR. Primeiro, vamos experimentar uma técnica muito simples utilizando um laço for e fazendo uso de alguns métodos já apresentados nas 1 e 2. Adicione à 1 o método apresentado na 3. Agora, vá até o método startApp() e localize a linha onde é apresentado o espaço total disponível para o RMS (uso do método getSizeAvailable()) e insira uma linha fazendo uma chamada ao método recém implementado NavegacaoSimples(). Observe a técnica de navegação utilizada. Um laço simples e o uso do método getNumRecords() (linha 10 da 3) tornam possível uma varredura unidirecional no RR. Veremos mais adiante como navegar de forma bidirecional no RR utilizando a interface RecordEnumeration. O processo de obter um registro anteriormente armazenado também é uma tarefa simples. Para isso, pode-se utilizar o método getRecords() (linha 13 da 3).
int i = rr.addRecord(Registro, 0, Registro.length);
3. Exemplo de uso de navegação simples de registros em um RR
Agora insira o código da 2 a partir da linha 43 da 1. O exemplo da 2 altera o primeiro registro inserido no RR produto. A 2 lista os métodos responsáveis pelas operações executadas nos registros de um RR. Ao utilizar a classe RecordStore, todos os seus métodos que realizam acesso direto ao RR levantam exceções. Por esse motivo, é necessário o uso de um tratador de exceção para operações no RMS.
2. Exemplo de uso de alteração de um registro já existente no RR
1:public void NavegacaoSimples() 2:{ 3:
byte[] Registro;
4:
int tam;
5: 6:
lsLista.append(“ “, null);
7:
lsLista.append(“Navegação Simples”, null);
8:
try
9:
{
10:
for (int i = 1; i <= rr.getNumRecords(); i++)
11:
{
12:
Registro = new byte[rr.getRecordSize(i)];
13:
tam = rr.getRecord(i, Registro, 0);
14:
lsLista.append(“Registro “ + i + “: “ + new String(Registro, 0, tam), null);
// Preparando novo registro str = “PARAFUSO”;
15:
}
16:
}
17:
catch (Exception e)
// Modificando registro número i atribuindo-lhe o novo
18:
{
valor
19:
rr.setRecord(i, Registro, 0, Registro.length);
20:
Registro = str.getBytes();
System.err.println(“Erro: “ + e.toString()); }
21:}
Funcionalidade
Método Correspondente
Incluir um registro
int addRecord(byte[] dado, int ByteIni, int NumBytes)
Eliminar um registro
void deleteRecord(int NumReg)
Modificar um registro
Void setRecord(int NumReg, byte[] NovoDado, int ByteIni, int NumBytes)
Obter o tamanho de um registro em bytes
int getRecordSize(int NumReg)
Obtêm um array de bytes a partir do número do registro
byte[] getRecord(int NumReg)
Obtêm o registro para o array de bytes informado como parâmetro
int getRecord(int NumReg, byte[] dado, int ByteIni)
2. Métodos da classe RecordStore que manipulam registros.
WebMobile 49
wm03.indb 49
30/6/2005 17:33:06
Em um primeiro momento, instanciamos um array de byte devidamente dimensionado para comportar o registro inteiro. Atente ao uso do método getRecordSize() (linha 12 da 3) para pegar o tamanho do registro e poder criar o vetor com o tamanho correto. Em seguida, para ter acesso à informação desejada, basta converter o vetor para o tipo mais apropriado, no nosso exemplo, o tipo String (linha 14 da 3).
Navegação avançada com um enumerador O RMS possui uma interface responsável pela definição de um enumerador capaz de efetuar movimentação bidirecional no RR. O RecordEnumeration consegue manter uma sequência lógica dos identificadores dos registros, de forma a proporcionar operações de varredura simplificada. Também, como veremos mais tarde, essa interação pode ocorrer não somente em todos os registros, mas opcionalmente em alguns registros que satisfaçam a um filtro previamente definido ou até mesmo em uma determinada ordem. Observe na 4 a implementação de um método para exemplificar o uso do RecordEnumeration como opção substituta à navegação apresentada na 3. O código da 4 ficou ainda menor se comparado com o de mesma funcionalidade da 3. É bastante elementar o teste com o método hasNextElement() (linha 8 da 4) para verificar a existência de mais elementos à frente e o método nextRecord() (linha 9 da 4) que retorna um array de bytes do próximo registro definido no enumerador. Assim, basta convertê-lo para o tipo String para apresentá-lo. Aqui estamos utilizando mais um método da classe RecordStore, o enumerateRecords() (linha 7 da 4) que retorna um enumerador para efetuar as operações de varredura no RR.
4. Uso do RecordEnumeration 1:public void NavegacaoAvancada() 2:{ 3:
lsLista.append(“ “, null);
4:
lsLista.append(“Navegação Avançada”, null);
5:
try
6:
{
7:
RecordEnumeration re = rr.enumerateRecords(null,
8:
while (re.hasNextElement())
null, false);
9:
lsLista.append(“Registro: “ + new String( re.nextRecord()), null);
10:
}
11:
catch (Exception e)
12:
{
13: 14:
System.err.println(“Erro: “ + e.toString()); }
15:}
Vá até a linha onde é feita uma chamada ao método NavegacaoSimples(), e após essa, insira uma nova linha chamando o método NavegacaoAvancada(). Veja a 2 com a aplicação chamando os dois métodos de navegação. Navegar em um RR utilizando um laço for ( 3) parece ser bem mais intuitivo e, também, proporciona uma maior economia de recursos, já que não necessita de alocação de objetos adicionais para a navegação propriamente dita. Porém, a abordagem com a interface Enumeration ( 4) consegue ser mais eficiente na medida em que ocorre uma redução substancial no código e proporciona, ainda, dois recursos indispensáveis no tratamento de dados, filtro e ordenação. Tais funcionalidades ajudam a reforçar o uso da interface Enumeration como melhor alternativa para navegar pelos registros de um RR. É possível configurar, opcionalmente, para o enumerador um filtro e um critério de ordenação dos dados, definindo respectivamente o primeiro e o segundo parâmetro de enumerateRecords(). Como ainda não estamos fazendo uso de nenhum dos dois recursos foi passado nulo nos dois primeiros parâmetros. O último parâmetro de enumerateRecords() identifica se o enumerador será atualizado automaticamente após a realização de alguma operação de atualização de registros no RR. Contudo, o uso dessa funcionalidade pode não ser interessante devido a problemas de performance, já que nesse caso a atualização seria automática e imediata. É aconselhável deixar sempre o padrão para não fazer a atualização automática do enumerador, e efetuar essa operação manualmente quando realmente for conveniente. Isso pode ser feito com utilização do método rebuild() da interface RecordEnumeration. Um detalhe importante é que enumerateRecords pode levantar uma exceção caso o RR não esteja aberto (RecordStoreNotO penException) e por esse motivo não se deve esquecer de efetuar o tratamento adequado de seus possíveis erros. No nosso caso, todas as operações no RR já estão envoltas em um bloco try...except, apesar de seus erros não estarem sendo tratados adequadamente. Para fazer a navegação oposta, do último ao primeiro registro, bastaria substituir as linhas 7, 8 e 9 da 2. Exemplo do uso dos métodos de navegação. 4 pelo código da 5.
50 3º Edição
wm03.indb 50
30/6/2005 17:33:06
Java
Observe que a única mudança aqui é o uso dos métodos hasPreviousElement() e previousRecord(). A 3 lista mais algumas funcionalidades da interface RecordEnumeration.
Definindo um filtro de pesquisa em um enumerador É comum a necessidade de efetuar a pesquisa de uma determinada informação em um banco de dados, ou mesmo a definição de um critério para um filtro em que apenas registros que satisfaçam a uma condição sejam efetivamente apresentados. Para configurar um filtro em um enumerador, é necessário implementar a interface RecordFilter, definindo o critério de filtro no seu método matches(). Veja o exemplo na 6. A classe EfetuaPesquisa é a classe responsável por pesquisar um determinado registro. Para isso, o seu construtor recebe como parâmetro o valor a ser pesquisado no RR. O método matches() é o responsável por testar esse mesmo valor com um array de bytes que ele recebe como parâmetro e, antes de mais nada, faz algumas conversões para garantir a homogeneidade de tipos. Observe que é verificado se o valor a ser pesquisado não é nulo e se o mesmo está dentro da String convertida por matches() (linha 15 da 6). No caso da utilização da classe EfetuaPesquisa em um enumerador, o método matches() é chamado a cada avanço de registro, sempre testando a condição, trazendo apenas os registros que
5. Navegação inversa de registros 1:RecordEnumeration re = rr.enumerateRecords(null, null, false);
conseguiram satisfazê-la. Para utilizar a classe EfetuaPesquisa, observe na 7 a definição do método Pesquisa(). Agora localize a linha onde o método NavegacaoAvancada() é utilizado e insira, após esta, as duas linhas abaixo para testar o método Pesquisa(). Pesquisa(“AMORTECEDOR”); Pesquisa(“PNEU”);
6. Classe para filtrar registros 1:class EfetuaPesquisa implements RecordFilter 2:{ 3:
// Receberá do construtor valor a ser pesquisado 4: 5:
public EfetuaPesquisa(String palavra)
6:
{ // Convertendo para minúscula parâmetro do construtor
7: }
9: 10:
/* matches testará se o array de bytes devidamente * convertido em string é
11:
equivalente ao valor a ser pesquisado */
12:
public boolean matches(byte[] campo)
13:
{ // Convertendo tudo para minúscula
14:
String str = new String(campo).toLowerCase();
15:
if (palavra != null && str.indexOf(palavra) != -1)
16:
return true;
17: 18:
3:
19:
previousRecord()), null);
this.palavra = palavra.toLowerCase();
8:
2:while (re.hasPreviousElement()) lsLista.append(“Registro : “ + new String(re.
private String palavra = null;
else return false; }
20:}
Funcionalidade
Método Correspondente
Retorna true se existem mais elementos na direção à frente.
boolean hasNextElement()
Retorna true se existem mais elementos para trás.
boolean hasPreviousElement()
Retorna um array de bytes representando o próximo registro na enumeração.
byte[] nextRecord()
Retorna o identificador do próximo registro na enumeração.
int nextRecordId()
Retorna um array de bytes representando o registro anterior na enumeração.
byte[] previousRecord()
Retorna o identificador do próximo registro na enumeração.
int previousRecordId()
Retorna o número de registros na enumeração.
int numRecords()
Força a atualização do enumerador para refletir as mudanças no RR.
void rebuild()
Posiciona o índice do enumerador no mesmo local quando da sua criação.
void reset()
Libera todos os recursos utilizados pelo enumerador.
void destroy()
Configura a atualização automática do enumerador, no caso de inserções, alterações ou exclusões no RR. Retorna true se o enumerador for atualizado automaticamente quando da ocorrência de mudanças no RR.
void keepUpdated(boolean keepUpdated) boolean iskeepUpdated()
3. Métodos da interface RecordEnumeration.
WebMobile 51
wm03.indb 51
30/6/2005 17:33:08
7. Método responsável pela pesquisa no RR, utilizando a classe EfetuaPesquisa 1:public void Pesquisa(String palavra) 2:{ 3:
lsLista.append(“ “, null);
4:
lsLista.append(“Pesquisa”, null);
5:
try
6:
{
7:
EfetuaPesquisa pesquisa = new EfetuaPesquisa(palavra);
8:
RecordEnumeration re = rr.enumerateRecords(pesquisa, null, false);
9:
Veja na 9 a definição do método NavegacaoOrdenada(), responsável pela ordenação de registros, utilizando a classe Ordenar em um enumerador. No momento em que o enumerador criar o seu índice interno do RR, para permitir a navegação pelos registros, ele usará o método compare() da classe Ordenar para determinar a ordenação de cada um dos registros. Localize a linha onde o método Pesquisa() é utilizado pela última vez e insira, após esta, uma chamada ao méto-
Valor
Descrição
EQUIVALENT
Os registros são equivalentes. O método compare() estabelece que o primeiro
FOLLOWS
parâmetro virá após o segundo.
// Se pesquisa bem sucedida (achou pelo menos um
10:
O método compare() estabelece que o primeiro
PRECEDES
// registro)
parâmetro precede o segundo.
if (re.numRecords() > 0)
11:
lsLista.append(“Encontrado : “ + new String(re.
4. Campos da interface RecordComparator.
nextRecord()), null); 12:
else
13:
9. Método NavegacaoOrdenada()
lsLista.append(“Não encontrado!”, null);
14:
}
1:public void NavegacaoOrdenada()
15:
catch (Exception e)
2:{
16:
{
17:
System.err.println(“Erro: “ + e.toString());
18:
}
19:
}
20:}
3:
lsLista.append(“ “, null);
4:
lsLista.append(“Navegacao Ordenada”, null);
5:
try
6:
{
7:
Ordenar ordem = new Ordenar();
8:
RecordEnumeration re = rr.enumerateRecords(
9:
while (re.hasNextElement())
null, ordem, false); 10:
Veja a 3 com a aplicação chamando o método Pesquisa() para os casos em que ela é bem sucedida e mal sucedida.
3. Exemplo do uso do método Pesquisa().
lsLista.append(“Registro: “ + new String( re.nextRecord()), null);
11:
}
12:
catch (Exception e)
13:
{
Ordenando registros
14:
Para permitir que um enumerador retorne seus registros ordenados, basta implementar a interface RecordComparator e definir o critério de ordenação em seu método compare(). Em seguida, utilize o objeto criado a partir de tal implementação como parâmetro na criação do enumerador. Veja na 8 o exemplo de uma classe que implementa a interface RecordComparator. A interface RecordComparator faz uso dos campos na 4 para determinar a ordem de cada par de registros avaliado pelo método compare() (linha 3 da 8).
15:
System.err.println(“Erro: “ + e.toString()); }
16:}
8. Classe para ordenar registros 1:class Ordenar implements RecordComparator 2:{ 3:
public int compare(byte[] par1, byte[] par2)
4:
{
5:
String str1 = new String(par1), str2 = new
String(par2); 6: 7:
int result = str1.compareTo(str2);
8:
if (result == 0)
9:
return RecordComparator.EQUIVALENT;
10:
else if (result < 0)
11:
return RecordComparator.PRECEDES;
12:
else
13: 14:
return RecordComparator.FOLLOWS; }
15:}
52 3º Edição
wm03.indb 52
30/6/2005 17:33:08
Java
WebMobile 53
wm03.indb 53
30/6/2005 17:33:15
do NavegacaoOrdenada(). 4 com a apliVeja a cação exemplificado o uso desse método. Também é possível fazer uso de um critério de filtro e ordenação ao mesmo tempo em um enumerador. Veja na 10 como ficaria o código.
Tratamento de eventos no RMS O RMS permite a utilização de um receptor de eventos para detectar quaisquer mudanças ocorridas em um RR. A interface RecordListener oferece a estrutura necessária para o mapeamento 4. Exemplo do uso do método NavegacaoOrdenada(). de ações como inclusão, atualização e exclusão de registros permitindo com isso, tomar decisões e efetuar outras operações automaticamente a partir de tais ações. Para utilizá-la, basta implementar uma classe a partir da interface RecordListener e registrá-la no RR, utilizando o método addRecordListener(). Esse recurso pode ser útil quando houver a necessidade de atualizar automaticamente um RR secundário a partir de mudanças em algum outro RR, por exemplo. Na verdade, até mesmo uma conexão HTTP com um servidor pode ser implementada para ser disparada como uma ação ao evento, por exemplo, de inclusão de algum novo registro for realizada. A interface RecordListener possue apenas três métodos: recordAdded(), recordChanged() e recordDeleted(), capazes de detectar ações de inclusão, atualização e exclusão de registros respectivamente. O detalhe é que esses eventos são disparados somente após a ação acontecer. Os três métodos recebem, igualmente, dois parâmetros. O primeiro identifica o RR que disparou o evento e o
segundo representa o identificador do registro motivo da mudança. Uma implementação da interface RecordListener pode ser observada na 11. Para fazer valer o receptor de eventos EventoTabelaPedido, basta registrá-lo no RR, utilizando o método addRecordListener(), conforme é demonstrado na 12. O receptor ainda pode ser removido utilizando o método removeRecordListener().
Tratamento de erros A maioria das operações em um RR está preparada para levantar exceções no caso da ocorrência de erros, portanto, é necessário está preparado para tratá-los. O RMS oferece cinco exceções para representar toda uma possibilidade de problemas que possam vir a ocorrer em operações de persistência. É desejável sempre capturar e tratar da melhor maneira possível algum eventual erro. A 5 apresenta essas exceções juntamente com a descrição de cada uma delas.
Conclusão O perfil MIDP, através do RMS, conseguiu fornecer um valoroso conjunto de APIs para persistência de dados. Todas as operações tipicamente necessárias em um ambiente persistente foram lembradas. O interessante de tudo isso é que todos esses recursos estão disponíveis em dispositivos computacionalmente restritos.
11. Implementando a interface RecordListener 1:class EventoTabelaPedido implements RecordListener 2:{ 3:public void recordAdded(RecordStore RR, int NumRec) 4:{ 5:/* Atualiza Saldo Tabela de Itens */ 6:} 7: 8:public void recordChanged(RecordStore RR, int NumRec) 9:{ 10:/* Atualiza Saldo Tabela de Itens */ 11:} 12: 13:public void recordDeleted(RecordStore RR, int NumRec) 14:{ 15:/* Atualiza Saldo Tabela de Itens */ 16:} 17:}
10. Filtrando e ordenando um enumerador 12. Adicionando um receptor de eventos
... EfetuaPesquisa pesquisa = new EfetuaPesquisa(“AMORTECEDOR”); Ordenar ordem = new Ordenar(); RecordEnumeration re = rr.enumerateRecords( pesquisa, ordem, false); ...
... // Cria o repositório de registros rr = RecordStore.openRecordStore(“produto”, true); rr.addRecordListener( new EventoTabelaPedido()); // Adiciona o receptor de eventos ...
54 3º Edição
wm03.indb 54
30/6/2005 17:33:18
Java
RecordStoreException
Indica um erro geral no RR.
RecordStoreFullException
Indica que o RR atingiu a sua capacidade máxima de armazenamento.
RecordStoreNotFoundException
O RR não existe ou é inválido.
RecordStoreNotOpenException
Tentativa de executar alguma operação que necessita que o RR esteja aberto quando o mesmo está fechado.
InvalidRecordIDException
Foi feita uma referência a um identificador de registro inválido ou inexistente no RR.
5. Exceções no RMS.
Um outro ponto importante é que não foi tratado nesse artigo a manipulação de registros com fluxo de dados. Apenas a gravação e leitura de texto puro foram documentadas. O uso de fluxo de dados permite armazenar e ler tipos de dados Java direto no array de bytes. Mas, ainda assim é possível e absolutamente funcional utilizar apenas a técnica apresentada nesse artigo para gerenciar Antonio Eloi de Sousa Júnior (eloijr@uol.com.br) está cursando o último ano do curso de Sistemas de Informação na Faculdade de Imperatriz (FACIMP). Trabalha a mais de 10 anos desenvolvendo softwares para desktop nas áreas de automação comercial, industrial e engenharia florestal. Há cerca de um ano descobriu J2ME como uma grande solução para computação móvel.
Primeiro livro lançado na língua portuguesa sobre programação J2ME. Além disso, é um excelente material sobre o assunto. Core J2ME: John W. Muchow Portal brasileiro especializado em desenvolvimento para dispositivos móveis. http://www.microd.info MIDP APIs for Wireless Applications – Apostila introdutória sobre MIDP. http://java.sun.com/products/midp/midp-wirelessapps-wp.pdf MIDP 2.0 Introduction – Documento interessante sobre os novos recursos do MIDP 2.0. http://www.forum.nokia.com/main/1,6566,040,00. html?fsrParam=2-3-/main.html&fileID=6516
Faça o download no site: www.devmedia.com.br/webmobile/downloads
WebMobile 55
wm03.indb 55
30/6/2005 17:33:19
C
Desenvolvendo uma aplicação
Smart Client Fábio Câmara
omo utilizar toda a riqueza de soluções baseadas em windows forms e ao mesmo tempo desfrutar das virtudes das soluções web? O inesperado sucesso de aplicativos baseados em web browser tornou necessário aos desenvolvedores o exercício da inteligência criativa para superar todas as limitações impostas pelos navegadores web com o objetivo de aproximar a produtividade e usabilidade de sistemas web forms em algo próximo ao que temos nas soluções windows forms. Particularmente, defino estes últimos anos como uma fase histórica de desperdícios de horas. Esta conclusão é baseada no fato de que o desenvolvedor web não implementa apenas os requisitos de negócios, mas também codifica inúmeras funcionalidades de interface através de linguagens como Java Script e VB Script ou preocupa-se com o desenvolvimento de componentes ActiveX para finalidades de prover riqueza a interface com o usuário. Considero que desenvolver projetos para plataforma web requer um maior número de horas e por isso é consideravelmente mais dispendioso financeiramente em comparação há projetos desenvolvidos com windows forms. Entretanto, na contra mão destes valores, as soluções baseadas em web rapidamente desbancaram as demais arquiteturas existentes pelo simples fato, na minha leitura, de resolver questões de distribuição e implantação. Neste artigo apresentaremos uma comparação entre as arquiteturas comentadas, justificaremos o uso do smart client como opção para lidar com este problema e apresentaremos um exemplo passo a passo de um aplicativo na arquitetura smart client.
O melhor dos dois mundos A popularidade das soluções web browser é tão grande que provavelmente existam hoje um grande número de programadores que não sabem desenvolver aplicativos em outras arquiteturas de software. Para a compreensão plena das vantagens desta nova solução, vamos exibir um comparativo entre sistemas desenvolvidos para web browser, que podemos chamar de “Thin Client”, e sistemas construídos na arquitetura Cliente-Servidor, que podemos chamar de “Fat Client”. É importante explicar que as definições Thin e Fat Client utilizadas para ilustrar nossa compreensão do assunto não correspondem aos respectivos conceitos plenamente, ou seja, thin client (ler 1) não é somente web browser e muito menos fat client (ler 2) é somente Cliente-Servidor, mas a utilizaremos ao longo do artigo para facilitar o entendimento.
1. Thin Client Pouco ou nenhum gerenciamento de memória na estação; Facilidade de implantação e distribuição; Dependência da rede, pouco ou nenhum recurso desconectado; Interface pobre para o usuário; Complexo para o desenvolvimento; Fácil para gerenciar mudanças (praticamente substituir arquivos).
56 3º Edição
wm03.indb 56
30/6/2005 17:33:22
Smart Client
2. Fat Client Total gerenciamento de memória na estação; Difícil de implantar e distribuir; Extremamente produtivo para o desenvolvedor; Interface rica para o usuário, satisfação das questões de gerência de usabilidade;
Responsável por seu próprio comportamento na estação de trabalho;
“DLL Hell” - problemas com registros referentes a versões de DLL instaladas localmente.
Antes de proporem uma solução com o melhor dos dois mundos, iniciou-se um movimento histórico para resgatar a qualidade da interação com o usuário. Este resgate foi conceituado e denominado “rich client”. Rich client, ou cliente rico, é uma solução que possui interfaces que proporcionam autonomia para realizar atividades na estação de trabalho sem precisar diretamente de recursos do servidor de aplicações como, memória e processador, e uma orientação em atender questões relacionadas à navegabilidade e usabilidade proporcionando uma rica experiência com o usuário. Diferentemente dos sistemas web browser, uma aplicação rich client pode realizar várias operações desconectadas do servidor e em uma oportunidade planejada atualizar estas operações, se for o caso. Apesar da semelhança com os conceitos “fat client”, duas questões caracterizam indiscutivelmente a diferença de rich client e fat client: orientação à facilidade de distribuição e a concepção arquitetônica de trabalhar “desconectado” de servidores; somente proceder à conexão quando extremamente necessário e da forma mais econômica possível. Explicaremos a seguir a proposta que une o desenvolvimento de um “cliente rico”, com as vantagens das soluções baseadas em web, o smart client.
Smart Client Utilizando como base os conceitos apresentados nas 1 e 2, um smart client: Provê facilidade de implantação e distribuição; É responsável por seu próprio comportamento na estação de trabalho; É extremamente produtivo para o desenvolvedor; Possui interface rica para o usuário, satisfazendo as questões de gerência de usabilidade; É fácil para gerenciar mudanças (praticamente substituir arquivos). A definição acadêmica de smart client é ser uma arquitetura de software para construção de sistemas que possibilitem: Proporcionar todas as vantagens para usuários de Pocket
PC, Smartphone, Tablet PC, Laptops e Desktops; Consumir web services; Suporte aos conceitos conectado e desconectado; Distribuição e implantação fácil, similar ao modelo web. A melhor forma de visualizar esta nova proposta é a implementação prática. Propomos um exemplo muito simples a seguir para este objetivo. Certamente em algum livro ou curso que já participou você encontrou um exemplo que dispara através de um botão a mensagem “Hello World!”. Este exemplo tornou-se um épico entre as técnicas de aprendizagem. Nosso objetivo é surpreender você demonstrando o quanto é simples construir um aplicativo smart client. Considerando que você já saiba criar um projeto windows forms novo com o Visual Studio .NET, coloque um botão no centro do seu formulário como sugere a 1. No ambiente de desenvolvimento, clique duas vezes com o mouse no botão inserido para disparar o evento click do objeto. O código a ser inserido para exibição de uma mensagem “Hello World!” durante a execução do sistema é demonstrado na 1. Até o momento, nada diferente, nada novo! Afinal, por que é smart client? É justamente este o sentido de “smart”, ou seja, esperto. Você não precisa fazer nada diferente, o .NET Framework faz por você. A diferença está apenas no deployment (distribuição). Acompanhe os seguintes passos para proceder a distribuição de nosso windows forms via HTTP. Primeiramente, clique no menu build build solution. Coloque o executável gerado na pasta Inetpub \ wwwroot (pasta utilizada e gerenciada pelo IIS para publicações web) ou alguma outra pasta que possua compartilhamento web (web sharing). Inicie seu browser e digite o endereço URL conforme a 2. Surpreendentemente o aplicativo foi iniciado a partir do browser, em outras palavras, você não se preocupou com a distribuição e implantação. Simplesmente passa-se uma URL para seu cliente. Para entender o que aconteceu, temos que recapitular o que é MSIL e JIT. MSIL ou Intermediate Language, é a linguagem resultante dos inúmeros compiladores .NET que é transformada em linguagem de máquina pelo JIT (compilador Just In Time que é distribuído com o .NET Framework). Se você é um leitor atento, deve ter concluído corretamente que para utilizar aplicativos baseados em Smart Client, a estação de trabalho deve ter previamente instalado o .NET Framework (ler 3). Este é o preço para utilizar esta arqui1. Formulário proposto para nosso exemplo tetura de software. smart client.
WebMobile 57
wm03.indb 57
30/6/2005 17:33:33
1. Código .NET com o evento de clique do botão private void button1_Click(object sender, System.EventArgs e) { MessageBox.Show(“Hello World!”); }
2. Executando aplicativos smart client.
3. Instalação do .NET Framework para a criação de smart client O .NET Framework Redistribute é gratuito e precisa ser instalado em todas as estações e servidores que executarão dll ou executáveis construídos com compiladores .NET. Você encontra o .NET Framework Redistribute no atalho de menu Windows Update encontrado nos browsers Internet Explorer versão 6.0 ou superior, ou na seguinte URL: http://msdn.microsoft.com/library/default.asp?url=/library/ en-us/dnnetdep/html/vsredistdeploy1_1.asp
a página de visualização padrão de um web service e seus métodos disponíveis, no nosso exemplo somente a função HelloWorld. Nosso próximo desafio é substituir o método anteriormente implementado em nosso projeto smart client para receber a string “Hello World” do web service que terminamos de implementar. Primeiramente devolva a condição de StartUp Project para nosso projeto WindowsApplication1 procedendo da mesma forma anterior. Para criar a instância do web service, necessitamos adicionar sua web reference no projeto que deseja instânciá-la. Clique novamente com o botão direito do mouse em cima do projeto WindowsApplication1 na janela Solution Explorer ( 3) e selecione a opção de menu “Add Web Reference”. Neste momento, será visualizada a 5. No campo destinado a URL digite o endereço visualizado na 5 e clique no botão “Go”. Aparecerá a informação “1 Service Found: - Service1”. Em seguida, clique no botão Add Reference. Vamos agora alterar o código do botão que havíamos anteriormente implementado para o demonstrado na 3. Procedendo desta forma, desligamos o método que apresentava string local com a frase Hello World e passamos a consumir uma string fornecida por um web service. No código apresentamos como criar uma nova instância do web service e como invocar seu método HelloWorld( ). Perceba que Localhost é a classe que possui a abstração para tratar as questões relacionadas aos endereços físicos de web services. Em outras palavras, localhost não será substituído – ele é o servidor real.
Veremos agora como faremos nossa aplicação se comunicar com o servidor. Para isto você precisa criar apenas um web service que será instanciado através do seu rich client.
Web service Vamos alterar nossa solução anterior incrementando um novo projeto. Utilizaremos o template ASP.NET Web Service. Utilize o menu File New Project. Atenção para marcar a opção “Add to Solution”. Após corretamente procedido, verifique a janela Solution Explorer do Visual Studio .NET e deverá ver uma imagem similar a 3. Verificando o código pré-existente no arquivo Service1.asmx, observamos que já existe uma implementação de exemplo do tipo “Hello World!” comentada. Retire as marcas de comentário e o resultado está apresentado na 2. Para testar nosso web service, clique com o botão direito do mouse em cima do projeto WebService1 na janela Solution Explorer ( 3) e marque a opção de menu “Set As StartUp Project”. Em seguida clique no botão Start da barra de atalhos. A 4 apresenta
3. Visualização da janela Solution Explorer.
2. Implementação do web service HelloWorld [WebMethod] public string HelloWorld() { return “Hello World”; }
58 3º Edição
wm03.indb 58
30/6/2005 17:33:35
Smart Client
4. Página de amostra de nosso web service.
palmente no quesito performance. Listamos abaixo uma série de orientações que deverão ser contempladas em suas implementações baseadas nesta solução proposta para minimizar, e na maioria dos casos, evitar os problemas relatados. 1. Não construir assemblies grandes: lembre-se que seus formulários seriam disponibilizados através da web utilizando o protocolo HTTP que mesmo empacotando e dividindo estes bytes, os mesmos serão transportados do servidor até a estação do trabalho como se fosse uma espécie de download de arquitetura. 2. Construa blocos de código autônomos: seu planejamento arquitetônico será eficiente se você conseguir isolar funcionalidades que possam operar sozinhas independentemente, diminuindo assim o tráfego na rede. 3. Não construa web services “tagarelas”: a comunicação de sua aplicação com o servidor deve ter o menor número de repetições possíveis. Opte por receber um conjunto maior de dados a ter que buscar registro a registro quando necessário. 4. Analise sua aplicação antes de distribuir: utilize ferramentas que analisam as chamadas e o tráfego HTTP e verifique se sua aplicação não esta tentando fazer download de assemblies desnecessários. 5. Não comparar com arquiteturas cliente-servidor: verificamos em nossos desenvolvimentos que muitos usuários consideram lento um aplicativo smart client. Este pensamento é fruto de uma comparação com soluções baseadas na arquitetura cliente-servidor. É importante explicar que deve ser considerado o tempo de transporte do formulário e o tempo de compilação JIT na estação de trabalho.
Perguntas e respostas sobre smart client
5. Formulário fornecido pelo Visual Studio.NET para adicionar web references.
3. Instanciando o web service private void button1_Click(object sender, System. EventArgs e) { localhost.Service1 WS = new localhost. Service1(); WS.Url = “http://localhost/WebService1/Service1. asmx”; MessageBox.Show(WS.HelloWorld()); }
Repita os procedimentos de teste anteriormente ensinados e obterá o resultado visualizado na 6.
Dicas práticas Como considerações finais é importante entender alguns pontos. A implementação smart client utilizada sem um bom planejamento arquitetônico apresentará resultados desastrosos princi-
A seguir serão apresentadas algumas perguntas recorrentes sobre smart client feitas normalmente pelos iniciantes deste novo tipo de solução e suas respectivas respostas: Qual o esforço de transformar uma aplicação fat client desenvolvida em .NET para smart client? A resposta esta diretamente dependente da forma de implementação. Se a estratégia de codificação do fat client orientou a separação das regras de negócios e dos acessos ao banco de dados em dll, caracterizando-se uma implementação três camadas, o esforço será transformar estas dll em web services. Para implementações em que se misturam códigos de interface com regras de negócios e códigos de acesso a dados, a conversão será mais dispendiosa que uma nova construção a partir do marco zero. Tudo o que eu posso fazer em uma aplicação baseada em formulários pode ser transformado para smart client, ou existem restrições de objetos que podem ser trafegados por HTTP e visualizados pelo browser? As restrições estão relacionadas a questões de segurança e não totalmente ligadas ao objeto. Se você utiliza uma rotina que pretende gravar um arquivo localmente na estação de trabalho, ne-
WebMobile 59
wm03.indb 59
30/6/2005 17:33:36
Conclusão
6. Demonstração final de nosso aplicativo smart client.
cessitará configurar uma permissão especial para esta finalidade no computador antes de executá-la, caso contrário você receberá uma mensagem de erro. Da mesma forma, se algum objeto utilizado necessitar de uma iteração maior com a estação, precisamos estudar antecipadamente como liberar a segurança desta iteração. Qual porta do firewall precisamos liberar para trafegar uma aplicação smart client? Uma aplicação smart client trafega sobre protocolo HTTP, nenhuma porta especial precisa ser liberada para esta finalidade. Devemos construir portais web na arquitetura smart client? Soluções baseadas em smart client são indicadas somente para cenários intranet / extranet. A questão é a obrigatoriedade do .NET Framework previamente instalado na estação cliente, cenário não indicado para uma solução aberta aonde poderemos encontrar os mais diversos sistemas operacionais e visualizadores web. Todos os componentes existentes numa aplicação windows forms podem ser utilizados numa aplicação smart client? Sim, sem limitações para os componentes “nativos” do Visual Studio.NET. Na prática existem ressalvas apenas para componentes de “terceiros” que podem exigir permissões especiais ou utilizarem classes não registradas no Global Assembly Cache (ler 4). Nestes casos, comportamentos indevidos como excesso de requisições ao servidor ou até mesmo exceptions poderão acontecer e dificilmente serão evitados.
4. Global Assembly Cache Cada computador que possui o .NET Framework instalado tem uma área de código de máquina chamada Global Assembly Cache. O GAC armazena bibliotecas especialmente desenvolvidas para serem compartilhadas por inúmeras aplicações em seu computador. Para a interação com o GAC o .NET Framework SDK possui uma ferramenta de linha de comando denominada Gacutil.exe ou você pode usar o próprio Windows Explorer no caminho C:\$WindowsPath$\assembly
Se você tem uma necessidade de desenvolver uma solução de software baseada em intranet ou extranet, é muito menos dispendioso construir com a arquitetura smart client. Não apenas em aspectos financeiros e conseqüentemente de tempo. Os aspectos qualitativos de navegabilidade e usabilidade serão atendidos de forma plena sem exigir “malabarismos” das linguagens de scripts. Por outro lado, se sua necessidade estiver voltada para a internet, escolher smart client pode ser muito caro, pois exigirá das estações requisitos de sistema operacional, a instalação antecipada do .NET Framework e um requisito de hardware - memória RAM. Um cenário típico de smart client são soluções que necessitam de facilidade de distribuição e ao mesmo tempo possuem exigências de interação com recursos avançados da estação de trabalho. Citando um exemplo real, presenciei recentemente na empresa em que trabalhamos o desenvolvimento de um aplicativo para uma gigantesca companhia de Call Center em que os desafios eram construir uma solução que controlasse níveis de SLA (Service Level Agreement) que pudesse ser facilmente implantada e distribuída em 8.000 estações de trabalho e que interagissem com APIs de telefonia para obter os indicadores a serem controlados. Utilizamos Visual Basic.NET em uma arquitetura smart client e persistimos os dados no SQL Server 2000. Com 2 desenvolvedores e 1 technical leader gastamos apenas 30 dias para finalizar uma solução com aproximadamente 20 telas complexas. Se utilizássemos uma arquitetura baseada em web browser, certamente gastaríamos mais tempo e necessitaríamos de mais um technical leader que estudaria e implementaria scripts para interação com as APIs de telefonia necessárias ao objetivo do projeto. Por fim, é importante ressaltar que arquiteturas baseadas smart client desenvolvidas com o Visual Studio.NET versão 2003, necessitaram ser carregadas toda vez que foram executadas. Para a versão 2005 do Visual Studio.NET está sendo criado um mecanismo chamado “Click Once” que proverá recursos automáticos que eliminarão com esta limitação. Smart client, use e abuse, mas com bom senso!
Fábio Câmara (fabio.camara@architettura. com.br) MCP, MCSA, MCAD Charter, MCDBA e MCSD.NET - É Diretor de Operações da ArchITettura e responsável pela divisão Technical Services. Escreveu os livros “Projetos com Windows DNA e .NET”, “Dominando o Visual Studio.NET com C#”, “58+ Soluções em .NET” e “Orientação a Objeto com .NET” dentre outros.
60 3º Edição
wm03.indb 60
30/6/2005 17:33:37
Web Services
Leitura Obrigatória Web Mobile 1 Construindo sua primeira aplicação para POCKET PC com .NET. Web Mobile 2 Acesso a dados no Pocket?
Integrando dados com
Web Services por Renato Haddad
A
palavra chave em integração de dados chama-se Web Services. Muitas integrações no MS-Office 2003 com SharePoint já são feitas em Web Service e você nem sabe o que está por trás das coisas. Web Service veio para se tornar um padrão mundial de integração de dados entre aplicações, plataformas e qualquer outro meio de comunicação. O objetivo deste artigo é explorar o uso de Web Services em Pocket PCs, afinal com o advento de novas tecnologias como Wi-Fi, Bluetooth, redes Wireless e, em breve Wi-Max, o acesso aos dados ficarão exatamente na palma da mão em qualquer hora e lugar. Wi-Max é uma nova tecnologia que permitirá acessar dados sem fio em um raio de até 75 quilômetros. Com isto, as redes wireless deverão ganhar um espaço considerável no mundo de aplicações (imagine que você conseguirá estar conectado à rede da sua empresa a uma distância de 75 quilômetros).
Web Services A forma mais comum de trocar ou integrar dados entre plataformas e aplicações é através de arquivos textos. Com o uso de Web Services, tudo ficou mais fácil e transparente, pois todo Web Service tem uma estrutura XML, o que permite dizer que a descrição do conteúdo do arquivo é muito mais fácil de se entender. Isso não significa que o tamanho do arquivo ficará menor e mais rápido, pelo contrário, ficará sempre maior devido às tags XML. Mas isso é apenas um detalhe, pois toda a descrição dos dados estão representadas por estas tags XML, dispensando totalmente o uso de manuais descritivos dizendo o que significa cada campo ou em que posição começa e termina um determinado campo. O Web Service pode se comunicar através de um conjunto de letras, sendo: SOAP (Soap Object Access Protocol, protocolo de comunicação contendo métodos do SOAP, Get e Post), http (meio de comunicação), XML (conteúdo com dados). Isso quer dizer que quando você solicita um Web Service, ele é processado no servidor, é gerado um arquivo XML com os dados, o qual é empacotado com o SOAP e transmitido via http. Tenha em mente que Web Service deve ser usado todas as vezes em que você precisa ter acesso às informações de uma empresa que não está aberta ao público diretamente pela rede da empresa, somente através de Web Services. Então, esta empresa publica um Web Service, e quem quiser obter tal informação, basta consumir este Web Service. Como o uso de Web Services tem se tornado uma prática comum nas aplicações, saiba que no Pocket PC é possível consumir um
WebMobile 61
wm03.indb 61
30/6/2005 17:33:39
Web Service sem nenhum trauma. Para isso, é preciso que haja uma forma de conexão http com a rede, pois o Web Service estará hospedado em um servidor. Uma particularidade nas aplicações em Pocket PC é que, para se consumir Web Services, é preciso referenciá-lo pelo número IP do servidor com a URL completa (ler 1).
1. Atenção no uso da URL Jamais use http://localhost para referenciar um Web Service no Pocket PC, pois isto não funciona igual às aplicações ASP.NET ou Windows.
1. Código do Web Service using System.Data.SqlClient; string conexao = “Database=Northwind;Server=(local);user id=sa”;
[WebMethod(Description=”Lista dados a partir de uma string SQL”)] public DataSet GetData(string sql) { SqlConnection conn = new SqlConnection(conexao);
Agora, vamos ver na prática como utilizar Web Services em Pocket PC. Abra o Visual Studio .NET 2003 e crie um novo projeto do tipo ASP.NET Web Service chamado WSTutorialPocket, conforme 1. No Solution Explorer, adicione um novo Web Service (Add / Add Web Service) chamado Estoque. Veja na 1 o código do Web Service contendo os três métodos que serão disponibilizados para quem quiser consumi-lo em qualquer tipo de aplicação. Como será feito acesso ao banco de dados SQL Server, é preciso referenciar na lista de using o namespace System.Data.SqlClient. A classe chama-se Estoque e contém os seguintes métodos: GetData: recebe uma string SQL como parâmetro, executa no banco de dados Northwind e retorna um DataSet; QtdeEstoque: recebe o código de um produto como parâmetro, pesquisa a quantidade no estoque e retorna uma string; Faturamento: retorna o faturamento dos produtos da tabela Products.
// monta o dataAdapter com a instrução sql // // recebida SqlDataAdapter adapter = new SqlDataAdapter(sql, conn); // instancia o Dataset DataSet ds = new DataSet(); // abre a conexão conn.Open(); // preenche o adapter com a tabela “dados” adapter.Fill(ds, “dados”); // fecha a conexão conn.Close(); // retorna o dataset gerado return ds; }
[WebMethod(Description=”consulta qtde estoque”)] public string QtdeEstoque(int cod) {
Note que em todos os exemplos, o banco de dados utilizado é o Northwind. Cabe ressaltar que você deve ajustar a string de conexão de acordo com as credenciais do seu SQL Server, inserindo a password e segurança integrada. Cabe ressaltar ainda que este Web Service está escrito em C#, mas poderia estar escrito em VB.NET. Apesar de ser apenas uma questão de sintaxe, você já visualiza o quanto a plataforma .NET é flexível em relação à multiplicidade de linguagens, pois esse código estará em MSIL (Microsoft Intermediate Language).
SqlConnection conn = new SqlConnection(conexao); string sql = “Select UnitsInStock FROM Products Where ProductID=” + cod; conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); string estoque = Convert.ToString(cmd. ExecuteScalar()); conn.Close(); return estoque; }
[WebMethod(Description=”faturamento”)] public double Faturamento() { SqlConnection conn = new SqlConnection(conexao); string sql = “Select Sum(UnitsInStock * UnitPrice) AS Total FROM Products”; conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); double valor = Convert.ToDouble(cmd. ExecuteScalar()); conn.Close(); return valor; }
1. Novo Web Service.
62 3º Edição
wm03.indb 62
30/6/2005 17:33:42
Web Services
Salve o projeto. No Solution Explorer defina este Estoque como sendo o Set As Startup Project e dê um CTRL + F5 para executar o 2). Web Service no browser ( Selecione o método Faturamento e clique em Invoke. Note que o retorno é uma string XML contendo o conteúdo a ser usado no Front-End ( 3). A facilidade que um Web Service proporciona é que você deverá apenas consumir o XML retornado, não importa onde, deixando a interface a cargo da aplicação. O próximo passo é criar o formulário que irá consumir esse Web Service no PocketPC. No projeto tutorialPocket (abordado na Web Mobile 1 e Web Mobile 2), adicione um novo formulário chamado frmWebService. Adicione os controles com as respectivas propriedades, conforme 1e 4. No formulário frmWebService, configure a propriedade MinimizeBox = False.
tante ressaltar que se o contrato que descreve o Web Service (WSDL) não for encontrado, o mesmo não será disponibilizado. No campo Web Reference name, digite WSEstoque que é a referência que faremos durante o projeto. Por fim, clique no botão Add Reference. Veja no Solution Explorer que a referência foi adicionada com sucesso ( 6). 4. Formulário para consumir Web Service. Agora digite o código da 2 para instanciar o Web Service e, para cada Button, adicione a chamada ao método do Web Service, passando se necessário o devido argumento. Note que o Front-End praticamente só contém códigos de cha-
Uma vez definido o layout, é preciso referenciar o Web Service. Para isso, selecione o menu Project|Add Web Reference. Digite a URL completa do Web Service (por exemplo: http://192.168.2.13/ WSTutorialPocket/Estoque.asmx) e clique no botão GO para que o Web Service seja pesquisado e exibido na tela ( 5). É impor-
5. Adicionando uma referência ao Web Service 2. Execução do Web Service.
3. Resultado final do consumo do método do Web Service.
1 Label 1 numericUpDown 1 Button 1 Label 1 Button 1 Label 1 Button 1 Datagrid
Text: código: Name: codProduto
Minimum: 1
Maximum: 100 Value: 1 Increment: 1 Name: btnEstoque Text: Estoque Name: lblQtde Text: 0 Name: btnFaturamento Text: Faturamento Name: lblFaturamento Text: branco Name: btnDados Text: Dados tabela Name: gridDados
1. Componentes do formulário Web Service.
6. Solution Explorer exibindo referências ao Web Service.
WebMobile 63
wm03.indb 63
30/6/2005 17:33:43
2. Código para consumir o Web Service // referencia o Web Service na variável ws WSEstoque.Estoque ws = new WSEstoque.Estoque();
private void btnEstoque_Click(object sender, System. EventArgs e) { try { // captura o código do produto int cod = Convert.ToInt32(codProduto.Value);
madas ao Web Service, e toda a lógica, regras de negócios, se existirem, estão armazenadas diretamente no Web Service. O mais importante é você saber que o Front-End usado aqui foi um Pocket PC, mas poderia ser um Windows Application, uma ASP.NET, uma Console Application ou o MS-Excel ou MS-Access, deixando o Front-End para o desenvolvedor definir junto ao usuário. Salve o projeto (Ctrl + S). Na primeira parte deste tutorial (exibido na Web Mobile 1) foi desenvolvido um formulário de menu contendo um login com algumas opções. Portanto, inclua o formulário frmWebService no menu, digite o código para chamar o respectivo formulário e teste a aplicação ( 7).
// consome o método QtdeEstoque do web service lblQtde.Text = ws.QtdeEstoque(cod).ToString(); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
private void btnFaturamento_Click(object sender, System. EventArgs e) { try { // consome o método Faturamento do web service lblFaturamento.Text = string.Format(“R$ {0}”, ws.Faturamento().ToString()); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
Conclusão Neste artigo você percebeu o quanto que um Web Service pode tornar a integração de dados das empresas transparente, simples e que grande parte dos negócios entre plataformas e aplicações podem ser feitos através de Web Services. O Gartner Group estima que 80% dos negócios até o ano de 2008 serão feitos via Web Service. Cabe ressaltar que Web Service suporta https e outros protocolos e meios de proteção de dados, invasões, etc, basta você pesquisar sobre WS2 nos sites especializados em Web Services.
7. Aplicação consumindo Web Service.
private void btnDados_Click(object sender, System. EventArgs e) { try { // define uma instrução SQL string sql = “Select ProductName, UnitsInStock FROM Products”; // consome o método GetData do web service gridDados.DataSource = ws.GetData(sql).Tables[0].DefaultView; } catch (Exception ex) { MessageBox.Show(ex.Message);
Renato Haddad (rehaddad@msn.com) é Microsoft Most Valuable Professional .NET Mobile Devices, editor da revista MSDN Magazine Brasil, ministra treinamentos e palestras sobre .NET e é autor de diversos livros e treinamentos em CD multimídia de ASP.NET, SQL Reporting Services, Visual Studio .NET 2003 e Aplicações Móveis para celulares e Pocket PC, tanto no Brasil como em outros países da América Latina.
} }
Faça o download no site:
www.microsoft.com/windowsmobile
www.devmedia.com.br/webmobile/downloads
64 3º Edição
wm03.indb 64
30/6/2005 17:33:45
Xxxxx
WebMobile 65
wm03.indb 65
30/6/2005 17:33:58
Usando conexões HTTP em um jogo J2ME/ MIDP por Carlos Eduardo Lima Borges
A
comunicação móvel é fundamental para a vida moderna. Independente do lugar em que nos encontramos, podemos ver o horário do cinema, consultar nosso saldo bancário, verificar a cotação de moedas e bolsas de valores, enviar e receber e-mails, navegar pela web (de forma restrita na maioria dos dispositivos, mas já é um começo). Tendo isso em mente, iremos abordar a terceira parte de uma série de artigos que começou na primeira edição da WebMobile com o artigo: “Desenvolvimento de jogos J2ME/MIDP para celular”, onde foram apresentados conceitos básicos sobre MIDP e J2ME e o processo de elaboração de jogos. Para demonstrar os conceitos apresentados, os autores Eduardo Peixoto e Renato Iida criaram o jogo Mobile Invaders. A segunda edição da WebMobile apresentou a segunda parte desta série: “Incrementando um jogo J2ME/MIDP usando RMS: Uma introdução ao Record Management System (RMS)” no qual o autor, Fábio Mesquita, acrescentou novas funcionalidades, tais como fases com nível de dificuldade crescente, pontuação e armazenamento local de pontos; demonstrando os conceitos relacionados a RMS. Para aumentar a atratividade e a vida útil do jogo, vamos modificar este jogo para estimular a competitividade entre os jogadores por meio do registro on-line dos recordes. Neste artigo você aprenderá a programar essa nova funcionalidade. Além disso, o artigo discute como aperfeiçoar a transmissão de dados pela internet, e para isso, serão abordados alguns conceitos teóricos, como transmissão de dados usando GPRS, otimização da quantidade de dados a ser transmitida, protocolo HTTP, Generic Connection Framework (GCF) e Servlets. Estes conceitos serão necessários para compreender o que está ocorrendo no servidor e no celular quando enviamos um recorde ao servidor responsável pelo seu armazenamento e gerenciamento, e como isto se reflete na implementação deste tipo de aplicação em dispositivos móveis. Após realizar toda essa etapa móvel, você aprenderá a criar um servlet para receber os recordes e deixá-los disponíveis para consulta no lado do servidor. Com isto, esperamos que ao final do artigo você seja capaz de criar um serviço com um cliente móvel acessando um servidor usando conexões HTTP.
Transmissão de dados pela internet Leitura Obrigatória Web Mobile 1 Artigo Desenvolvimento de jogos J2ME/MIDP para celular. Web Mobile 2 Artigo Incrementando um jogo J2ME/MIDP usando RMS: Uma introdução ao Record Managament System (RMS).
Dispositivos móveis geralmente possuem conexão limitada. O tipo de serviço mais comum em aparelhos GSM é o GPRS – General Packet Radio Service – com taxa de transferência máxima teórica de 171,2 kbps. Entretanto, esse máximo teórico é muito caro para as operadoras de telefonia móvel e geralmente não é suportado pelos aparelhos disponíveis no mercado. O que se consegue na prática costuma ser em torno de 21,5 kbps. Alem disso, o tempo para iniciar uma sessão GPRS e realizar a primeira entrega de dados pode durar entre 2 e 15 segundos, dependendo do tráfego aéreo de dados e de voz, e também da qualidade dos equipamentos das operadoras.
66 3º Edição
wm03.indb 66
30/6/2005 17:34:06
Jogos Outra limitação importante a ser destacada é o custo pago pelo usuário final para utilizar os serviços de envio e recebimento dos dados através de GPRS. As operadoras costumam cobrar por cada kilobyte enviado/recebido um valor entre R$ 0,005 e R$ 0,02, ou seja, algumas operadoras chegam a oferecer 2 KB por centavo. Portanto, devemos nos preocupar com a quantidade de conexões que o celular irá realizar, bem como a quantidade de dados que vamos enviar, pois isso reflete diretamente na interatividade do aplicativo e no custo para o usuário final.
1. Representação de um recorde em XML <record> <nome>XAXIM</nome> <hiscore>32370</hiscore> </record>
2. Representação de um recorde em padrão simples
Otimização da quantidade de dados para o envio Existe uma relação de troca entre a eficiência na quantidade de dados e a interoperabilidade da aplicação, ou seja, quanto mais interoperabilidade na representação dos dados desejamos, menos eficiência na transmissão dos dados obteremos. Isso ocorre principalmente, pois a quantidade de dados a serem transmitidos aumenta à medida que aumentamos a sua interoperabilidade. Com isso, se compararmos um padrão de comunicação de alta interoperabilidade com um padrão mais simples, percebemos grande diferença na eficiência na transmissão de dados, como veremos com mais detalhes a seguir. A 1 mostra a representação de um recorde para o jogo Mobile Invaders no formato XML – mais complexo – que possui 65 bytes de tamanho para envio. Já a 2 apresenta a representação de um recorde para o mesmo jogo utilizando um padrão simples que possui 25 bytes de tamanho. Por fim, a 3 mostra a representação em hexadecimal do mesmo recorde usando simplesmente um int para representar uma pontuação e uma String em UTF para representar um nome. Esta representação possui 11 bytes no total. O gráfico da 1 mostra que devemos escolher o formato de nível mais simples, isto é, aquele em que cabem mais dados por byte. No jogo que estamos desenvolvendo, escolhemos a opção de baixo nível devido à sua eficiência, na qual a pontuação é armazenada em 32 bits, que corresponde ao tamanho de um int na linguagem Java. O nome é armazenado em uma String codificada em UTF-8, isso significa que usaremos no máximo 7 bytes; 2 bytes indicando o tamanho da string e 5 bytes para o máximo de 5 letras definido no jogo. A 2 mostra a codificação UTF para os caracteres possíveis no jogo. Mesmo sabendo que os padrões de baixo nível são mais eficientes, às vezes temos de usar padrões mais interoperáveis e complexos. É o caso de aplicações móveis que desejam usar web services. Nesse caso é necessário enviar os dados em um formato que o web service saiba interpretar, como o XML. Existem casos em que se fosse adotado um padrão complexo, o celular passaria a ter uma eficiência intolerável, ou seja, a maioria dos usuários cancelaria a operação antes dela terminar. Para esses casos podemos adotar uma solução envolvendo um middleware (veja 1). Este serviria de intermediário entre o celular e o serviço a ser acessado. A responsabilidade por processamentos mais complexos é transferida do dispositivo móvel para o middleware, que retorna informações simplificadas para o celular.
NOME=XAXIM HISCORE=32370
3. Representação hexadecimal do recorde usando um padrão otimizado /* int */ 0x00,0x00,0x7E,0x72 // 4 bytes representando o int: 32370, // veja que 0x00007E72 = 32370 /* string em UTF */ 0x00,0x05 // 2 bytes = tamanho da String: 0x0005 = 5 letras 0x58,0x41,0x58,0x49,0x4D
// os outros 5 bytes
representam a String “XAXIM”: // X: 0x58, A: 0x41, X: 0x58, I: 0x49, M: 0x4D
1. Eficiência dos padrões de comunicação.
2. Codificação UTF-8 com 1 byte por caractere.
WebMobile 67
wm03.indb 67
30/6/2005 17:34:09
1. Middlewares Middleware é um conjunto de agentes que atuam como intermediários entre diferentes componentes de aplicações. Geralmente, são usados em aplicações distribuídas complexas. Um exemplo clássico é o gerenciador de transações, que é o componente colocado entre os usuários clientes e um banco de dados em aplicações cliente/servidor. Nestas situações, o middleware reduz o número de conexões de alto custo que devem ser feitas e as faz da maneira mais eficiente possível. Desse modo, a velocidade de atendimento aos clientes aumenta significativamente. Alguns exemplos de middlewares baseados em transações são: CICS, IBM Websphere MQ, Tibco e Tuxedo. Outra razão para o uso de middlewares é o fornecimento de:
Serviços; Abstrações de alto nível para aplicações (APIs – Application Programming Interfaces). Estes fatores facilitam:
O desenvolvimento de aplicações; A integração de aplicações; As tarefas de gerenciamento de sistemas. Os Middlewares atuais usam, comumente, as seguintes tecnologias:
XML; SOAP; Web services; Arquiteturas Orientadas a Serviço (SOA – Service-Oriented Architecture). Grupos como Apache Software Foundation e ObjectWeb Consortium apóiam o desenvolvimento de middleware open-source.
Vejamos agora alguns conceitos sobre o protocolo usado para enviar esses dados para um servidor remoto na nossa aplicação.
PUT: guarda um recurso na URL de requisição DELETE: remove o recurso identificado pela URL de requisição. OPTIONS: retorna os métodos HTTP que o servidor suporta. TRACE: retorna os campos de cabeçalho enviados com a requisição TRACE. O protocolo HTTP 1.0 inclui apenas os métodos GET, HEAD e POST. Uma resposta HTTP contém o código de resultado, campos de cabeçalho e um corpo. O protocolo HTTP espera que o código de resultado e todos os campos de cabeçalho sejam retornados antes de qualquer conteúdo do corpo. Alguns códigos de resultado comumente usados são: 404: indica que o recurso requisitado não está disponível. 401: indica que a requisição requer autenticação HTTP. 500: indica que um erro ocorreu dentro do servidor HTTP que impediu o atendimento da requisição. 503: indica que o servidor HTTP está temporariamente sobrecarregado e incapaz de tratar a requisição. Para maiores informações veja os seguintes RFCs (Request for Comment): HTTP/1.0 (RFC 1945) HTTP/1.1 (RFC 2616) Eles podem ser encontrados em: www.ietf.org/rfc.html ou www.rfcs.org. Usamos o método POST e o método GET na nossa aplicação para ilustrar como usar cada um deles. Para enviar recordes usamos o método POST e para receber recordes usamos o método GET. Para usar o protocolo HTTP em nossa aplicação, usamos a interface HttpConnection do Generic Connection Framework.
Generic Connection Framework (GCF) Introdução ao HTTP – HyperText Transfer Protocol As comunicações entre o celular e o servidor web são feitas usando o protocolo HTTP. Este define as requisições que um cliente pode enviar para um servidor e as respostas que o servidor retorna. Cada requisição contém uma URL, que é uma string que identifica um componente web ou um objeto estático como uma página HTML ou um arquivo de imagem. O Tomcat, que é um servidor J2EE converte uma requisição HTTP em um objeto de requisição HTTP e o entrega ao componente web identificado pela URL de requisição. O componente web completa um objeto de resposta HTTP, que o servidor converte em uma resposta HTTP e envia ao cliente. Uma requisição HTTP consiste em um método de requisição, uma URL de requisição, campos de cabeçalho e um corpo. O protocolo HTTP 1.1 define os seguintes métodos de requisição: GET: recupera o recurso identificado pela URL de requisição. HEAD: retorna os cabeçalhos identificados pela URL de requisição. POST: envia dados de tamanho ilimitado para o servidor web.
Os pacotes java.io.* e java.net.* do J2SE necessitam de mais memória que a maioria dos dispositivos móveis possuem. O Generic Connection Framework (GCF) foi definido para atender a necessidade de usar recursos de rede em dispositivos que possuem a configuração CLDC – Connected Limited Device Configuration. O GCF é uma API de J2ME formada por uma hierarquia de interfaces e classes para criar conexões e realizar leitura e escrita de dados. Essas conexões podem ser HTTP, datagramas, Bluetooth entre outras. O GCF possui uma abordagem genérica para conectividade, pois fornece uma API de base comum para leitura e escrita de todos os tipos básicos de conexão, tanto conexões baseadas em pacotes (blocos de dados) quanto para conexões baseadas em streaming (dados contínuos ou seqüenciais). A 3 mostra a hierarquia de interfaces do Generic Connection Framework. Na 3 podemos ver as interfaces originais do CLDC em amarelo, as interfaces adicionadas pelo MIDP 1.0 em verde e as interfaces acrescentadas pelo MIDP 2.0 em azul. No topo da hierarquia está a interface Connection, que é o tipo mais básico de conexão. Todos os outros tipos de conexão herdam de
68 3º Edição
wm03.indb 68
30/6/2005 17:34:13
Jogos
tornando-se cada vez mais complexos e funcionais. Para leitura e escrita baseada em pacotes, o GCF especifica o DatagramConnection. Para conexões baseadas em streaming são definidos: InputConnection, OutputConnection, StreamConnection e ContentConnection. Veja pela 3 que StreamConnection herda tanto de InputConnection quanto de OutputConnection, o que implica em uma conexão de streaming de entrada e de saída de dados. O ContentConnection é um tipo especial de StreamConnection que possui informações de conteúdo tais como tamanho de dados (data length), tipo de conteúdo (content-type) e codificação dos dados (data encoding). O StreamConnectionNotifier permite que uma aplicação aguarde pela chegada assíncrona de conexões de streaming. O MIDP 1.0 acrescenta a interface HttpConnection ao GCF definido pelo CLDC. Essa interface contém os métodos e constantes necessários para uma conexão HTTP e será bastante explorada nesse artigo. O MIDP 2.0 adiciona algumas interfaces para acesso de baixo nível aos recursos de redes, são elas: ServerSocketConnection, UDPDatagramConnection e SocketConnection. Não usaremos nenhuma delas nesse artigo. Connection
Além da hierarquia de interfaces, o GCF possui os elementos mostrados na 4, são eles: A classe Connector: usada como fábrica de conexões. A classe ConnectionNotFoundException: usada para lançar uma exceção indicando quando um tipo de conexão não pode ser criado. A interface Datagram: usada para representar um pacote do tipo datagrama em conexões baseadas em pacotes.
Usando o HttpConnection Para usar a interface HttpConnection devemos importar o pacote javax.microedition.io do perfil MIDP de J2ME. Usamos o método open(String url) da classe Connector para criar um objeto que implementa a interface HttpConnection, passando a URL de conexão como parâmetro, como mostra a 4. Já com o objeto criado, definimos o método HTTP usando setRequestMethod(String metodo) e podemos definir também as propriedades da requisição HTTP usando o método setRequest Property(String propriedade, String valor). Veja a 2 para maiores detalhes sobre as propriedades de HTTP.
2. Propriedades de requisições HTTP Algumas propriedades podem ser enviadas nas requisições e respostas para definir informações sobre o conteúdo que está sendo transportado ou sobre o cliente que está fazendo a requisição. Por exemplo:
Content-Length: indica o tamanho do corpo da resposta HTTP, em bytes; Content-Type: indica o tipo de conteúdo presente no corpo da resposta http; User-Agent: contem informações sobre o cliente que está originando uma requisição http. Usamos o tipo: “application/octet-stream” como Content-Type na
Todos esses elementos são definidos no CLDC. Algumas classes não são definidas pelo GCF, mas são relacionadas: InputStream, OutputStream, DataInputStream e DataOutputStream. Essas classes são usadas para conexões de streaming.
4. Generic Connection Framework – Demais elementos.
4. Uso da interface HttpConnection // Cria a conexão com a URL HttpConnection conn = (HttpConnection) Connector. open(url); conn.setRequestMethod(HttpConnection.POST); conn.setRequestProperty(“Content-Type”, “application/ octet-stream”);
3. Hierarquia de interfaces do Generic Connection
WebMobile 69
wm03.indb 69
30/6/2005 17:34:13
Listagem 4 para definir que o tipo de conteúdo era um vetor de bytes. Podem ser encontradas mais propriedades no RFC 2616 (www.ietf. org/rfc/rfc2616.txt) – capítulo 14. Header Field Definitions.
5. Uso da classe DataOutputStream para escrita de um Record no jogo Mobile Invaders // Obtém um stream para envio de dados
Com o objeto de conexão criado, podemos abrir um stream para escrita de dados com o método openDataOutputStream() ( 5 – linha 1). Usamos a classe DataOutputStream para escrever tipos de dados primitivos ( 5 - linhas 2 e 3). Para esvaziar o buffer de envio, usamos o método flush() da mesma classe ( 5 – linha 4). Podemos executar a leitura de dados de modo análogo. Com uma conexão estabelecida, criamos um DataInputStream usando o método openDataInputStream() ( 6 – linha 1) e fazemos a leitura dos dados enviados pelo servidor. A classe DataInputStream possui métodos para leitura de tipos de dados primitivos ( 6 – linhas 2 e 3). Como já dissemos no artigo anterior, o processo de escrita e leitura de um record do RMS é idêntico ao processo de envio e recebimento de pacotes pela rede, como mostram as 5 e 6. Depois do envio ou recebimento de dados, devemos fechar as streams de envio/recebimento e a conexão que não estiver mais sendo usada. O fechamento deve acontecer na ordem inversa da criação, por exemplo, a Connection foi o primeiro objeto a ser criado, logo será o último a ser fechado, como mostra a 7. É importante notar que usamos métodos das classes que lançam exceção do tipo IOException, portanto devem estar dentro de blocos try...catch. Uma conexão bloqueia a thread na qual ela está sendo executada enquanto a conexão não termina. Com isso, veremos a seguir porque é importante manter threads de conexão separadas das outras threads da aplicação.
1. DataOutputStream dos = conn.openDataOutputStream();
2. dos.writeInt(32370); 3. dos.writeUTF(“CARLOS”);
4. dos.flush(); // escreve os bytes bufferizados para o stream de escrita
6. Uso da classe DataInputStream //Obtém o stream para recebimento de dados 1. DataInputStream dis = conn.openDataInputStream();
2. int recorde = dis.readInt(); 3. String nome = dis.readUTF();
7. Fechando streams e terminando a conexão try{ if(dis != null){ dis.close(); } if(dos != null){ dos.close(); }
Mantendo a interface gráfica ativa com gerenciamento de threads
if(conn != null){
O usuário de seu jogo ficará extremamente frustrado se não puder cancelar uma conexão com a rede, seja ela a busca pelos mais recentes recordes armazenados no servidor ou o envio de um novo recorde. Com isso, torna-se necessário criar e gerenciar threads para enviar ou receber dados e manter a aplicação respondendo aos comandos do usuário. Na prática, isso significa que a thread de conexão não poderá ser a mesma que escuta os comandos dados pelo usuário. Para isso, criamos as seguintes classes responsáveis pela criação de threads de conexão de rede: MobileInvadersClient.SendRecord e MobileInvadersClient.GetRecords. Criamos também algumas variáveis de controle para evitar que duas conexões de rede sejam realizadas ao mesmo tempo. As variáveis booleanas sendRecordCancelled e getRecordsCancelled definem o cancelamento do envio ou do recebimento de recordes. A variável booleana connectionRunning indica a existência de uma thread de conexão sendo executada. Se permitirmos que várias threads de conexão sejam executadas sem nenhum critério, rapidamente o dispositivo esgota seus recursos e pára de responder.
}
conn.close();
}catch(IOException ioe){}
Quando a classe MenuCanvas chama o método submitRecord() ou o método retrieveOnLineRecords(), a classe MobileInvadersClient executa um dos seguintes métodos: enviarRecorde(Record record): cria uma thread instanciando a classe MobileInvadersClient.SendRecord e envia o maior recorde do celular para o servidor. receberMelhoresRecordes(): cria uma thread instanciando a classe MobileInvadersClient.GetRecords e recebe os melhores recordes armazenados no servidor. A 8 mostra a classe MobileInvadersClient e sua relação com as classes internas MobileInvadersClient.SendRecord ( 8 – linhas 145 a 199) e MobileInvadersClient.GetRecords ( 8 – linhas 206 a 256). Podemos ver também como o gerenciamento de threads foi implementado.
70 3º Edição
wm03.indb 70
30/6/2005 17:34:16
Jogos
O método getInstance() ( 8 – linhas 86 a 93) verifica a existência de uma instância da classe MobileInvadersClient. Caso exista, essa instância é retornada na função, caso contrário, é instanciada e retornada uma nova classe. O método enviarRecorde() ( 8 – linhas 99 a 109) atualiza o recorde interno do dispositivo, em seguida envia o recorde ao servidor. Para enviar o recorde, inicialmente é instanciada, caso não exista, uma nova classe SendRecord ( 8 – linhas 101 a 103). Após isto, é criada uma thread de conexão, caso não exista uma conexão ativa, para envio do objeto SendRecord instanciado anteriormente ( 8 – linhas 105 a 108). Essa thread executará o método run() ( 8 – linhas 145 a 199) do objeto SendRecord enviado. O método receberMelhoresRecordes() ( 8 – linhas 114 a 124) funciona de maneira análoga. Inicialmente é instanciado um objeto da classe GetRecords ( 8 – linhas 116 a 118) que será responsável pelo recebimento dos recordes do servidor. Em seguida, é verificada a existência de alguma thread de conexão ativa ( 8 – linha 120). Caso não exista, uma thread de conexão é criada e executada ( 8 – linhas 121 e 122). Esta thread executará o método run()do objeto GetRecords ( 8 – linhas 206 a 256) enviado para realizar o recebimento dos recordes.
Os métodos cancelarEnvioRecorde() ( 8 – linhas 129 a 131) e cancelarRecebimentoRecordes() ( 8 – linhas 136 a 138) servem apenas para mudar o valor das variáveis de controle usadas pelas classes SendRecords e GetRecords durante a execução das threads de conexão. O funcionamento das classes internas SendRecords e GetRecords está explicado nos comentários da própria classe.
Classe de apoio Record: versão do dispositivo móvel Para armazenar os recordes no dispositivo móvel, criamos uma classe Record, mostrada na 9. Esta classe serve apenas para armazenar um recorde com a pontuação do jogador ( 9 – linha 9) e o nome do jogador ( 9 – linha 13). Esta classe possui dois construtores: um default sem parâmetros ( 9 – linha 16) e outro que recebe a pontuação e o nome do jogador ( 9 – linhas 23 a 26). Perceba que as variáveis de pontuação e nome são acessadas diretamente, ou seja, não existem métodos set e get para cada variável devido à limitação de recursos nos dispositivos.
Classe de apoio Record: versão do servidor Criamos uma classe de apoio – Record – semelhante à usada no
8. A classe MobileInvadersClient 26: * @author /*
Carlos Eduardo Lima Borges (carlos.
borges@gmx.net)
2:
* Projeto Mobile Invaders.
27: * @version 1.0.6
3:
*
28: */
4:
* Código desenvolvido para mostrar os conceitos de desenvolvimento de jogos para
29: public class MobileInvadersClient { 30:
5:
* celular em J2ME/MIDP.
31: /**
6:
* ** Atualizado para mostrar conceitos do uso de recursos
32:
* Instância estática da classe MobileInvadersClient
33:
*/
de rede ** 7:
* Esse jogo não está totalmente otimizado e não deve ser
8:
*
9:
* Aparelhos testados : Nokia N-Gage, Nokia 3650, Nokia
comercializado.
34: private static MobileInvadersClient instance; 35: 36: /**
3660 e Nokia 6600. 10: * Autores :
Eduardo Peixoto (eduardopfs@gmail.com)
11: *
Renato Iida
12: *
Carlos Eduardo Lima Borges (carlos.
13: *
Fábio Mesquita Póvoa (fabio.povoa@gmail.com)
(renatoiida@gmail.com)
borges@gmx.net)
14:
*/
37:
* Número de recordes que será recebido do serlvet
38:
* IMPORTANTE: Esse número deve ser igual ao número * definido no servlet
39:
*/
40: public static final int TOP = 5; 41: 42: /** 43:
* Servlet que tratará os pedidos de MobileInvadersClient
15: package br.unb.inovamobile;
44:
*/
16:
45: public static String servlet = “http://xaxim.sytes.
17: import javax.microedition.midlet.*;
net:8080/mobileinvaders/MobileInvadersServlet”;
18: import javax.microedition.io.*;
46:
19: import java.io.*;
47: /**
20:
48:
21: import br.unb.inovamobile.Record;
* Nome que Mobile Invaders usa quando se conecta ao * servlet
22:
49:
23: /**
50: public static String agent = “WebMobile 3 - Mobile
24: * Classe usada para enviar e receber dados HTTP
Invaders”;
*/
25: *
WebMobile 71
wm03.indb 71
30/6/2005 17:34:16
104:
Continuação
105:
if(connectionThread == null ||
51:
(connectionThread != null
52: // Objetos usados para enviar e receber dados
&& !connectionThread.isAlive())){
53: private static HttpConnection conn = null;
106:
54: private static DataOutputStream dos = null;
107:
55: private static DataInputStream dis = null;
108:
56:
109: }
57: // Recorde a ser enviado
110:
58: private Record record;
111:/**
59:
112: * Recebe os recordes Online do servidor
60: // MenuCanvas que ira responder nossas solicitações
113: */
61: private MenuCanvas menu;
114: public void receberMelhoresRecordes(){
62:
115:
63: // classes internas usadas pra criar Threads de conexão
116:
64: private SendRecord sendRecord;
117:
65: private GetRecords getRecords;
118:
66:
119:
67: // variáveis de controle
120:
connectionThread = new Thread(sendRecord); connectionThread.start(); }
if(getRecords == null){ getRecords = new GetRecords(); }
if(connectionThread == null || (connectionThread !=
68: private boolean sendRecordCancelled;
null && !connectionThread.isAlive())){
69: private boolean getRecordsCancelled;
121:
70:
122:
71: // Thread de conexão que será usada
123:
72: private Thread connectionThread = null;
124: }
73: // variável booleana que define o estado da thread de
125:
// conexão
connectionThread = new Thread(getRecords); connectionThread.start(); }
126:/**
74: private static boolean connectionRunning = false;
127: * Cancela o envio de recordes
75:
128: */
76: // Construtor privado para evitar a existência de mais de //uma instancia de MobileInvadersClient
129: public void cancelarEnvioRecorde(){ 130:
sendRecordCancelled = true;
77: private MobileInvadersClient(MenuCanvas menu){
131: }
78:
132:
this.menu = menu;
79: }
133: /**
80:
134:
* Cancela o recebimento de recordes
81: /**
135:
*/
82:
* Retorna a instancia de MobileInvadersClient.
136: public void cancelarRecebimentoRecordes(){
83:
* @param menu MenuCanvas que tratará as notificações de
137:
MobileInvadersClient
getRecordsCancelled = true;
138: }
84:
* @return retorna a instancia de MobileInvadersClient
139:
85:
*/
140: /**
86: public static MobileInvadersClient getInstance(MenuCanvas
141:
* Classe usada para enviar o melhor recorde para o
142:
* @author
menu){ 87:
servidor
if(instance == null){
88:
instance = new MobileInvadersClient(menu);
89:
} else {
90:
instance.menu = menu;
Carlos Eduardo Lima Borges (carlos.
borges@gmx.net) 143:
* @version 1.0.6
144:
*/
91:
}
145: class SendRecord implements Runnable{
92:
return instance;
146:
public void run(){
93: }
147:
94:
148:
connectionRunning = true;
149:
try{
95:
/**
if(!connectionRunning && !sendRecordCancelled){
96:
* Envia o recorde local atual para o servidor
150:
97:
* @param record Recorde que deve ser enviado ao servidor
151:
//Cria a conexão com o servlet
98:
*/
152:
conn = (HttpConnection) Connector.
153:
conn.setRequestMethod(HttpConnection.
154:
conn.setRequestProperty(“User-Agent”,
99:
public void enviarRecorde(Record record){
100:
this.record = record;
101:
if(sendRecord == null){
102: 103:
sendRecord = new SendRecord(); }
open(servlet);
POST);
agent);
72 3º Edição
wm03.indb 72
30/6/2005 17:34:17
wm03.indb 73
30/6/2005 17:34:25
207:
Continuação
public void run() {
208: 155:
if(!connectionRunning &&
conn.setRequestProperty(“Content-Type”, “application/octet-stream”);
156:
!getRecordsCancelled){ 209:
connectionRunning = true;
210:
try{
157:
// Obtém um stream para envio de dados
211:
// Cria a conexão com o servlet
158:
dos = conn.openDataOutputStream();
212:
conn = (HttpConnection) Connector.
/* Envia o recorde atual usando o
213:
conn.setRequestMethod(HttpConnection.GET);
214:
conn.setRequestProperty(“User-Agent”,
159:
open(servlet + “?action=melhores”);
160:
protocolo: 161:
* int: valor do recorde
162:
* UTF: dono do recorde
215:
*/
163:
agent);
216:
//Obtém o stream para recebimento de dados
164:
dos.writeInt(record.score);
217:
dis = conn.openDataInputStream();
165:
dos.writeUTF(record.name);
218:
166:
dos.flush();
219:
/*
220:
// Cria um array de Record que será
167:
// notificado ao MenuCanvas
168: 169:
* Verifica se a conexão ocorreu
170:
* e avisa à MenuCanvas se o recorde
171:
corretamente
Record[] onlineRecords = new Record[TOP];
221: // Record temporário para armazenar os
* foi enviado com sucesso ou não.
223:
Record currentRecord = null;
*/
224:
172:
222:
// valores conforme eles forem sendo recebidos
173:
int rc = conn.getResponseCode();
225:
//Recebe e armazena os recordes
174:
if(rc == HttpConnection.HTTP_OK &&
226:
for(int i=0;i<TOP;i++){
!sendRecordCancelled){
227:
175:
menu.recordSubmited(true);
228:
currentRecord = new Record();
176:
} else if(!sendRecordCancelled) {
229:
currentRecord.score = dis.
230:
currentRecord.name = dis.
231:
onlineRecords[i] = currentRecord;
177:
if(!getRecordsCancelled){
menu.recordSubmited(false);
178:
}
readInt();
179:
readUTF();
180:
}catch(IOException ex){
181:
if(!sendRecordCancelled){
182:
menu.recordSubmited(false);
232:
183:
}
234:
184:
}finally{
235:
185:
// fecha o stream usado e a conexão
186:
try{
// notifica a MenuCanvas sobre o // recebimento dos recordes online
if(dos != null){
237:
188:
dos.close();
238:
189:
}
239:
190:
if(conn != null){
240:
conn.close();
241:
191: }
193:
}catch(IOException ex){}
194:
sendRecordCancelled = false;
195:
}
236:
187:
192:
}
233:
}
if(!getRecordsCancelled){ menu.onLineRecords(onlineRecords); } } catch (IOException ioe){
} finally{
242:
try{
243:
// fecha os streams e encerra a // conexão
244:
if(dis != null) {
196:
}
245:
197:
connectionRunning = false;
246:
}
247:
if(conn != null){
198:
}
199: }
248:
200:
249:
201: /**
250:
202:
251:
* Classe usada para receber melhores recordes do servidor
203:
* @author
Carlos Eduardo Lima Borges (carlos.
borges@gmx.net)
conn.close(); } } catch (IOException ioe){} getRecordsCancelled = false;
252:
}
253:
}
254:
204:
* @version 1.0.6
255:
205:
*/
256:
206: class GetRecords implements Runnable{
dis.close();
connectionRunning = false; } }
257: }
74 3º Edição
wm03.indb 74
30/6/2005 17:34:29
Jogos
WebMobile 75
wm03.indb 75
30/6/2005 17:34:33
9. Classe Record no dispositivo móvel 14: 1: /**
15:
/** Construtor vazio*/
2: *
16:
public Record(){}
Classe usada para representar os recordes do jogo
3: */
17:
4: public class Record{
18:
5:
19:
* Construtor aceitando ser iniciado com nome e recorde
20:
* @param score recorde * @param name dono do recorde
6:
/**
7:
* Recorde
21:
8:
*/
22:
9:
public int score;
10:
23:
* Dono do Recorde
25:
12:
*/
26:
public String name;
10. Classe Record no servidor 1: package br.com.devmedia.webmobile3.mobileinvaders;
* *
6:
*
7:
*/
Classe usada para representar os recordes do jogo
private String name;
11:
private Integer hiscore;
12: * Construtor da classe Record * @param name Nome de quem obteve o recorde
16:
* @param hiscore Valor do Recorde
17:
*/ public Record(String name, Integer hiscore) { this.name = (name != null ? name : “AAAAA”);
20:
this.hiscore = (hiscore != null ? hiscore : new Integer(0));
48:
return record.name.equals(name) &&
50:
record.hiscore.equals(hiscore); }
52:
/**
53:
* método para sobrescrever hashCode() de Object
54:
* @return retorna um valor de hash code baseado nos
55:
*/
56:
public int hashCode() {
57:
return 7*name.hashCode() + hiscore.hashCode(); }
59: 60:
/**
61:
* método para sobrescrever toString() de Object
62:
* @return retorna uma String no formato “Nome:
63:
*/
recorde”, por exemplo: “Carlos: 32430” 64:
/**
24:
* método getter que retorna o dono do recorde.
25:
* @return retorna o nome de quem obteve o recorde
26:
return name;
public String toString() {return name + “: “ + hiscore.toString();}
65: 66:
*/ public String getName(){
28:
/**
67:
* método da interface Comparator implementado
68:
* para que a ordenação deixe primeiro os recordes com
69:
* @param o Objeto a ser comparado
70:
* @return Retorna um inteiro negativo, zero, ou um
maior valor
29:
}
30:
/**
31:
* metodo getter que retorna o valor do recorde
32:
* @return retorna o valor do recorde
33:
inteiro positivo se esse objeto é menor, igual ou maior que o objeto especificado.
*/ public Integer getHiscore(){
35:
return hiscore; }
37: 38:
return false; Record record = (Record)o;
}
22:
36:
if (!(o instanceof Record))
47:
58:
19:
34:
public boolean equals(Object o) {
valores de name e hiscore
15:
27:
*/
/**
14:
23:
false em caso contrario 43:
51:
10:
21:
* @return retorna true se forem equivalentes e
49:
9:
18:
42:
46:
8: public class Record implements Comparable{
13:
this.name = name; }
45:
3: /** 5:
this.score = score;
27: }
44:
2: 4:
*/ public Record(int score, String name){
24:
/**
11:
13:
/**
/**
39:
* método para sobrescrever equals() de Object
40:
* @param o Objeto a ser comparado
41:
*
71: 72:
*/ public int compareTo(Object o) {
73:
Record record = (Record)o;
74:
int lastCmp = record.hiscore.compareTo(hiscore);
75:
return (lastCmp!=0 ? lastCmp :
76: 77:
record.name.compareTo(name)); }
78: }
76 3º Edição
wm03.indb 76
30/6/2005 17:34:35
Jogos dispositivo móvel, porém acrescentamos tudo que era necessário para que pudéssemos deixar vários objetos Record ordenados em um java.util.SortedSet. Veja a 10 para examinar com maiores detalhes como ficou a classe Record no servidor. Esta classe serve para armazenar os recordes no servidor. Como o servidor possui mais recursos, podemos deixar a classe mais elegante. As variáveis que representam o nome ( 10 – linha 10) e a pontuação do jogador ( 10 – linha 11) são privadas e possuem os respectivos métodos get: getName() ( 10 – linhas 27 a 29) e getHiscore() ( 10 – linhas 34 a 36). Uma vez criado um objeto Record, seus valores não podem ser alterados. Para criar um novo objeto, usamos o construtor e passamos como parâmetros o nome e um objeto integer representando a pontuação ( 10 – linhas 18 a 21).
Como estamos usando a interface SortedSet do Collections Framework, devemos deixar a classe Record de forma que seus objetos possam ser ordenados, e para isso implementamos o método compareTo() ( 10 – linhas 72 a 77) da interface Comparable, que irá ordenar os recordes pela pontuação ( 10 – linhas 74). Caso as pontuações sejam iguais, ele irá ordenar por ordem alfabética e retornar o valor da comparação ( 10 – linhas 75 e 76). A documentação da interface Comparable recomenda fortemente que implementemos o método equals()( 10 – linhas 44 a 50) corretamente para que objetos iguais sejam considerados iguais quando usarmos esse método. O método hashCode()( 10 – linhas 56 a 58) foi implementado para devolver o mesmo código hash para objetos iguais. O método toString() ( 10 –
recordes
11. O Servlet 31:
* enviados pelos celulares com o jogo Mobile
1: package br.com.devmedia.webmobile3.mobileinvaders;
Invaders
2:
32:
3: import java.io.*;
33:
4: import java.net.*;
TreeSet();
5: import java.util.*;
34:
6:
35:
7: import javax.servlet.*;
36:
* Define a quantidade de recordes que será retornada
8: import javax.servlet.http.*;
37:
*/
9:
38:
10: /**
39:
11:
* Projeto Mobile Invaders.
40:
12:
*
41:
* Initializes the servlet.
13:
* Código desenvolvido para mostrar os conceitos de
42:
* @param config Configurações do Servlet
43:
* @throws javax.servlet.ServletException servlet
44:
*/
desenvolvimento de jogos para 14:
* celular em J2ME/MIDP.
15:
* ** Atualizado para mostrar conceitos do uso de recursos
/**
public static final int TOP = 5;
/**
exception
de rede ** 16:
*/ private static SortedSet recordes = new
45:
public void init(ServletConfig config) throws
* Esse jogo não está totalmente otimizado e não deve ser comercializado.
ServletException { 46:
17:
*
18:
* Aparelhos testados : Nokia N-Gage, Nokia 3650, Nokia
super.init(config);
47:
3660 e Nokia 6600.
48:
}
49:
19:
* Autores :
Eduardo Peixoto (eduardopfs@gmail.com)
50:
20:
*
Renato Iida
51:
21:
*
Carlos Eduardo Lima Borges (carlos.
(renatoiida@gmail.com)
borges@gmx.net)
*/ public void destroy() {
53:
22:
*
23:
*
55:
24:
* @author Carlos Eduardo Lima Borges
56:
25:
* @version 1.0.1
57:
* Handles the HTTP <code>GET</code> method.
26:
*/
58:
* @param request servlet request
27: public class MobileInvadersServlet extends HttpServlet {
59:
* @param response servlet response
28:
60:
* @throws javax.servlet.ServletException servlet
61:
*
29: 30:
Fábio Mesquita Póvoa (fabio.povoa@gmail.com)
52:
/** Destroys the servlet.
54:
}
/**
/** * SortedSet usado para armazenar de forma ordenada os
exception
WebMobile 77
wm03.indb 77
30/6/2005 17:34:35
102:
Continuação
103:
/**
62:
* @throws java.io.IOException io exception
104:
* Handles the HTTP <code>POST</code> method.
63:
*/
105:
* @param request servlet request
106:
* @param response servlet response
107:
* @throws javax.servlet.ServletException servlet
64:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
65:
throws ServletException, IOException {
exception
66:
// Define o tipo de saída como um stream de bytes.
108:
*
67:
response.setContentType(“octet/stream”);
109:
* @throws java.io.IOException io exception
68:
110:
69:
// Define o objeto que será usado para enviar dados
111:
*/ protected void doPost(HttpServletRequest
// (OutputStream) 70:
ServletOutputStream sos = response.getOutputStream();
request, HttpServletResponse response) 112:
throws ServletException, IOException {
113:
// Define o objeto que será usado para receber
71:
// dados (InputStream)
72:
// Encapsula o OutputStream
114:
73:
DataOutputStream dos = new DataOutputStream(sos);
115:
74: 75:
/* se for um pedido para os maiores recordes ele varre o SortedSet
76:
* de recordes e caso não exista o número de recordes a ser devolvido
77:
* então devolve um recorde com nome “aaaaa” e valor 0
78:
116:
// Encapsula o InputStream
117:
DataInputStream dis = new DataInputStream(sis);
118: 119:
/* Recebe os recordes usando o protocolo:
120:
* int: score
121:
* UTF: name
122:
*/
79:
ServletInputStream sis = request.getInputStream();
if(“melhores”.equals(request.getParameter(“action ”))){
*/
123:
Integer score = new Integer(dis.readInt());
124:
String name = dis.readUTF();
125:
80:
126:
// Adiciona o recorde recebido no SortedSet
81:
Iterator it = recordes.iterator();
127:
recordes.add(new Record(name,score));
82:
for(int i=0;i<TOP;i++){
128:
83:
if(it.hasNext()){
129:
// Fecha os streams usados
84:
Record current = (Record)it.next();
130:
if(dis != null){
85:
dos.writeInt(current.getHiscore().
131:
intValue()); 86:
dos.writeUTF(current.getName());
87:
} else {
}
133:
if(sis != null){
134:
sis.close();
88:
dos.writeInt(0);
135:
89:
dos.writeUTF(“aaaaa”);
136:
90:
}
91:
}
92:
}
93:
138:
* Returns a short description of the servlet.
140:
* @return informações sobre o servlet
141:
95:
if(dos!=null) {
142:
dos.close(); }
98:
if(sos!=null){
99:
101:
} }
*/ public String getServletInfo() {
143:
return “MobileInvadersServlet, version 1.0.1 \n” +
144:
sos.close();
100:
/**
139:
// Fecha os streams usados
97:
} }
137:
94:
96:
dis.close();
132:
“Author: Carlos Eduardo Lima Borges <carloslimaborges@gmail.com>”;
145:
}
146: 147: }
78 3º Edição
wm03.indb 78
30/6/2005 17:34:36
Jogos
linha 64) foi sobrescrito para retornar um valor significativo do recorde com nome e pontuação.
O servidor Para processar os dados enviados pelo celular usaremos um servidor J2EE, ou seja, que suporta Servlets. Servlets são classes Java que processam requisições e constroem respostas dinamicamente. Como estamos interessados somente em tratar conexões HTTP, usaremos uma classe de servlet escrita especificamente para isso: HttpServlet. Os dois métodos específicos de HttpServlet que nos interessam são: doPost(HttpServletRequest request, HttpServletResponse response)
doGet(HttpServletRequest
request,
7. Criando um novo Servlet.
HttpServletRes-
ponse response)
Esses métodos são usados para tratar as requisições HTTP de métodos POST e GET. No nosso jogo Mobile Invaders, usamos o método HTTP GET para enviar os melhores recordes do servidor ao celular e o método HTTP POST para enviar o melhor recorde do celular ao servidor. Dessa forma, quando usamos a classe MobileInvadersClient.SendRecord ( 8 – linhas 145 a 199) a resposta será dada pelo método doPost()( 11 – linhas 111 a 136) 8. Definições do novo Servlet.
5. Criando uma aplicação web.
6. Definições da aplicação web.
e quando usamos a classe MobileInvadersClient.GetRecords ( 8 – linhas 206 a 256) a resposta será dada pelo método doGet() ( 11 – linhas 64 a 101). Vejamos agora como criar o Servlet no NetBeans 4.0 (veja 3 para a criação de uma aplicação Web sem a ajuda do NetBeans). Abra o NetBeans 4.0, Clique em “File New Project...” ou aperte CTRL+SHIFT+N. Depois disso, na Categoria “Web” escolha “Web Application” e selecione “Next >”, como mostra a 5. Complete os dados da aplicação web como mostra a 6. É importante manter o “Context Path” igual ao contexto definido na variável “servlet” do celular, no nosso caso usamos “/mobileinvaders” como contexto da aplicação web. A opção “Set as Main Project” se for marcada fará com que a sua aplicação web rode no NetBeans toda vez que o botão “Run Main Project” ou F6 forem pressionados. “Project Name” e “Project Location” são nomes quaisquer e nossa sugestão para estes campos está dada na figura. Depois de preencher tudo clique no botão “Finish”. Vamos criar agora um Servlet. Para isso, clique com o botão direito do mouse no projeto criado e escolha “New Servlet...”, como mostra a 7. Preencha os dados do Servlet, lembrando mais uma vez da variável “servlet” do celular, que deve estar sincronizada com o “Context Path” e com o nome de classe (Class Name) do Servlet. No nosso caso o “Class Name” é “MobileInvadersServlet”. Defina o pacote do servlet e clique em “Next >”. Como mostra a 8.
WebMobile 79
wm03.indb 79
30/6/2005 17:34:36
10. Criando a classe de apoio Record no servidor.
11. Definições da classe Record.
9. Configurando o Servlet.
A 9 mostra o mapeamento e nomeação interna do Servlet. Deixe os valores preenchidos e clique no botão “Finish”, pois essa tela serve apenas para não precisarmos editar as configurações do arquivo web.xml manualmente. O arquivo web.xml é o descritor de distribuição de uma aplicação web e possui informações como: Servlets pertencentes à aplicação web; Tempo para que uma sessão expire; Arquivos buscados por padrão (index.jsp, index.html, index.htm); Mapeamentos de url de Servlets. Para finalizar, criamos a classe de apoio: Record, como mostra
a 10. Para isso, clique com o botão direito do mouse no nome do projeto ou no pacote em que a classe Record vai estar e escolha: “New Java Class...”. Preencha a tela que aparece com o nome da classe “Class Name”: ‘Record’ e deixe essa classe no mesmo pacote que o servlet. Como mostra a 11. Agora edite a nova classe Record criada para que fique igual à classe que explicamos e mostramos na 10. O próximo passo é modificar o Servlet da maneira que desejamos. Começamos excluindo o método criado automaticamente: processRequest(), pois ele centraliza o tratamento das requisições GET e POST em um único método, mas queremos tratar as requisições separadamente. Depois importamos o pacote java. util.*. Definimos os atributos necessários ao nosso Servlet, isto é, um SortedSet para guardar os recordes ordenadamente e um inteiro representando o número de recordes que deve ser enviado ao celular representando os melhores. Sobrescrevemos então, os métodos doGet() ( 11 – linhas 64 a 101) e doPost() ( 11 – linhas 111 a 136), para tratar as requisições recebidas e o método getServletInfo() ( 11 – linhas 142 a 145) para retornar alguma informação útil sobre o Servlet. Os atributos que definimos, os métodos que modificamos e o código gerado automaticamente pelo NetBeans podem ser vistos na 11. Temos agora todos os elementos que precisamos para criar uma conexão entre o celular e o servidor J2EE. Agora, devemos compilar o servlet e a classe Record que criamos no lado do servidor. Para isso, no NetBeans clique com o botão direito do mouse sobre o projeto do Servidor Mobile Invaders e escolha o comando “Build Project”. O arquivo de distribuição mobileinvaders.war será criado e colocado na pasta “dist” do seu projeto NetBeans. Copie esse arquivo para o diretório de distribuição do seu servidor de aplicações J2EE para distribuí-lo (deploy). Por exemplo, caso você esteja utilizando o Tomcat, o diretório de distribuição é o “webapps”. Caso você ainda não tenha um projeto no Wireless TollKit com os códigos fontes do Mobile Invaders, crie agora no KToolBar. Para isso, siga os seguintes passos no KTollBar: 1. File New Project... 2. Project Name: MobileInvaders MIDlet Class Name: br.unb.inovamobile.MobileInvadersMidlet Daqui em diante troque “C:\WTK22\” pelo diretório de instalação do seu WTK. O diretório “C:\WTK22\apps\MobileInvaders” deve ter sido criado. 3. Coloque todos os códigos fonte na pasta “C:\WTK22\apps\ MobileInvaders\src“ e todas as figuras na pasta “C:\WTK22\ apps\MobileInvaders\res”. 4. No KtoolBar escolha um device compatível com os Séries 60 da Nokia para compilar o Mobile Invaders (o Series_60_MIDP_Concept_SDK_Beta_0_3_1_Nokia_Edition, por exemplo). Para maiores detalhes nesses processos veja os sites para de-
80 3º Edição
wm03.indb 80
30/6/2005 17:34:39
Jogos senvolvedores: wireless.java.sun.com e www.forum.nokia.com e os artigos na primeira edição da WebMobile: Introdução ao J2ME e Desenvolvimento de Jogos J2ME/MIDP para celular. Agora que já temos um servidor aguardando por requisições HTTP, devemos preparar um pacote de distribuição J2ME, para que o celular possa acessar o servidor pela internet com nossa aplicação. Execute o KToolbar do J2ME Wireless Toolkit, abra o projeto do Mobile Invaders e escolha o menu: Project Package Create Package. Os arquivos “MobileInvaders.jar” e “MobileInvaders. jad” serão criados no diretório “bin” do seu projeto no WTK. (C:\WTK22\apps\MobileInvaders\bin, por exemplo). Instale o .jar no celular e divirta-se. Veja a 3 para entender como o NetBeans gera um pacote de distribuição do tipo WAR quando escolhermos o comando “Build Project”.
pois apenas definem o Context Path da aplicação web. Podemos agora compactar os arquivos nessa estrutura de diretórios e salvar o arquivo gerado como mobileinvaders.war e teremos nosso Web ARchive feito sem precisar de usar o NetBeans. Para compactar podemos usar um programa compatível com o WinZIP ou usar o programa jar que já vem com o J2SE SDK. Para usar o jar na linha de comandos, vá até o diretório raiz que contem as classes compiladas e os arquivos de configuração (mobileinvaders da 12 por exemplo) e dê o comando: jar cf mobileinvaders.war *
12. web.xml <?xml version=”1.0” encoding=”UTF-8”?> <web-app version=”2.4” xmlns=”http://java.sun.com/xml/ ns/j2ee” xmlns:xsi=”http://www.w3.org/2001/XMLSchemainstance” xsi:schemaLocation=”http://java.sun.com/xml/ns/ j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”>
3. Fazendo a aplicação Web sem a ajuda do NetBeans Aplicações web em Java geralmente são distribuídas em um pacote no formato WAR, que significa Web ARchive. Um WAR é um arquivo compactado e possui uma estrutura de diretórios semelhante à da 12. Existe um diretório WEB-INF que fica invisível para o cliente. Ficam nesse diretório: as classes servlet, no subdiretório “classes” o arquivo web.xml, que pode ser visto na 12 e já foi explicado na 9. Veja que a estrutura gerada pelo pacote ao qual o servlet pertence é mantida nesse diretório. No diretório META-INF fica o arquivo context.xml mostrado na 13. Esse arquivo contém informações sobre o contexto em que a aplicação web vai estar, ou seja, se ela estiver no contexto “/mobileinvaders” poderemos acessá-la pelo endereço http://servidor_j2ee.com:8080/ mobileinvaders caso o servidor esteja rodando na porta 8080 e esteja localizado em “servidor_j2ee.com”.
<servlet> <servlet-name>MobileInvadersServlet</servlet-name> <servlet-class>br.com.devmedia.webmobile3. mobileinvaders.MobileInvadersServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MobileInvadersServlet</servlet-name> <url-pattern>/MobileInvadersServlet</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file> index.jsp </welcome-file> <welcome-file>
Como veremos, as configurações são intuitivas. Na 12 a definição de um servlet está fechada por tags <servlet>, o mapeamento é feito por tags <servlet-mapping>, as configurações de sessão são feitas na tag <session-config> e a lista de arquivos padrões estão numa lista <welcome-file-list>. Todas essas configurações pertencem à aplicação web e, portanto, ficam entre tags do tipo <web-app>. As configurações de context.xml ( 13) são ainda mais simples,
index.html </welcome-file> <welcome-file> index.htm </welcome-file> </welcome-file-list> </web-app>
13. context.xml <?xml version=”1.0” encoding=”UTF-8”?> <Context path=”/mobileinvaders”> <Logger className=”org.apache.catalina.logger. FileLogger” prefix=”mobileinvaders.” suffix=”.log” timestamp=”true”/> </Context>
12. Estrutura de diretórios de um WAR
WebMobile 81
wm03.indb 81
30/6/2005 17:34:40
na sintaxe acima estamos criando um arquivo jar: “c” (de create), enviando seu conteúdo para um arquivo: “f” (de file). O arquivo criado terá o nome “mobileinvaders.war” e iremos pegar todos os arquivos, diretórios e subdiretórios recursivamente a partir do diretório atual: “*”.
Seu Web ARchive está criado e pronto para ser distribuído em qualquer servidor J2EE. Para mais comandos do programa jar, digite apenas jar na linha de comando e veja as opções disponíveis.
Conclusões GSM World - What is GPRS? www.gsmworld.com/technology/gprs/intro.shtml Commercial GPRS Quality Requirements www.gsmworld.com/technology/gprs/presentations/ gprs_congress_may2002/mika_perttila.pdf RFC 3629 - UTF-8, a transformation format of ISO 10646 www.ietf.org/rfc/rfc3629.txt The Generic Connection Framework http://developers.sun.com/techtopics/mobility/midp/ articles/genericframework/ RFC 1945 - Hypertext Transfer Protocol -- HTTP/1.0 www.ietf.org/rfc/rfc1945.txt RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 www.ietf.org/rfc/rfc2616.txt The Life Cycle of a Thread http://java.sun.com/docs/books/tutorial/essential/ threads/lifecycle.html Tutorial Tomcat - Instalação e Configuração www.mhavila.com.br/topicos/java/tomcat.html Apache Jakarta Tomcat http://jakarta.apache.org/tomcat/index.html Wireless Development Tutorial Part II http://developers.sun.com/techtopics/mobility/midp/ articles/tutorial2/ JSR-30 Connected, Limited Device Configuration (Final Release) www.jcp.org/aboutJava/communityprocess/final/jsr030/ JSR-37 Mobile Information Device Profile (MIDP) (Final Release) www.jcp.org/aboutJava/communityprocess/final/jsr037/ JSR-118 Mobile Information Device Profile 2.0 (Final Release)
Ao longo desta série de artigos, estivemos acompanhando todo o processo de criação de um jogo até a sua construção e evolução na tentativa de aumentar atratividade e conseqüentemente a vida útil de um jogo. Diversas funcionalidades foram desenvolvidas utilizando tecnologias variadas para o desenvolvimento de aplicações para dispositivos móveis. Esperamos, com isso, que o leitor tenha aprendido ao final desta série os conceitos de: Criação de jogos; Armazenamento persistente no dispositivo móvel usando RMS; Comunicações móveis usando HTTP; Generic Connection Framework; Integração de um servidor J2EE e uma aplicação móvel. Encorajamos o leitor a se aprofundar nos assuntos em que foi despertado maior interesse. O leitor pode, por exemplo, incrementar o servidor tornando o armazenamento dos recordes em um armazenamento persistente usando banco de dados, caso o armazenamento persistente seja o foco do seu interesse. Outra sugestão é a criação de diferentes níveis de jogos, acrescentando labirintos ou inimigos especiais, caso a criação de jogos desperte seu interesse. Um serviço interessante para quem se interessou pelas comunicações móveis é fazer um bloco de anotação on-line, ao invés de carregar um bloco de papel. O código completo com o jogo Mobile Invaders utilizado como mecanismo de aprendizado durante esta série de artigos pode ser obtido no portal da WebMobile. Encerramos o artigo com a certeza de termos contribuído para o desenvolvimento J2ME, usando como exemplo um jogo completo com diversas funcionalidades.
Carlos Eduardo Lima Borges (carlos. borges@gmx.net) faz parte do grupo Inova Mobile e é Sun Certified Programmer. Especializouse em integrar aplicações MIDP com JSP e Servlets. O grupo Inova Mobile é formado por professores e alunos da Universidade de Brasília e é voltado para o desenvolvimento de aplicativos para o ambiente móvel entre eles: jogos, aplicações cliente-servidor e ferramentas (API`s) para auxílio no desenvolvimento.
Faça o download no site: www.devmedia.com.br/webmobile/downloads
82 3º Edição
wm03.indb 82
30/6/2005 17:34:41
wm03.indb 83
30/6/2005 17:34:42
wm03.indb 84
30/6/2005 17:34:49