Perda de conexão — um dos fenômenos mais comuns e frustrantes em aplicações móveis. O usuário perde acesso aos dados, uma operação é interrompida, o aplicativo congela ou fecha. De acordo com o Google Android Developer Blog, 70% dos usuários deletam um aplicativo se ele fecha ou congela duas vezes. Vamos analisar as causas da perda de conexão e formas de construir comunicações tolerantes a falhas.
Pontos principais
Perda de conexão — termo do usuário que descreve uma situação em que o aplicativo perde a conexão com o servidor, para de responder a ações ou termina com um erro. Em termos técnicos, isso pode ser: erro de rede (timeout, falha de DNS), ANR (congelamento da thread de UI), crash (exceção não tratada) ou condição de corrida (race condition).
Da perspectiva do usuário, todos esses cenários parecem iguais: o aplicativo para de funcionar. A diferença para os desenvolvedores está na abordagem de diagnóstico e correção. Erros de rede são resolvidos com mecanismos de repetição, ANR removendo operações da thread de UI, crashes com tratamento de exceções.
De acordo com a Crittercism (agora Apteligent), em média um aplicativo móvel perde 1–2% dos usuários a cada crash. Para um aplicativo com 1 milhão de usuários, isso significa 10–20 mil instalações perdidas por um único bug. Isso é especialmente crítico para aplicativos nos setores financeiro e médico.
Rede instável — dispositivos móveis alternam constantemente entre Wi-Fi e rede celular, entrando em áreas sem cobertura (metrô, elevador, subsolo). Cada alternância causa uma perda temporária de conexão que o aplicativo deve tratar corretamente.
Timeouts — se o servidor não responder dentro do timeout definido (geralmente 10–30 segundos), o cliente lança SocketTimeoutException. Timeouts longos sem feedback são percebidos pelo usuário como congelamento. Recomenda-se definir um timeout de no máximo 15 segundos.
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
Condição de corrida (race condition) — ocorre quando várias threads leem e escrevem os mesmos dados simultaneamente sem sincronização. Por exemplo, carregar dados do cache na thread de UI enquanto atualiza o cache da rede pode levar à exibição de dados desatualizados ou incorretos.
Offline-first — um padrão arquitetural onde o armazenamento local (Room, CoreData) é a única fonte da verdade. A rede é usada para sincronização de dados em segundo plano. O usuário sempre vê dados atualizados do cache local, mesmo sem conexão de rede.
Padrão Repository — um ponto de entrada único para dados que decide se busca dados da rede ou do cache. O repository abstrai a fonte de dados do ViewModel e da UI. Em caso de erro de rede, o repository muda automaticamente para a fonte local.
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUsers(): Result<List<User>> {
return try {
val remote = api.fetchUsers()
dao.insertAll(remote)
Result.success(remote)
} catch (e: IOException) {
val cached = dao.getAll()
if (cached.isNotEmpty()) {
Result.success(cached) // return cache on network error
} else {
Result.failure(e)
}
}
}
}
Circuit Breaker — um padrão que protege o servidor de uma avalanche de requisições quando está indisponível. Após N erros consecutivos, o disjuntor abre, e todas as requisições retornam imediatamente um erro sem tentar conexão. Após um timeout determinado, o disjuntor transita para um estado semiaberto para uma requisição de teste.
Exponential backoff — um mecanismo de repetição padrão. Após a primeira falha, aguarde 1 segundo; após a segunda, 2 segundos; depois 4, 8, 16. Limite o número máximo de tentativas (geralmente 3–5) para não sobrecarregar o servidor e a bateria.
Feedback ao usuário — em caso de erro de rede, mostre uma mensagem clara: “Sem conexão”, “Servidor temporariamente indisponível”, “Verifique sua internet”. Use Snackbar ou Inline State View. Nunca mostre erros técnicos (HTTP 500, SocketException) ao usuário.
ConnectivityManager — API Android para monitoramento de rede. Permita que o aplicativo reaja a mudanças: mostre um placeholder ao perder conexão, atualize automaticamente os dados ao restaurar. No iOS use NWPathMonitor do framework Network.
class NetworkMonitor(private val context: Context) {
private val manager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
fun isOnline(): Boolean {
val network = manager.activeNetwork ?: return false
val caps = manager.getNetworkCapabilities(network) ?: return false
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
Crashlytics (Firebase) — uma ferramenta padrão de relatório de crashes para aplicativos móveis. Coleta stacktraces de todas as exceções não tratadas, versão do SO, modelo do dispositivo e hora do crash. Permite agrupar erros e atribuir responsáveis por correções.
Sentry — uma alternativa ao Crashlytics com suporte a monitoramento de desempenho. Permite rastrear transações específicas (por exemplo, “authorização do usuário”) e ver em qual etapa ocorreu um erro. Performance tracing ajuda a distinguir timeouts de rede de bugs na lógica do aplicativo.
Timber — uma biblioteca de registro para Android com adição automática de tags por classe. Em builds de depuração, registre todas as requisições e respostas de rede. Em builds de lançamento, registre apenas erros e avisos via Crashlytics.setCustomLog.
| Ferramenta | Tipo | Quando usar |
|---|---|---|
| Crashlytics | Relatório de crashes | Sempre em release — coleta automática de crashes |
| Sentry | Crash + Desempenho | Quando precisar perfilar cenários de usuário específicos |
| Timber | Registro | Debug: registro completo; Release: apenas erros |
| HTTP Toolkit | Depuração de rede | Interceptação e análise local de tráfego HTTP |
De acordo com o Firebase Summit 2023, aplicativos que implementaram Crashlytics + Performance Monitoring reduzem o tempo médio de detecção e correção de bugs críticos de 3 dias para 4 horas. Recomenda-se configurar alertas para cada crash com frequência acima de 0.1% dos usuários ativos.
Perguntas frequentes
Se um crash não for detectado no Crashlytics, verifique crashes nativos (SIGSEGV, SIGABRT) — eles não são tratados pelo manipulador de exceções Java/Kotlin. No Android, isso pode ser vazamento de memória nativa do JNI; no iOS, EXC_BAD_ACCESS. Use Breakpad (Android) ou PLCrashReporter (iOS) para coletar stacktraces de crashes nativos.
Use o Network Link Conditioner (integrado no iOS; para Android, use Facebook Network Connection Class ou Developer Options > Network > Select network type). Defina um atraso de 500–3000 ms e perda de pacotes de 5–30%. Você também pode usar Charles Proxy ou mitmproxy para simular latência e desconexões de rede.
ANR ocorre se a thread de UI estiver bloqueada por mais de 5 segundos. Requisições de rede devem ser executadas em uma thread em segundo plano: corrotinas (viewModelScope.launch(Dispatchers.IO)), RxJava (subscribeOn(Schedulers.io)), ou WorkManager para sincronização. Sempre defina timeouts no cliente HTTP — a falta de timeout pode levar a um bloqueio permanente.
Condição de corrida — uma situação em que o resultado de uma operação depende da ordem de execução das threads. Por exemplo, um usuário pressiona rapidamente o botão “Enviar” duas vezes, e a requisição é enviada duas vezes. Solução: use Mutex, executores de thread única ou uma máquina de estados (desative o botão após o primeiro clique). Em Kotlin, use Mutex de corrotinas ou a anotação @Synchronized.
Aplique Chaos Engineering para aplicativos móveis: desconecte a rede durante operações, simule alta latência, alterne entre Wi-Fi e rede celular, mate o processo pelo sistema. Ferramentas: Facebook Network Connection Class, Charles Proxy, iOS Network Link Conditioner. Em CI/CD, adicione testes de UI com diferentes condições de rede via AndroidTest Orchestrator.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também