Aplicativo Android ONDEVOU
Aplicação nativa para coleta de dados e rastreamento GPS com integração total ao WebView da plataforma.
Fonte única da verdade: globais.json sincronizado entre WebView e componentes nativos.
🚀 Produção
📱 Android
🌐 WebView
📍 GPS
🏭 Cilindros
📦 Produtos
🖨️ Bluetooth
📄 Offline First
5
Activities
3
WebViews
2
Services
7
Selectors
30+
Arquivos
99%
Offline
🏗️ Arquitetura do Sistema
Visão Geral da Arquitetura
graph TB
subgraph WebViewCamada["🌐 WebView (Site ONDEVOU)"]
JS[JavaScript]
LS[localStorage]
end
subgraph NativeCamada["📱 Native Android"]
MA[MainActivity]
CA[ColetaActivity]
LSvc[LocationService]
CPA[ColetasPendentesActivity]
PM[PrinterManager]
end
subgraph Armazenamento["💾 Armazenamento"]
GJ[globais.json
Fonte Única] DB[SQLite
Coordenadas GPS] FILES[Arquivos .json
Coletas Pendentes] end JS -->|Android.gravarGlobaisJson| MA MA -->|salva| GJ MA -->|carrega| GJ CA -->|lê via getters| MA LSvc -->|carregaDadosFixos
lê via getters| MA LSvc -->|salva| DB CA -->|salva/restaura| LS CA -->|fallback| FILES JS -->|acesso direto| LS PM -->|usa dados empresa| MA
Fonte Única] DB[SQLite
Coordenadas GPS] FILES[Arquivos .json
Coletas Pendentes] end JS -->|Android.gravarGlobaisJson| MA MA -->|salva| GJ MA -->|carrega| GJ CA -->|lê via getters| MA LSvc -->|carregaDadosFixos
lê via getters| MA LSvc -->|salva| DB CA -->|salva/restaura| LS CA -->|fallback| FILES JS -->|acesso direto| LS PM -->|usa dados empresa| MA
📁 Principais Componentes
| Classe | Responsabilidade |
|---|---|
MainActivity | WebView principal, interface, gerenciamento de estado |
ColetaActivity | Coleta de cilindros/produtos/etiquetas |
LocationService | Rastreamento GPS com filtro inteligente |
ColetasPendentesActivity | Gerenciamento de coletas offline |
PrinterManager | Impressão Bluetooth térmica (Singleton) |
GlobalLoadingDialog | Loading global para operações assíncronas |
🎯 Filosofia do App
- 📦 Fonte Única da Verdade —
globais.jsoncentraliza dados do WebView - 📍 Rastreamento Inteligente — Filtro por distância/direção/velocidade/tempo
- 📴 Offline First — Coletas salvam em localStorage + fallback para arquivos
- 🔄 Keep Alive — START_STICKY + verificação periódica
- 🔋 Economia de Bateria — Diálogo para desativar otimização
- 🖨️ Impressão Bluetooth — Suporte a impressoras térmicas 58mm
🔄 Fluxo de Dados — globais.json
📝 Estrutura do globais.json (Fonte Única)
{
"dados_fixo": {
"participante": "662691",
"razao_social": "EMPRESA EXEMPLO LTDA",
"cnpj": "12.345.678/0001-90",
"nome_cidade": "São Paulo",
"sigla_uf": "SP",
"id_membro_logado": "12345",
"nome_membro_logado": "João Silva",
"email_membro_logado": "joao@email.com",
"id_romaneio": "ROM-001",
"descricao_romaneio": "Entrega Zona Sul",
"monitoramento_ativo": "S"
}
}
Fluxo de Escrita e Leitura
sequenceDiagram
participant WV as WebView (JavaScript)
participant MA as MainActivity
participant FILE as globais.json
participant CA as ColetaActivity
participant LS as LocationService
participant PM as PrinterManager
Note over WV,FILE: ESCRITA (WebView → Native)
WV->>MA: Android.gravarGlobaisJson(json)
MA->>FILE: FileWriter.write()
MA->>MA: carregarGlobaisJson()
Note right of MA: globais estática atualizada
Note over MA,PM: LEITURA (Native → Componentes)
CA->>MA: MainActivity.getIdParticipante()
CA->>MA: MainActivity.getIdRomaneio()
CA->>MA: MainActivity.getIdMembroLogado()
LS->>LS: carregarDadosFixos()
LS->>MA: MainActivity.getIdParticipante()
LS->>MA: MainActivity.getIdRomaneio()
LS->>MA: MainActivity.getIdMembroLogado()
LS->>MA: MainActivity.globais (monitoramento_ativo)
PM->>MA: MainActivity.getRazaoSocial()
PM->>MA: MainActivity.getCidadeUf()
🔑 Getters Estáticos (MainActivity)
// Acessíveis de qualquer lugar do app
public static String getIdParticipante() {
if (globais != null) {
JSONObject dadosFixo = globais.optJSONObject("dados_fixo");
if (dadosFixo != null) {
return dadosFixo.optString("participante", "0");
}
}
return "0";
}
public static String getIdRomaneio() { ... }
public static String getIdMembroLogado() { ... }
public static String getRazaoSocial() { ... }
public static String getCidadeUf() { ... }
public static String getCnpj() { ... }
📦 Sistema de Coleta de Dados
Fluxo de Coleta (ColetaActivity)
flowchart TD
A1["Teclado Zebra SCAN"] -->|dispatchKeyEvent| F["Fila de Leitura"]
A2["Botao Buscar Cilindro"] --> C1["CilindroSelector"]
A3["Botao Buscar Produto"] --> P1["ProdutoSelector"]
A4["Botao QR Code"] --> Q1["QRCodeScannerActivity"]
C1 -->|onCilindroSelecionado| F
P1 -->|onProdutoSelecionado| F
Q1 -->|ArrayListString| F
F -->|processarProximoDaFila| P["processarCodigo"]
P --> T["getTipoItem"]
T -->|cilindro ou produto ou etiqueta| V{"isItemCompativelComFinalidade"}
V -->|sim| ADD["Adiciona ItemColeta"]
V -->|nao| FEEDBACK["Feedback visual vermelho"]
ADD --> ADAPTER["ColetaAdapter.notifyDataSetChanged"]
ADAPTER --> FIM1["Aguardando proximo item"]
BOT["Botao SALVAR"] --> FORM["mostrarFormularioFinalizacao"]
FORM --> SAVE["salvarColeta"]
SAVE --> JS["salvarNoLocalStorage"]
JS -->|ONLINE| WV["localStorage WebView"]
JS -->|OFFLINE| FILE["Arquivo json"]
SAVE --> LIMPA["Limpa lista atual"]
LIMPA --> PEND["carregarColetasPendentes"]
🏷️ Tipos de Item
| Tipo | Formato | Exemplo |
|---|---|---|
| Cilindro | ondevou_cl_{participante}_{id} | ondevou_cl_662691_12345 |
| Produto | ondevou_pr_{participante}_{id} | ondevou_pr_662691_67890 |
| Etiqueta | ondevou_ea_{participante}_{id} | ondevou_ea_662691_ETQ001 |
⚙️ Metodo carregarDadosFixos()
// ColetaActivity.java
private void carregarDadosFixos() {
if (MainActivity.globais != null) {
JSONObject dadosFixo = MainActivity.globais.optJSONObject("dados_fixo");
if (dadosFixo != null) {
participante = dadosFixo.optString("participante", "0");
idMembro = dadosFixo.optString("id_membro_logado", "0");
nomeMembro = Uri.decode(dadosFixo.optString("nome_membro_logado", "Visitante"));
idRomaneio = dadosFixo.optString("id_romaneio", "");
}
}
}
📋 Estrutura da Coleta Pendente (JSON)
{
"header": {
"titulo": "Entregar cilindro",
"descricao": "Observacao...",
"leituras": 5,
"data": "2026-05-11 14:30:00",
"participante": "662691",
"id_membro": "12345",
"id_romaneio": "ROM-001",
"latitude": "-23.5505",
"longitude": "-46.6333",
"status": "pendente",
"id_finalidade": 3,
"nome_finalidade": "Entregar cilindro"
},
"data": {
"coletar_dados_leituras": {
"0": {"leitura": "ondevou_cl_662691_12345", "quantidade": "1"},
"1": {"leitura": "ondevou_pr_662691_67890", "quantidade": "3"}
}
}
}
📍 Sistema de Rastreamento GPS (LocationService)
Fluxo do LocationService
flowchart TD
JS["JavaScript: startGpsMonitoringService"] --> DIA["sugerirDesativarEconomiaBateria"]
DIA --> START["startForegroundService"]
START --> ONCREATE["LocationService.onCreate"]
ONCREATE --> LOAD["carregarDadosFixos"]
LOAD --> MA["MainActivity.globais"]
ONCREATE --> STARTGPS["iniciarMonitoramento"]
ONCREATE --> SYNC["iniciarSincronizador"]
ONCREATE --> KEEP["iniciarKeepAlive"]
LOC["LocationCallback a cada 8s"] --> PROC["processarCoordenada"]
PROC --> MA2["MainActivity.globais atualizado"]
PROC --> FILTRO{"deveGravarCoordenadaMelhorado"}
FILTRO -->|distancia maior igual 15m| GRAVA
FILTRO -->|direcao maior igual 25 graus| GRAVA
FILTRO -->|velocidade maior igual 8 km/h| GRAVA
FILTRO -->|tempo maior igual 2 minutos| GRAVA
FILTRO -->|nenhuma condicao| DESCARTAR["DESCARTAR"]
GRAVA --> SQL["salvarNoSQLite"]
SQL --> ENVIO["tentarEnviarCoordenada"]
ENVIO -->|sucesso| SUCESSO["enviado = 1"]
ENVIO -->|erro| TENTATIVAS["tentativas++"]
SYNC --> PENDENTES["tentarEnviarPendentes a cada 30s"]
KEEP --> VERIF["verifica servico a cada 10 min"]
VERIF --> REINICIA["reinicia monitoramento se necessario"]
🔍 Filtro Inteligente deveGravarCoordenadaMelhorado
| Condicao | Limiar | Motivo |
|---|---|---|
| Distancia percorrida | maior ou igual 15 metros | Movimento significativo |
| Mudanca de direcao | maior ou igual 25 graus com maior ou igual 5m | Curva ou desvio na rota |
| Mudanca de velocidade | maior ou igual 8 km/h | Aceleracao ou freio brusco |
| Tempo maximo sem gravar | maior ou igual 2 minutos | Fallback de seguranca |
⚙️ Metodo carregarDadosFixos() no LocationService
private void carregarDadosFixos() {
// SEMPRE le do MainActivity fonte unica
participante = MainActivity.getIdParticipante();
idMembro = MainActivity.getIdMembroLogado();
idRomaneio = MainActivity.getIdRomaneio();
// Le monitoramento_ativo diretamente do JSON
if (MainActivity.globais != null) {
JSONObject dadosFixo = MainActivity.globais.optJSONObject("dados_fixo");
if (dadosFixo != null) {
String monitoramentoStr = dadosFixo.optString("monitoramento_ativo", "N");
monitoramentoAtivo = "S".equals(monitoramentoStr);
}
}
}
🔋 Dialogo de Economia de Bateria
private void sugerirDesativarEconomiaBateria() {
if (jaSugeriuBateria) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
jaSugeriuBateria = true;
}
}
}
📌 Chamado por: WebAppInterface.startGpsMonitoringService()
🔌 Integrações e Interfaces
🌐 WebAppInterface (JavaScript ↔ Native)
| Método JS | Método Native | Descrição |
|---|---|---|
Android.gravarGlobaisJson(json) | gravarGlobaisJson() | Salva dados no globais.json |
Android.startGpsMonitoringService() | startGpsMonitoringService() | Inicia rastreamento GPS |
Android.stopGpsMonitoringService() | stopGpsMonitoringService() | Para rastreamento GPS |
Android.abrirQrCodeScanner() | abrirQrCodeScanner() | Abre scanner QR Code |
Android.showLoading(msg) | showLoading() | Mostra loading global |
Android.hideLoading() | hideLoading() | Esconde loading global |
Android.viewUrl(url) | viewUrl() | Visualiza PDF ou baixa APK |
🖨️ PrinterManager (Singleton)
// Obter instância
PrinterManager pm = PrinterManager.getInstance(context);
// Conectar à última impressora usada
pm.connectToLastPrinter(callback);
// Imprimir recibo térmico
pm.printThermalReceipt(receiptText, callback);
// Teste de impressão
pm.printTest(callback);
// Verificar conexão
boolean connected = pm.isConnected();
📌 Impressoras suportadas: 58mm térmicas Bluetooth (ESC/POS)
📡 DataWedge (Zebra TC25)
// Detecção automática do dispositivo
isZebraDevice = DeviceDetector.isZebraTC25();
// Inicialização
dataWedgeScanner = new DataWedgeScanner(this);
dataWedgeScanner.setCallback((barcode, barcodeType) -> {
ColetaActivity coleta = ColetaActivity.getInstance();
if (coleta != null) {
coleta.adicionarBarcodeExterno(barcode);
}
});
dataWedgeScanner.register();
⚙️ Métodos Principais por Classe
📱 MainActivity
carregarGlobaisJson()— Carrega do arquivo para memóriagetIdParticipante()— Getter estáticogetIdRomaneio()— Getter estáticogetIdMembroLogado()— Getter estáticogetRazaoSocial()— Getter estáticogetCidadeUf()— Getter estáticogetInstance()— Retorna instância singletongetWebViewAtivo()— Retorna WebView atualsugerirDesativarEconomiaBateria()— Diálogo bateria
📍 LocationService
carregarDadosFixos()— Lê do MainActivity via gettersprocessarCoordenada()— Processa cada localizaçãodeveGravarCoordenadaMelhorado()— Filtro inteligentesalvarNoSQLite()— Persiste coordenadatentarEnviarCoordenada()— Envia via WebViewtentarEnviarPendentes()— Sincroniza pendentesiniciarKeepAlive()— Mantém serviço vivo
📦 ColetaActivity
carregarDadosFixos()— Lê dados do MainActivityprocessarCodigo()— Adiciona item à listasalvarColeta()— Salva em localStorage/arquivogetTipoItem()— Identifica cilindro/produto/etiquetaisItemCompativelComFinalidade()— Valida compatibilidadeadicionarBarcodeExterno()— Para Zebra TC25getInstance()— Retorna instância singleton
🖨️ PrinterManager
getInstance()— SingletonconnectToLastPrinter()— Conecta à última usadaconnectToPrinter()— Conecta a dispositivo específicoprintThermalReceipt()— Imprime texto (ESC/POS)printTest()— Teste de impressãoisConnected()— Verifica conexão ativahasSavedPrinter()— Verifica impressora salva
📊 Diagramas Completos
Diagrama de Classes (Principais)
classDiagram
class MainActivity {
+static JSONObject globais
+static String getIdParticipante()
+static String getIdRomaneio()
+static String getIdMembroLogado()
+static String getRazaoSocial()
+static MainActivity getInstance()
+WebView getWebViewAtivo()
+carregarGlobaisJson()
+sugerirDesativarEconomiaBateria()
-WebView webViewOndevouCom
-WebView webViewOndevouComBr
}
class ColetaActivity {
+static ColetaActivity getInstance()
+adicionarBarcodeExterno(String)
-carregarDadosFixos()
-processarCodigo(String)
-salvarColeta(String)
-getTipoItem(String)
-isItemCompativelComFinalidade(String, int)
-List~ItemColeta~ itensColetados
}
class LocationService {
-carregarDadosFixos()
-processarCoordenada(Location)
-deveGravarCoordenadaMelhorado(Location)
-salvarNoSQLite(String, Location)
-tentarEnviarCoordenada(long, String, Location)
-tentarEnviarPendentes()
-iniciarKeepAlive()
-String participante
-String idRomaneio
-boolean monitoramentoAtivo
}
class ItemColeta {
-String leitura
-int quantidade
+String getTipo()
+String getLeitura()
}
class PrinterManager {
-static PrinterManager instance
+static PrinterManager getInstance()
+connectToLastPrinter(PrinterCallback)
+printThermalReceipt(String, PrinterCallback)
+printTest(PrinterCallback)
+boolean isConnected()
+boolean hasSavedPrinter()
}
class WebAppInterface {
+gravarGlobaisJson(String)
+startGpsMonitoringService()
+stopGpsMonitoringService()
+viewUrl(String)
+abrirQrCodeScanner()
+showLoading(String)
+hideLoading()
}
MainActivity --> WebAppInterface : contains
MainActivity --> LocationService : starts/uses
ColetaActivity --> MainActivity : reads globais via getters
LocationService --> MainActivity : reads globais via getters
PrinterManager --> MainActivity : reads company data
Diagrama de Sequência — Ativação do GPS
sequenceDiagram
participant JS as JavaScript (WebView)
participant WAI as WebAppInterface
participant MA as MainActivity
participant LS as LocationService
participant PM as PowerManager
JS->>WAI: Android.startGpsMonitoringService()
WAI->>MA: sugerirDesativarEconomiaBateria()
MA->>PM: isIgnoringBatteryOptimizations()
PM-->>MA: false (ainda otimizando)
MA->>JS: AlertDialog "Configurar"
WAI->>LS: new Intent(LocationService.class)
WAI->>LS: startForegroundService()
LS->>LS: onCreate()
LS->>MA: MainActivity.getIdParticipante()
MA-->>LS: "662691"
LS->>MA: MainActivity.getIdRomaneio()
MA-->>LS: "ROM-001"
LS->>MA: MainActivity.globais (monitoramento_ativo)
MA-->>LS: "S"
LS->>LS: iniciarMonitoramento()
LS->>LS: requestLocationUpdates(8000ms)
loop A cada 8 segundos
LS->>LS: onLocationResult()
LS->>MA: carregarDadosFixos() (sempre atualizado)
LS->>LS: deveGravarCoordenadaMelhorado()
alt Distância ≥ 15m
LS->>LS: salvarNoSQLite()
LS->>LS: tentarEnviarCoordenada()
else Descartar
LS->>LS: Log descarte
end
end
Diagrama de Estado — Coleta Pendente
stateDiagram-v2
[*] --> Coletando: Iniciar ColetaActivity
Coletando --> AdicionandoItem: SCAN / Buscar
AdicionandoItem --> Coletando: Item adicionado
Coletando --> ValidandoFinalizacao: Botão SALVAR
ValidandoFinalizacao --> ValidandoFinalizacao: Verifica compatibilidade
ValidandoFinalizacao --> Salvando: Itens compatíveis
ValidandoFinalizacao --> RemovendoIncompativeis: Itens incompatíveis
RemovendoIncompativeis --> Salvando: Usuário confirma
Salvando --> VerificandoOnline: Salvar JSON
VerificandoOnline --> SalvandoOnline: Conexão OK
VerificandoOnline --> SalvandoOffline: Modo offline
SalvandoOnline --> [*]: Salvo em localStorage
SalvandoOffline --> [*]: Salvo em arquivo .json
📝 Changelog
// ======================================================================
// CHANGELOG - ONDEVOU Android App
// ======================================================================
2026-05-11: 📚 v2026.04.12 - Documentação Completa
• 📄 Criação da documentação técnica - Arquitetura, fluxos e diagramas
• 🎯 Refatoração LocationService - Agora lê direto do MainActivity.globais
• 🔄 Fonte única da verdade - globais.json como único armazenamento
• 📊 Diagramas Mermaid - Classes, sequência e estado
2026-04-12: 📍 Melhorias no Rastreamento GPS
• 📏 Filtro inteligente - 15m distância, 25° direção, 8 km/h velocidade
• ⏰ KeepAlive - Verificação a cada 10 minutos
• 🔋 Diálogo bateria - Sugere desativar economia de bateria
2026-04-08: 📦 Coleta Offline
• 💾 Fallback para arquivos - Quando localStorage indisponível
• 🔄 Sincronização automática - Arquivos → localStorage ao voltar online
• 🖨️ Impressão Bluetooth - Suporte a ESC/POS
// ======================================================================
// 📌 PRÓXIMOS PASSOS PLANEJADOS
// ======================================================================
✅ CONCLUÍDO: Documentação completa do App Android
✅ CONCLUÍDO: Refatoração LocationService com fonte única
⏳ EM ANDAMENTO: Melhorias na sincronização de coletas pendentes
📋 PLANEJADO: Dashboard de acompanhamento de entregas em tempo real