连接丢失 — 移动应用中最常见和最令人烦恼的现象之一。用户失去对数据的访问,操作中断,应用冻结或崩溃。根据Google Android Developer Blog的数据,70%的用户会在应用崩溃或冻结两次后将其删除。我们将分析连接丢失的原因以及构建容错通信的方法。
要点
断开连接 — 用户术语,描述应用失去与服务器的连接、停止响应操作或以错误终止的情况。从技术上讲,可能是:网络错误(超时、DNS故障)、ANR(UI线程阻塞)、崩溃(未处理的异常)或竞态条件(race condition)。
对用户来说,所有这些场景看起来都一样:应用停止工作。对开发人员来说,区别在于诊断和修复的方法。网络错误通过重试机制解决,ANR通过将操作移出UI线程解决,崩溃通过异常处理解决。
根据Crittercism(现为Apteligent)的数据,普通移动应用每次崩溃会损失1-2%的用户。对于拥有100万用户的应用,这意味着每个错误会导致1-2万次安装损失。这在金融和医疗领域尤为关键。
网络不稳定 — 移动设备不断在Wi-Fi和移动网络之间切换,进入无覆盖区域(地铁、电梯、地下室)。每次切换都会导致暂时的连接丢失,应用必须正确处理。
超时 — 如果服务器在设定时间内(通常10-30秒)未响应,客户端抛出SocketTimeoutException。没有反馈的长超时会被用户视为冻结。建议将超时设置为不超过15秒。
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
竞态条件(race condition)— 当多个线程在没有同步的情况下同时读取和写入相同数据时发生。例如,在UI线程中从缓存加载数据与从网络更新缓存同时进行,可能导致显示过时或不正确的数据。
离线优先 — 架构模式,其中本地存储(Room、CoreData)是唯一的真相来源。网络用于后台数据同步。即使用户没有网络,也始终从本地缓存查看最新数据。
仓库模式 — 数据的统一入口点,决定从网络还是缓存获取数据。仓库将数据源从ViewModel和UI中抽象出来。在发生网络错误时,仓库自动切换到本地源。
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) // 网络错误时返回缓存
} else {
Result.failure(e)
}
}
}
}
断路器 — 服务器不可用时防止请求洪流的保护模式。连续N次错误后,断路器打开,所有请求立即返回错误而不尝试连接。经过一定的超时时间后,断路器进入半开状态进行试探性请求。
指数退避 — 标准重试机制。第一次失败后等待1秒,第二次后等待2秒,然后是4、8、16。限制最大尝试次数(通常3-5次),以免过载服务器和电池。
用户反馈 — 在网络错误时显示易懂的消息:"无连接"、"服务器暂时不可用"、"请检查网络"。使用Snackbar或Inline State View。切勿向用户显示技术性错误(HTTP 500、SocketException)。
ConnectivityManager — 用于监控网络的Android API。让应用能够响应变化:网络丢失时显示占位符,恢复时自动更新数据。在iOS中使用Network框架的NWPathMonitor。
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)— 移动应用的标准崩溃报告工具。收集所有未处理异常的堆栈跟踪、操作系统版本、设备型号和崩溃时间。允许对错误进行分组并分配修复责任人。
Sentry — Crashlytics的替代方案,支持性能监控。允许跟踪特定事务(例如"用户认证")并查看错误发生在哪一步。性能追踪有助于区分网络超时和应用逻辑中的错误。
Timber — Android的日志库,根据类自动添加标签。在debug构建中记录所有网络请求和响应。在release构建中 — 仅通过Crashlytics.setCustomLog记录错误和警告。
| 工具 | 类型 | 使用时机 |
|---|---|---|
| Crashlytics | 崩溃报告 | 始终在release中 — 自动收集崩溃 |
| Sentry | 崩溃+性能 | 需要分析特定用户场景时 |
| Timber | 日志 | Debug:完整日志;Release:仅错误 |
| HTTP Toolkit | 网络调试 | 本地拦截和分析HTTP流量 |
根据Firebase Summit 2023的数据,实施了Crashlytics + Performance Monitoring的应用将关键错误的平均检测和修复时间从3天缩短到4小时。建议对频率超过活跃用户0.1%的每次崩溃设置警报。
常见问题解答
如果崩溃没有被Crashlytics捕获,请检查原生崩溃(SIGSEGV、SIGABRT)— 它们不会被Java/Kotlin异常处理器处理。在Android中,这可能是JNI的原生内存泄漏,在iOS中是EXC_BAD_ACCESS。使用Breakpad(Android)或PLCrashReporter(iOS)收集原生崩溃堆栈跟踪。
使用Network Link Conditioner(内置于iOS,Android有Facebook Network Connection Class或Developer Options > Network > Select network type设置)。设置延迟500-3000毫秒和丢包5-30%。也可以使用Charles Proxy或mitmproxy模拟网络延迟和断开。
ANR当UI线程被阻塞超过5秒时发生。网络请求应在后台线程执行:协程(viewModelScope.launch(Dispatchers.IO))、RxJava(subscribeOn(Schedulers.io))或用于同步的WorkManager。始终在HTTP客户端上设置超时 — 缺少超时可能导致永久阻塞。
竞态条件 — 操作结果取决于线程执行顺序的情况。例如,用户快速按下"发送"按钮两次,请求被发送两次。解决方案:使用Mutex、单线程执行器或状态机(在首次点击后禁用按钮)。在Kotlin中使用协程中的Mutex或@Synchronized注解。
对移动应用应用混沌工程:在操作期间断开网络、模拟高延迟、在Wi-Fi和移动网络之间切换、让系统杀死进程。工具:Facebook Network Connection Class、Charles Proxy、iOS Network Link Conditioner。在CI/CD中通过AndroidTest Orchestrator添加不同网络条件下的UI测试。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。