Губитак везе — једна од најчешћих и најнепријатнијих појава у мобилним апликацијама. Корисник губи приступ подацима, операција се прекида, апликација се замрзава или пада. Према Google Android Developer Blog, 70% корисника брише апликацију ако се двапут сруши или замрзне. Размотрићемо узроке губитка везе и начине изградње отпорних комуникација.
Главно
Отпада — кориснички термин који описује ситуацију када апликација изгуби везу са сервером, престане да реагује на радње или се заврши грешком. У техничком смислу то може бити: мрежна грешка (timeout, DNS failure), ANR (блокирање UI нити), crash (необрађени изузетак) или race condition (трка услова).
За корисника сви ови сценарији изгледају исто: апликација престаје да ради. Разлика за програмера је у приступу дијагностици и поправци. Мрежне грешке се рјешавају retry механизмима, ANR — измјештањем операција из UI нити, crash — обрадом изузетака.
Према Crittercism (сада Apteligent), просјечна мобилна апликација губи 1-2% корисника при сваком паду. За апликацију са 1 милионом корисника то је 10-20 хиљада изгубљених инсталација по једној грешци. Посебно је критично за апликације у финансијском и медицинском сектору.
Нестабилна мрежа — мобилни уређаји стално прелазе између 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 нити паралелно са ажурирањем кеша из мреже може довести до приказа застарјелих или нетачних података.
Offline-first — архитектонски образац у којем је локално складиште (Room, CoreData) једини извор истине. Мрежа се користи за синхронизацију података у позадини. Корисник увијек види ажурне податке из локалног кеша, чак и када нема мреже.
Repository pattern — јединствена улазна тачка за податке која одлучује да ли ће узети податке из мреже или из кеша. Репозиторијум апстрахује извор података од 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)
}
}
}
}
Circuit Breaker — образац заштите сервера од поплаве захтјева када је недоступан. Након N узастопних грешака, прекидач се отвара и сви захтјеви одмах враћају грешку без покушаја повезивања. Након одређеног тајмаута, прекидач прелази у полуотворено стање за пробни захтјев.
Exponential backoff — стандардни механизам retry. Након првог неуспјеха сачекајте 1 секунду, након другог — 2 секунде, затим 4, 8, 16. Ограничите максималан број покушаја (обично 3-5) да не бисте преоптеретили сервер и батерију.
Повратна информација кориснику — при мрежној грешци прикажите разумљиву поруку: „Нема везе”, „Сервер је привремено недоступан”, „Провјерите интернет”. Користите Snackbar или Inline State View. Никада не приказујте кориснику техничке грешке (HTTP 500, SocketException).
ConnectivityManager — Android API за праћење мреже. Омогућите апликацији да реагује на промјене: при губитку мреже прикажите placeholder, при успостављању — аутоматски ажурирајте податке. У iOS-у користите NWPathMonitor из 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) — стандардни алат за извјештавање о падовима за мобилне апликације. Прикупља stacktrace свих необрађених изузетака, верзију ОС-а, модел уређаја и вријеме пада. Омогућава груписање грешака и додјељивање одговорних за поправке.
Sentry — алтернатива Crashlytics-у са подршком за праћење перформанси. Омогућава праћење одређених трансакција (нпр. „аутентификација корисника”) и увид у кораку на којем је дошло до грешке. Performance tracing помаже да се разликују мрежни тајмаути од грешака у логици апликације.
Timber — библиотека за евиденцију за Android са аутоматским додавањем ознака према класи. У debug верзији евидентирајте све мрежне захтјеве и одговоре. У release верзији — само грешке и упозорења путем Crashlytics.setCustomLog.
| Алат | Тип | Када користити |
|---|---|---|
| Crashlytics | Crash reporting | Увијек у release — аутоматско прикупљање падова |
| Sentry | Crash + Performance | Када треба профилисати одређене корисничке сценарије |
| Timber | Logging | Debug: потпуна евиденција; Release: само грешке |
| HTTP Toolkit | Network debug | Локално пресретање и анализа HTTP саобраћаја |
Према Firebase Summit 2023, апликације које су увеле Crashlytics + Performance Monitoring смањују просјечно вријеме откривања и поправљања критичних грешака са 3 дана на 4 сата. Препоручује се подешавање упозорења за сваки пад са учесталошћу већом од 0.1% активних корисника.
Често постављана питања
Ако се crash не хвата у Crashlytics-у, провјерите native crash (SIGSEGV, SIGABRT) — њих не обрађује Java/Kotlin exception handler. У Android-у то може бити цурење native меморије из JNI, у iOS-у — EXC_BAD_ACCESS. Користите Breakpad (Android) или PLCrashReporter (iOS) за прикупљање stacktrace-а native crash-ева.
Користите Network Link Conditioner (уграђен у iOS, за Android постоји Facebook Network Connection Class или подешавања Developer Options > Network > Select network type). Подесите кашњење 500-3000 ms и губитак пакета 5-30%. Такође можете користити Charles Proxy или mitmproxy за емулацију мрежних кашњења и прекида.
ANR настаје када је UI нит блокирана дуже од 5 секунди. Мрежни захтјеви треба да се извршавају у позадинској нити: coroutines (viewModelScope.launch(Dispatchers.IO)), RxJava (subscribeOn(Schedulers.io)) или WorkManager за синхронизацију. Увијек постављајте тајмауте на HTTP клијенту — недостатак тајмаута може довести до вјечног блокирања.
Race condition — ситуација у којој резултат операције зависи од редослиједа извршавања нити. На примјер, корисник брзо притисне дугме „Пошаљи” двапут и захтјев се шаље двапут. Рјешење: користите Mutex, single-threaded executors или state machine (онемогућите дугме након првог притиска). У Kotlin-у користите Mutex из coroutines или @Synchronized анотацију.
Примијените Chaos Engineering за мобилне апликације: искључите мрежу током операција, симулирајте велико кашњење, прелазите између Wi-Fi и мобилне мреже, убијте процес путем система. Алати: Facebook Network Connection Class, Charles Proxy, iOS Network Link Conditioner. У CI/CD додајте UI тестове са различитим мрежним условима путем AndroidTest Orchestrator-а.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође