Aplicativos mobile portáveis Elvis Pfützenreuter 31 de maio de 2015 1
ii Copyright c 2015 Elvis Pfützenreuter. Todos os direitos reservados. Todas as marcas mencionadas ou mostradas em figuras pertencem aos respectivos donos. Todas as figuras são de autoria de Elvis Pfützenreuter. Livro gerado em L A TEX.
Índice 1 O jardim murado 1 2 Pás e picaretas 7 3 Uma cobaia 29 4 Firefox OS 41 5 Android 51 6 ios 91 7 Windows Phone 125 8 Estudos de caso 139 Referências 151 vii
Índice Detalhado 1 O jardim murado 1 1.1 Um pouco de história... 1 1.2 O status quo... 3 1.3 As implicações para você... 4 1.4 O plano de escape... 5 2 Pás e picaretas 7 2.1 Arquitetura... 7 2.1.1 O padrão Model-View-Controller... 7 2.1.2 Model portável... 9 2.1.3 Model e View portáveis... 9 2.1.4 HTML5-shell...................... 10 2.1.5 Escolhendo a arquitetura................ 11 2.2 HTML5 ou não HTML5.................... 12 2.2.1 Aplicativos HTML5-shell............... 14 2.2.2 Apps puramente HTML5............... 15 2.3 E o J2ME?............................. 16 2.4 Frameworks de terceiros.................... 16 2.5 Telefones e tablets........................ 18 2.6 A linguagem de programação para o Model......... 19 2.6.1 C............................. 20 2.6.2 C em cada plataforma................. 21 2.6.3 C++............................ 22 2.6.4 Python.......................... 22 2.6.5 Lua............................ 23 2.6.6 Javascript......................... 23 viii
ÍNDICE DETALHADO ix 2.6.7 Outras linguagens................... 24 2.7 Testando............................. 25 3 Uma cobaia 29 3.1 Photo Friend........................... 29 3.1.1 Fotografia básica.................... 31 3.1.1.1 Exposição fotográfica............ 31 3.1.1.2 Profundidade de campo........... 33 3.2 Arquitetura............................ 34 3.3 Rodando na Web........................ 37 3.4 Da Web para as plataformas móveis............. 40 4 Firefox OS 41 4.1 Porte do Photo Friend...................... 42 4.2 Empacotado ou Open Web................... 43 4.3 Ecossistema........................... 45 4.4 Linguagens da plataforma................... 46 4.5 Telas................................ 46 4.6 Particularidades do HTML5.................. 46 4.6.1 Escalonamento de viewport.............. 47 4.6.2 Orientação de tela.................... 47 5 Android 51 5.1 Portes do Photo Friend..................... 51 5.1.1 HTML5-shell...................... 52 5.1.2 View nativa....................... 56 5.1.2.1 WebView escondida como interpretadora do Model................... 63 5.1.2.2 Rhino como interpretador do Model.... 68 5.1.2.3 Problemas do Rhino............. 79 5.2 Ecossistema........................... 80 5.3 Fragmentação.......................... 81 5.4 Linguagens da plataforma................... 83 5.4.1 Native Development Kit................ 84 5.4.2 JARs........................... 85 5.5 Tamanhos de tela........................ 85
x ÍNDICE DETALHADO 5.6 Apps HTML5.......................... 86 5.6.1 Diferenças entre versões................ 86 5.6.2 Viewport da tela..................... 87 5.6.3 Orientação de tela.................... 88 5.6.4 Eventos de toque.................... 90 6 ios 91 6.1 Portes do Photo Friend..................... 91 6.1.1 Web app......................... 91 6.1.2 Aplicativo HTML5-shell................ 92 6.1.2.1 App baseado em UIWebView........ 93 6.1.2.2 Aplicativo baseado em WKWebView... 98 6.1.3 View nativa....................... 103 6.2 Ecossistema........................... 116 6.3 Linguagens da plataforma................... 116 6.3.1 Objective-C....................... 117 6.3.2 Swift........................... 117 6.4 Tamanhos de tela........................ 118 6.5 Orientação de tela........................ 119 6.6 Aplicativos HTML5....................... 121 6.6.1 UIWebView versus WKWebView........... 121 6.6.2 Viewport de tela..................... 122 6.6.3 Eventos de toque.................... 122 6.6.4 Aplicativos puramente HTML5............ 123 7 Windows Phone 125 7.1 Portes do Photo Friend..................... 125 7.1.1 HTML5-shell...................... 126 7.1.2 View nativa....................... 130 7.2 Ecossistema........................... 135 7.3 Linguagens............................ 136 7.4 Resoluções de tela........................ 136 7.5 Orientação de tela........................ 136 7.6 Aplicativos HTML5....................... 137 7.6.1 Viewport de tela..................... 138
ÍNDICE DETALHADO xi 8 Estudos de caso 139 8.1 Calculadora financeira..................... 139 8.1.1 App HTML5-shell.................... 141 8.1.2 View específica para Android............. 141 8.1.3 Calculadora com contadores mecânicos....... 143 8.2 Framework de saúde e assistência............... 145 8.2.1 Antidote......................... 146 8.2.2 Antidote no Android.................. 149 Referências 151
8 CAPÍTULO 2. PÁS E PICARETAS Model Controller View Figura 2.1: O padrão Model-View-Controller. ses que nunca tomam a iniciativa para nada. Este livro coloca o Model num papel mais proeminente. Pela VIEW (visão) nós entendemos o código que toma conta exclusivamente da interface gráfica, incluindo layout, animações, tratamento da orientação da tela (retrato/paisagem), diferentes resoluções, etc. Na prática, a maioria das plataformas força o programador a implementar parte da lógica da View dentro de um módulo Controller uma classe descendente de UIViewController no ios, ou descendente de Activity no Android. Por CONTROLLER (controlador) entende-se a camada de código que adapta a View ao Model, permitindo que trabalhem juntos. Idealmente, esta camada deve ser muito fina. Muitas aplicações, inclusive as minhas, tendem a possuir Controllers hipertrofiados. Isto acontece em parte por falta de planejamento de arquitetura, e em parte porque os frameworks encorajam este anti-padrão. Quando você cria um projeto de aplicativo a partir de um template, não há um esqueleto de Model. Mas sempre há uma classe Controller (no Android, é a Activity), e o programador vai enfiando código nessa classe, pois já está lá mesmo. Quando você olha de novo, já tem nas mãos um Controller enorme, monolítico, não portável, que usurpou a função do Model. Em todo caso, agora que já concordamos com uma nomenclatura, vamos discutir algumas arquiteturas possíveis para nosso aplicativo (além do caso trivial onde todos os componentes são escritos na linguagem pri-
2.1. ARQUITETURA 9 mária da plataforma, e nenhum deles é portável). 2.1.2 Model portável Esta arquitetura, ilustrada na Figura 2.2, é o menor nível de portabilidade que você deve perseguir. Não se contente com menos! Uma View nativa entrega a interface de usuário da melhor qualidade possível, porque você está liberado para usar todos os truques de UI da plataforma. Model portável Controller View nativa Figura 2.2: Arquitetura Model portável O Controller é sempre escrito de forma não-portável, porque precisa interagir com a View nativa, e ao mesmo tempo precisa preparar o terreno para o Model portável. Por exemplo, se o Model é escrito em Javascript, o Controller toma conta de carregar e servir o interpretador Javascript. 2.1.3 Model e View portáveis Outra possibilidade, ilustrada na Figura 2.3, possui um Model portável e também uma View portável, colados por um Controller nativo. O ponto desta arquitetura é o desacoplamento total de componentes. A View pode ser reutilizada com outro Model, e o Model pode funcionar independentemente de uma View em particular. Bem, e como desenvolver uma View portável?
4.2. EMPACOTADO OU OPEN WEB 43 Figura 4.2: Ferramenta Firefox WebIDE 4.2 Empacotado ou Open Web O manifesto incluso no código-fonte de exemplo, e listado abaixo para ilustração, é apropriado para aplicativos empacotados. Conforme sugere o nome, aplicativos empacotados são distribuídos no formato Zip, via Web ou via loja. { "name": "Photo Friend Package", "description": "Photo utility", "launch_path": "/index.html", "icons": { "30": "/icon30.png", "32": "/icon32.png", "60": "/icon60.png", "90": "/icon90.png", "120": "/icon120.png", "128": "/icon128.png", "256": "/icon256.png", "developer": { "name": "Elvis Pfutzenreuter", "url": "https://epxx.co/"
44 CAPÍTULO 4. FIREFOX OS, "default_locale": "en", "orientation": ["portrait-primary"], "fullscreen": "true", "version": "1.2", "installs_allowed_from": [ "https://marketplace.firefox.com" ] Por outro lado, existem os aplicativos Open Web páginas Web como qualquer outra, mas que podem ser instaladas e usadas offline. Nós oferecemos esta opção na página do livro. O manifesto Open Web é parecido com o manifesto de pacote; as diferenças concentram-se nas propriedades que contêm URLs, como pode ser visto abaixo. { "name": "Photo Friend Page", "description": "Photo utility", "launch_path": "index.html", "icons": { "30": "https://epxx.co/wpma/photo/icon30.png", "32": "https://epxx.co/wpma/photo/icon32.png", "60": "https://epxx.co/wpma/photo/icon60.png", "90": "https://epxx.co/wpma/photo/icon90.png", "120": "https://epxx.co/wpma/photo/icon120.png", "128": "https://epxx.co/wpma/photo/icon128.png", "256": "https://epxx.co/wpma/photo/icon256.png", "developer": { "name": "Elvis Pfutzenreuter", "url": "https://epxx.co/wpma/photo", "default_locale": "en", "orientation": ["portrait-primary"], "fullscreen": "true", "version": "1.21", "installs_allowed_from": [ "https://epxx.co" ] Pelo menos na encarnação corrente do Firefox OS, abrir uma página Open Web não provoca a sua instalação automática, nem existe meio de
5.1. PORTES DO PHOTO FRIEND 55 Por último, mas não menos importante, o objeto Activity é ele mesmo adicionado ao escopo Javascript sob o nome Gateway. Através deste objeto, o Javascript pode invocar qualquer método público Java da Activity, e obter um valor de retorno. Ou seja, na direção Javascript-para-Java nós temos uma ponte de mão dupla. O próximo bloco de código da Activity mostra justamente os métodos Java que são invocados a partir do Javascript. public String getprefs() { SharedPreferences sp = getpreferences(activity.mode_private); String cookie = sp.getstring("c1", ""); return cookie; public void alertjs(string text) { Toast.makeText(this, "Alert JS:" + text, Toast.LENGTH_SHORT).show(); public void saveprefs(string c) { SharedPreferences sp = getpreferences(activity.mode_private); SharedPreferences.Editor ed = sp.edit(); ed.putstring("c1", c); ed.commit(); Há algumas regras para conversão automática de tipo entre Java e Javascript, mas eu usaria strings para tudo e codificaria tipos compostos usando JSON. Outras plataformas têm problemas com conversão de tipo, e usar JSON torna seu código mais portável com menos dor-de-cabeça. O método callhtml5() a seguir não é utilizado neste aplicativo. Está incluso no código apenas para ilustrar como o Java pode invocar uma função do Javascript. public void callhtml5() { /* Como invocar uma funcao do Javascript */ WebView webview = (WebView) findviewbyid(r.id.webview); webview.loadurl("javascript:somefunction( bla );");
56 CAPÍTULO 5. ANDROID O truque loadurl("javascript:function()") funciona, mas infelizmente ele não pode retornar dados, portanto qualquer valor retornado pela função Javascript será perdido. É uma ponte de mão única. Se for preciso mandar dados de volta, um callback de Javascript para Java é necessário. Outro problema deste método é o escapeamento da string (aspas que delimitam a string versus aspas dentro da string). Uma solução de força bruta para este problema é codificar qualquer string com Base64, mesmo que já esteja no formato JSON. 5.1.2 View nativa A versão com View nativa utiliza layouts do Android e um Controller escrito em Java, enquanto o Model roda dentro de uma máquina virtual Javascript. Figura 5.2: Photo Friend, View nativa para Android Os controles rotativos são instâncias da classe customizada
94 CAPÍTULO 6. IOS Figura 6.3: Xcode com o projeto baseado em HTML5 aberto, mostrando o fonte da UI HTML tre o Model e o shell Cocoa. Ele remenda (sobrepõe) as versões originais de algumas funções: var ios_data = ""; /* invocado antes do Model ser inicializado */ function preload(b64data) { ios_data = Base64.decode(b64data); /* chamado de volta pelo Model */ function load_cb() { return ios_data; /* chamado de volta pelo Model */ function save_cb(data) { document.location = "photoh:save:" + Base64.encode(data); Não é possível um método Objective-C retornar dados a um chama-
96 CAPÍTULO 6. IOS buto de preferência. Ele também configura a si mesmo como o delegado da UIWebView html. Então ele invoca loadpage, conforme mostrado abaixo. - (void) loadpage { NSURL *url = [NSURL fileurlwithpath:[[nsbundle mainbundle] pathforresource:@"indexa" oftype:@"html"] isdirectory:no]; [html loadrequest:[nsurlrequest requestwithurl:url]]; A página não é carregada da Internet, mas sim dos arquivos incluídos no pacote da aplicação. Quando a carga da página está completa, a Web View chama o método delegado webviewdidfinishload, também implementado nesta classe: - (void)webviewdidfinishload:(uiwebview *)webview { NSUserDefaults *prefs = [NSUserDefaults standarduserdefaults]; NSString *data = [prefs stringforkey: @"data"]; NSString *f = [NSString stringwithformat: @"preload(\"%@\");", data]; [html stringbyevaluatingjavascriptfromstring: f]; f = [NSString stringwithformat: @"init();"]; [html stringbyevaluatingjavascriptfromstring: f];... O método delegado webviewdidfinishload chama as funções Javascript preload() e então init(). Conforme já vimos no código Javascript, a função preload() existe apenas para transferir as preferências para o escopo Javascript antes do código HTML5 começar a rodar. Utilizar stringbyevaluatingjavascriptfromstring: para invocar funções Javascript é bastante conveniente, mas podemos ter problemas com aspas duplas. Novamente, codificar os parâmetros com JSON e Base64 torna tudo mais fácil. O método delegado shouldstartloadwithrequest é chamado quando a camada Javascript tenta carregar outra página, atribuindo algum valor a
8.2. FRAMEWORK DE SAÚDE E ASSISTÊNCIA Figura 8.4: Site comercial SigHealth para desenvolvedores. signove.com. 147 Fonte: A Figura 8.5 mostra o aplicativo HealthServiceTest, uma Activity extremamente simples que serve como exemplo e cliente de teste. Na ilustração, o app está conectado com um oxímetro Nonin 9650. Figura 8.5: App HealthServiceTest em ação. Fonte: oss.signove.com. O Antidote é escrito em C. Por quê?