Почему появляется DeadObjectException и что с этим делать
DeadObjectException возникает, когда клиент вызывает методы удалённого объекта (IBinder), чей процесс или сервис уже завершён; исправлять нужно проверкой isBinderAlive/pingBinder, корректной обработкой onServiceDisconnected, переходом на lifecycle‑aware компоненты и добавлением fallback‑логики (переподключение или отказоустойчивый путь).
Что такое DeadObjectException и типичные сценарии
DeadObjectException — подкласс RemoteException: сигнал о том, что удалённый Binder уже мёртв. Частые сценарии:
- Bound Service был убит системой (low memory) и клиент продолжил держать старый IBinder.
- Вызов методов после onDestroy() Activity/Fragment.
- IPC через AIDL/Messenger, где сторона‑получатель завершила работу.
- Утечки: статические поля или Handler удерживают ссылки на IBinder.
Как диагностировать проблему
- Logcat: фильтр по DeadObjectException — найдёте стек и место вызова.
- Включите StrictMode в debug для выявления утечек Binder:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}
- Проверяйте onServiceDisconnected в ServiceConnection — часто именно там забывают освобождать ссылки.
- Симуляция убийства процесса: adb shell am kill
или тесты в low‑memory.
Если в стеке виден вызов на стороне клиента сразу после системного уничтожения сервиса — ищите место, где сохраняется IBinder и где вызывается метод без проверки.
Практические способы исправления
- Обработка в ServiceConnection:
private var binder: IMyAidlInterface? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = IMyAidlInterface.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
// сервис мёртв — очистить ссылки и остановить запросы
binder = null
}
}
- Проверка состояния перед вызовом:
val b = (binder as? IBinder)
if (b?.isBinderAlive == true) {
b.pingBinder()
binder?.someMethod()
} else {
// переподключение или показ ошибки пользователю
}
- Lifecycle‑aware подходы:
- Используйте ViewModel + LiveData/StateFlow для фоновой логики, чтобы не держать прямые ссылки на Context.
- Отписывайтесь от сервисов в onStop/onDestroy и освобождайте ресурсы через LifecycleObserver или ProcessLifecycleOwner.
- Выбор IPC:
- Для простых задач используйте Messenger или PendingIntent вместо сложного AIDL.
- Для фоновых задач — WorkManager вместо долгоживущих сервисов.
Никогда не храните IBinder или Context в статических полях — это частая причина утечек и последующих DeadObjectException.
Краткое сравнение подходов
| Подход | Сложность | Когда подходит |
|---|---|---|
| ServiceConnection + onServiceDisconnected | Низкая | Bound services |
| pingBinder/isBinderAlive | Средняя | Частые вызовы к сервису |
| LifecycleObserver / ViewModel | Низкая | UI‑связанные компоненты |
| Messenger вместо AIDL | Высокая | Упрощённый IPC, отсутствие сложного AIDL |
Частые ошибки
- Игнорирование onServiceDisconnected и продолжение вызовов.
- Хранение IBinder в статике или долгоживущих объектах.
- Попытки доступа к сервису после onDestroy() Activity.
- Отсутствие fallback‑логики при недоступности сервиса.
FAQ
-
Нужно ли ловить DeadObjectException try/catch?
Да, в критичных местах поймайте RemoteException/DeadObjectException и выполните fallback (переподключение или graceful degradation). -
Поможет ли переход на WorkManager?
Для фоновых задач — да. WorkManager избавляет от многих проблем с принудительным убийством сервисов. -
Как быстро отреплицировать баг?
adb shell am killили тестирование на low‑memory устройстве.
Если после правок ошибка остаётся — приложите стек‑трейс и опишите сценарий (bound service, AIDL, Activity lifecycle) — помогу точечно.