📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.

🤖 P15: J.A.R.V.I.S. Sync (WorkManager & Tarefas em Segundo Plano)

Bem-vindo à quarta etapa da Fase 7: Arquitetura Avançada & Processamento em Background! 🤖

Neste projeto prático, você aprenderá a arquitetar rotinas de segundo plano persistentes e confiáveis no Android utilizando o Jetpack WorkManager — a biblioteca oficial recomendada pelo Google para tarefas assíncronas que precisam ser executadas com garantia de entrega, mesmo se o usuário fechar o aplicativo ou reiniciar o celular.

Com o tema futurista do sistema de inteligência artificial J.A.R.V.I.S. das Indústrias Stark, construiremos um fluxo de backup dos núcleos da armadura Mark 85. Você aprenderá a aplicar restrições inteligentes de hardware (Constraints), emitir progresso em tempo real através de setProgress() e observar o ciclo de vida da tarefa (ENQUEUED, RUNNING, SUCCEEDED) de forma reativa no Jetpack Compose!

Fluxo Completo de Telas do J.A.R.V.I.S. Sync


📱 Galeria de Telas da Aplicação

1. Configuração & Ocioso 2. Executando (RUNNING 75%) 3. Sucesso (SUCCEEDED 100%)
Tela 1 - Ocioso Tela 2 - Executando Tela 3 - Concluído
Reator Arc em espera com seleção de Constraints. Criptografia ativa, emissão de progresso e túnel TLS. Conclusão atômica persistida com dados de telemetria.

✅ Pré-requisitos e Continuidade

Antes de iniciar este laboratório, certifique-se de ter compreendido:


🎯 Objetivos de Aprendizagem

Ao concluir este projeto autoguiado, você será capaz de:

  1. Implementar um CoroutineWorker: Criar trabalhadores assíncronos não-bloqueantes com Kotlin Coroutines para operações de I/O e rede.
  2. Definir Restrições de Hardware (Constraints): Configurar requisitos de execução inteligente, como exigir conexão Wi-Fi (NetworkType.UNMETERED) e bateria não baixa (setRequiresBatteryNotLow(true)).
  3. Emitir e Observar Progresso Intermediário: Usar setProgress() dentro do Worker e sincronizar com a UI do Jetpack Compose via getWorkInfoByIdLiveData().
  4. Tratar Falhas com Políticas de Retentativa (Backoff): Configurar setBackoffCriteria(BackoffPolicy.EXPONENTIAL) para proteger servidores contra sobrecargas em caso de oscilações de rede.
  5. Aplicar Boas Práticas de Segurança em Background: Evitar esgotamento de recursos (Local DoS / Battery Drain) respeitando os limites do sistema operacional e da bateria.

🏗️ Ciclo de Vida do WorkManager

graph TD
    A[Usuário clica em DISPARAR PROTOCOLO] --> B[OneTimeWorkRequestBuilder com Constraints e Backoff]
    B --> C[WorkManager.enqueue: Enfileira a tarefa no SQLite interno do SO]
    C --> D{Restrições atendidas? Rede / Bateria}
    D -- Não --> E[Estado: ENQUEUED - Aguardando hardware favorável]
    D -- Sim --> F[Estado: RUNNING - Dispara DatabaseBackupWorker.doWork]
    F --> G[Worker emite setProgress: 25%, 50%, 75%]
    G --> H[Jetpack Compose atualiza Reator Arc e Barra de Progresso]
    F -->|Sucesso| I[Result.success -> Estado: SUCCEEDED 100%]
    F -->|Falha| J[Result.retry -> Aplica Backoff Exponencial e Reenfileira]

🛡️ Boas Práticas de Segurança em Segundo Plano (OWASP Mobile)

[!IMPORTANT] Vulnerabilidade de Esgotamento de Bateria e Recursos (Local DoS): No Android antigo, criar threads manuais ou Services infinitos em segundo plano esgotava a bateria do usuário e causava o encerramento forçado do app pelo sistema (Out of Memory Killer).

Como o WorkManager e nossas práticas garantem segurança:

  1. Execução sob Demanda e Respeito ao Doze Mode: O WorkManager acorda a aplicação apenas quando as condições de energia e rede forem favoráveis.
  2. Retentativa com Backoff Exponencial: Impede ataques acidentais de negação de serviço (DDoS) contra a API das Indústrias Stark caso a conexão caia.
  3. Conexões Seguras: O tráfego de dados de backup exige canais criptografados HTTPS / TLS 1.3.
  4. Higiene de Versionamento: O arquivo .gitignore bloqueia arquivos temporários de build e credenciais de ambiente.

📖 Dicionário Técnico do Projeto

Termo / Componente O que é e para que serve?
WorkManager API do Android Jetpack para gerenciar tarefas em segundo plano adiáveis e com garantia de execução.
CoroutineWorker Variante do Worker especializada em Kotlin Coroutines, permitindo suspensões nativas (delay, chamadas de rede assíncronas).
Constraints Conjunto de regras de hardware que determinam quando o trabalho pode rodar (ex: Wi-Fi, carregando na tomada, bateria suficiente).
OneTimeWorkRequest Requisição para executar uma tarefa uma única vez quando as restrições forem satisfeitas.
setProgress(workDataOf(…)) Método para enviar relatórios parciais de progresso do trabalhador para a interface do usuário.
BackoffPolicy.EXPONENTIAL Algoritmo que dobra o intervalo de espera entre tentativas sucessivas após uma falha de execução.

🛠️ Passo a Passo de Implementação

🚀 Passo 1: Criando o Novo Projeto no Android Studio

  1. Abra o Android Studio e clique em New Project (ou File > New > New Project).
  2. Selecione o template Empty Activity (com o ícone do Jetpack Compose 🌌).
  3. Preencha as configurações do projeto:
    • Name: J.A.R.V.I.S. Sync (ou android_p15_jarvis_workmanager)
    • Package name: br.com.curso.jarvis
    • Save location: Pasta do seu projeto no repositório
    • Language: Kotlin
    • Minimum SDK: API 24 ("Nougat"; Android 7.0) ou superior
    • Build configuration language: Groovy DSL (build.gradle) ou Kotlin DSL (build.gradle.kts)
  4. Clique em Finish e aguarde o Gradle sincronizar.

📦 Passo 2: Adicionando a Dependência do WorkManager no Gradle (build.gradle)

Abra app > build.gradle e inclua o WorkManager KTX:

dependencies {
    implementation platform('androidx.compose:compose-bom:2024.02.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    implementation 'androidx.activity:activity-compose:1.8.2'
    implementation 'androidx.core:core-ktx:1.12.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'

    // WorkManager KTX (Processamento Seguro em Background)
    implementation 'androidx.work:work-runtime-ktx:2.9.0'
}

Clique em Sync Now.


📄 Passo 3: Declarando Permissões de Rede no Manifesto (AndroidManifest.xml)

Abra app > src > main > AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:label="J.A.R.V.I.S. Sync"
        android:supportsRtl="true"
        android:theme="@style/Theme.CartaoTreinador">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

⚙️ Passo 4: Criando o DatabaseBackupWorker com Emissão de Progresso

Abra app > src > main > java > br > com > curso > jarvis > MainActivity.kt:

class DatabaseBackupWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        return try {
            // Fase 1: Criptografia dos núcleos
            setProgress(workDataOf("progresso" to 25, "etapa" to "Criptografando Núcleos de IA Mark 85..."))
            delay(1200)

            // Fase 2: Hash de integridade
            setProgress(workDataOf("progresso" to 50, "etapa" to "Gerando Hash de Integridade SHA-256..."))
            delay(1200)

            // Fase 3: Transmissão TLS
            setProgress(workDataOf("progresso" to 75, "etapa" to "Enviando Pacotes via Túnel TLS 1.3..."))
            delay(1200)

            // Fase 4: Conclusão
            setProgress(workDataOf("progresso" to 100, "etapa" to "Sincronização com o Satélite Stark Concluída!"))
            delay(800)

            val outputData = workDataOf(
                "tamanho" to "2.4 GB",
                "servidor" to "Nuvem Stark - Torre de Nova York",
                "timestamp" to System.currentTimeMillis()
            )

            Result.success(outputData)
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

🎨 Passo 5: Tema Futurista Stark Arc (JarvisTheme)

@Composable
fun JarvisTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = darkColorScheme(
            primary = Color(0xFF00E5FF),    // Ciano Reator Arc
            secondary = Color(0xFFFFD54F),  // Dourado Stark
            tertiary = Color(0xFF00E676),   // Verde Concluído
            background = Color(0xFF070B11), // Preto Profundo HUD
            surface = Color(0xFF0F1722),
            surfaceVariant = Color(0xFF182333)
        ),
        content = content
    )
}

📡 Passo 6: Enfileirando o Trabalho com Constraints e Observando o Status

@Composable
fun JarvisScreen() {
    val context = LocalContext.current
    var workRequestId by remember { mutableStateOf<UUID?>(null) }
    var modoWifiExclusivo by remember { mutableStateOf(false) }

    // Observa o WorkInfo emitido pelo WorkManager
    val workInfo by produceState<WorkInfo?>(initialValue = null, key1 = workRequestId) {
        if (workRequestId != null) {
            WorkManager.getInstance(context)
                .getWorkInfoByIdLiveData(workRequestId!!)
                .observeForever { value = it }
        }
    }

    val estadoTrabalho = workInfo?.state
    val progressoPercent = workInfo?.progress?.getInt("progresso", 0) ?: if (estadoTrabalho == WorkInfo.State.SUCCEEDED) 100 else 0

    // Disparo da Requisição com Constraints
    Button(
        onClick = {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(if (modoWifiExclusivo) NetworkType.UNMETERED else NetworkType.CONNECTED)
                .setRequiresBatteryNotLow(true)
                .build()

            val workRequest = OneTimeWorkRequestBuilder<DatabaseBackupWorker>()
                .setConstraints(constraints)
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
                .addTag("stark_backup")
                .build()

            workRequestId = workRequest.id
            WorkManager.getInstance(context).enqueue(workRequest)
        },
        enabled = estadoTrabalho != WorkInfo.State.RUNNING
    ) {
        Text("DISPARAR PROTOCOLO STARK (WORKMANAGER)")
    }
}

🔍 Guia de Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
A tarefa fica presa em ENQUEUED e não inicia As Constraints configuradas não foram atendidas pelo emulador/dispositivo (ex: sem conexão de rede ou bateria baixa). Certifique-se de que o emulador está conectado ao Wi-Fi ou relaxe a restrição para NetworkType.CONNECTED.
IllegalStateException: WorkManager is not initialized properly Conflito na inicialização padrão do WorkManager. Certifique-se de que a biblioteca androidx.work:work-runtime-ktx está devidamente declarada no build.gradle.
O progresso não atualiza na tela O método setProgress() do Worker não foi chamado ou o getWorkInfoByIdLiveData não está sendo observado. Verifique se você está emitindo setProgress(workDataOf(...)) com delay() entre as etapas.
O Worker trava a tela da aplicação Operações síncronas bloqueantes foram chamadas diretamente na thread principal. Herde de CoroutineWorker em vez do legado Worker, executando as operações dentro de doWork().

🏆 Desafios e Upgrades (Mão na Massa!)

  1. Backup Periódico com PeriodicWorkRequestBuilder: Configure a rotina de backup para executar automaticamente uma vez a cada 24 horas usando PeriodicWorkRequestBuilder<DatabaseBackupWorker>(24, TimeUnit.HOURS).
  2. ⛓️ Encadeamento de Tarefas (Work Chaining): Crie um segundo Worker (IntegrityCheckWorker) e encadeie a execução com WorkManager.beginWith(...).then(...).
  3. 🛑 Cancelamento de Tarefas: Adicione um botão “ABORTAR PROTOCOLO” que execute WorkManager.cancelWorkById(workRequestId).

📖 Gabarito Oficial de Código (Para Conferência)

Disponível em: app/src/main/java/br/com/curso/jarvis/MainActivity.kt.


🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:

cd proj_aplicacoes_full_stack/projetos/android_p15_jarvis_workmanager

2. Execute a aplicação e os testes

./gradlew build
./gradlew test
# ou abrir no Android Studio e clicar em Run (Shift+F10)

[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code ou Android Studio, abra o projeto diretamente pela pasta proj_aplicacoes_full_stack/projetos/android_p15_jarvis_workmanager.