금융 알림 수신 Android 앱 리브랜딩
목차
pay-monitor를 v1.0.0으로 태깅하고 아이콘까지 교체한 건 사소한 작업처럼 보이지만, 내부 도구를 "제대로 굴리겠다"는 선언에 가깝다. 버전 번호가 없는 앱은 릴리즈 기준도 없고 롤백 기준도 없다. 어느 빌드가 서버에 올라가 있는지 모르는 채로 운영하다 보면, 오류가 생겼을 때 "그 APK 맞아?" 같은 대화부터 해야 한다. 리브랜딩이라는 표현을 썼지만 실질적으로는 운영 체계를 갖추는 커밋이었다.
알림 캡처 앱이 안드로이드에서 살아남는 방법
pay-monitor의 핵심 과제는 단순하다. 금융 앱에서 오는 알림(입금 SMS, 카카오페이 송금 등)을 놓치지 않고 파싱해서 서버로 보내면 된다. 근데 안드로이드는 이걸 쉽게 놔두지 않는다.
배터리 최적화는 백그라운드 서비스의 주적이다. 특히 제조사 커스텀 레이어(삼성 One UI, 샤오미 MIUI 등)는 AOSP보다 훨씬 공격적으로 프로세스를 죽인다. 포어그라운드 서비스로 올려두면 상태바에 상주 알림이 생기는 대신 OS가 함부로 종료하지 못한다. 그게 트레이드오프다. 사용자 노출이 생기지만 안정성은 확보된다. 내부 운영 도구라 사용자 UX 민감도가 낮으니 이쪽을 택한다.
WakeLock은 더 조심스럽게 써야 한다. CPU 웨이크락을 잡아두면 기기가 잠들지 못해 배터리를 갉아먹는다. 실제로 필요한 순간에만 acquire하고, try-finally로 반드시 release하는 패턴을 지켜야 한다.
val wakeLock = (getSystemService(POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "pay-monitor::NotifLock")
wakeLock.acquire(10_000L) // 최대 10초, 타임아웃 지정 필수
try {
processNotification(notification)
} finally {
if (wakeLock.isHeld) wakeLock.release()
}
타임아웃 없이 acquire()만 쓰면 처리 도중 예외가 터졌을 때 웨이크락이 영구 잠금 상태로 남는다. 이게 쌓이면 메모리 누수보다 먼저 배터리 드레인으로 발현된다.
접근성 서비스는 카카오톡 링크 자동수령 로직에 쓰인다. 알림 리스너는 알림 페이로드만 가져오는 반면, 접근성 서비스는 화면 노드 트리를 순회해서 특정 버튼을 찾아 누를 수 있다. 자동화 범위가 넓어지는 대신 OS 업데이트나 카카오톡 UI 변경에 취약해진다. 노드 ID 대신 텍스트 기반으로 탐색하는 편이 버전 간 내성이 낫다.
API 전송과 오프라인 큐잉
알림을 파싱했는데 서버 전송에 실패하는 상황도 고려해야 한다. 모바일 환경은 네트워크가 일시적으로 끊기는 게 정상이다. 재시도 없이 한 번 실패하면 그 알림은 유실된다.
이번 커밋에서 정비한 방향은 오프라인 큐잉이다. 전송 실패분을 로컬에 보관했다가, 네트워크가 회복되면 순서대로 재전송한다. 구현 방식은 크게 두 가지다.
| 방식 | 장점 | 단점 |
|---|---|---|
| Room DB 큐 | 영속성, 앱 재시작 후에도 유지 | 보일러플레이트 많음 |
| SharedPreferences JSON 직렬화 | 구현 빠름 | 대량 누적 시 파싱 비용, 동시성 위험 |
| WorkManager | OS 스케줄링 통합, Doze 모드 대응 | 실행 타이밍 보장 안 됨 |
내부 도구 규모에서는 Room 큐가 과하다 싶을 수 있지만, 금융 알림 유실이 운영에 직접 영향을 준다면 영속성을 포기할 수 없다. 이번 작업에서 재시도 로직을 정비한 것도 같은 맥락이다.
ProGuard 설정도 이번에 같이 손댔다. MyBatis SQL 매퍼가 리플렉션을 사용하기 때문에 난독화 범위에서 빠뜨려야 한다. -keep 룰을 빠트리면 릴리즈 빌드에서만 런타임 오류가 나고, 디버그 빌드에서는 멀쩡하다. 디버그에서 안 잡히는 버그는 진짜 잡기 귀찮다. 빌드 스크립트(.kts) 단에서 릴리즈 빌드 설정을 명시적으로 관리하는 게 이런 사고를 줄인다.
24시간 떠 있는 앱의 현실
이런 류의 앱을 장기 운영하면 반드시 부딪히는 문제가 메모리 누수다. 백그라운드에서 알림을 계속 처리하다 보면 리스너 등록이 중복되거나, 컨텍스트를 오래 붙잡는 클래스가 GC를 막는 경우가 생긴다. Android Profiler로 힙 스냅샷을 찍어보면 예상 못한 객체가 살아있는 걸 종종 발견한다.
서비스가 죽고 자동 재시작까지 갭이 생기는 것도 운영 리스크다. START_STICKY를 반환하면 OS가 서비스를 재시작해주긴 하는데, 재시작 타이밍은 보장이 없다. 알림이 그 갭에 도착하면 유실된다. 완전한 해법은 없고, 포어그라운드 서비스 + 브로드캐스트 리시버로 재시작 트리거를 여러 경로로 열어두는 게 현실적인 대응이다.
v1.0.0 태깅은 작업의 끝이 아니라 "이 기준점부터 변경을 추적하겠다"는 시작이다. 다음 변경이 v1.0.1이 되든 v1.1.0이 되든, 이제 서버에 올라간 APK가 어느 버전인지는 바로 답할 수 있다. 그게 이 커밋에서 얻은 가장 작은 동시에 가장 실용적인 것이다.
댓글 0
첫 댓글 달아줘.