FCM 푸시와 인증코드로 금융 알림 앱 사용자 설정 강화
목차
pay-monitor v3.0 커밋이다. FCM 푸시 수신 경로와 인증코드 기반 사용자 설정을 붙였고, 그 과정에서 6개 파일을 건드렸다.
앱의 역할을 짧게 정리하면, 입금 알림이나 카카오페이 송금 알림 같은 금융 이벤트를 기기에서 캡처해 서버로 쏘는 Android 앱이다. 사용자가 직접 입력할 필요 없이 접근성 서비스를 통해 자동수령 자동화까지 처리한다. 단순해 보이지만 이 앱이 죽으면 전체 파이프라인이 멈추기 때문에, 24시간 살아있는 상태를 유지하는 게 설계 목표의 절반 이상이다.
이번 v3.0의 핵심은 두 가지였다. 하나는 FCM(Firebase Cloud Messaging) 채널을 뚫어서 서버에서 앱 쪽으로 제어 메시지를 내려보내는 경로를 만든 것이고, 다른 하나는 SetupActivity에서 인증코드를 입력받아 사용자 설정을 초기화하는 흐름을 정비한 것이다.
변경 파일과 각자의 역할
| 파일 | 주요 변경 |
|---|---|
App.kt |
FCM 토큰 초기화, 앱 전역 상태 관리 |
SetupActivity.kt |
인증코드 입력 UI, 설정 초기화 흐름 |
ApiClient.kt |
재시도 로직, 오프라인 큐잉 |
내부 클래스.kt |
알림 파싱, 오탐 방지 키워드 |
내부 파일.kts |
Kotlin 빌드 스크립트 수정 |
내부 파일 |
MyBatis SQL 매퍼/설정 |
변경 항목이 여러 카테고리에 걸쳐 있는 건 이번 커밋이 "기능 추가 + 안정화"를 함께 진행했기 때문이다. 원래는 분리하는 게 맞지만, 서버 연동 변경과 클라이언트 파싱 수정이 같은 흐름 위에 있어서 묶었다.
알림 파싱 쪽은 오탐 방지 키워드를 추가하는 방식으로 개선했다. 금융 알림은 문구 패턴이 일정해 보여도 앱 버전이나 통신사에 따라 조금씩 달라진다. 카카오페이 같은 경우 "송금", "입금완료", "결제" 같은 키워드가 섞여 있고, 광고성 푸시와 구분이 안 될 때가 생긴다. 키워드 필터를 코드에 하드코딩해두면 유지보수가 힘들어지기 때문에 서버에서 내려받거나 설정 파일로 분리하는 구조가 장기적으로 낫긴 하다. 이번엔 일단 코드 레벨로 처리했고, 리팩토링은 다음 이슈로 남겨뒀다.
FCM 채널을 붙인 이유
기존엔 앱이 단방향이었다. 기기 알림을 캡처해서 서버로 POST하는 게 전부였고, 서버에서 앱에 뭔가를 전달할 방법이 없었다. 이게 불편해지는 시점이 생각보다 빨리 왔다. 설정값을 바꾸거나 파싱 규칙을 업데이트할 때마다 앱을 재설치하거나 UI를 직접 열어야 했다.
FCM을 붙이면 서버 쪽에서 데이터 메시지를 내려보내 앱 설정을 갱신하거나 특정 동작을 트리거할 수 있다. 구현 포인트는 크게 두 곳이다.
// App.kt - FCM 토큰 갱신 시 서버에 등록
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) return@addOnCompleteListener
val token = task.result
ApiClient.registerFcmToken(token)
}
토큰은 앱 재설치나 데이터 초기화 때 바뀌기 때문에 onNewToken 콜백에서도 서버에 갱신 요청을 보내야 한다. 이걸 빠뜨리면 FCM 메시지가 조용히 묻힌다. 그리고 데이터 메시지는 알림 페이로드 없이 데이터 페이로드만 구성하면 앱이 포어그라운드든 백그라운드든 onMessageReceived 콜백이 온다. 상태 바에 알림을 띄울 필요 없이 백그라운드에서 처리하고 싶을 때 이게 유용하다.
백그라운드에서 24시간 버티기
이 앱의 설계 과제 중 가장 까다로운 부분이다. Android는 버전이 올라갈수록 백그라운드 프로세스에 점점 적대적으로 변했다. 배터리 최적화 설정이 켜져 있으면 포어그라운드 서비스조차 강제 종료되는 경우가 생기고, 제조사 커스텀 OS에서는 더 공격적으로 킬하기도 한다.
지금까지 확인된 주요 종료 원인과 대응 방식:
- 배터리 최적화 화이트리스트 미등록 -
ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS로 사용자에게 권한 요청 - 포어그라운드 서비스 강제 종료 -
startForeground()에 적절한 알림 채널 붙이기, Android 12 이상 타입 명시 필요 - WakeLock 누수 -
acquire()/release()페어를 try-finally로 감싸거나 타임아웃 지정 - 메모리 압박에 의한 OOM 킬 - 알림 이벤트 버퍼 크기 제한, 불필요한 캐시 제거
WakeLock은 무조건 붙이는 게 아니라 필요한 순간에만 잡고 바로 놓아주는 패턴이 맞다. 잘못 붙잡으면 배터리를 갉아먹고, 그게 결국 사용자가 앱을 지우는 이유가 된다.
ApiClient 쪽 오프라인 큐잉은 이번에 처음 제대로 정비했다. 기존엔 API 호출 실패 시 그냥 드랍했는데, 네트워크가 끊겼다 다시 연결될 때 놓친 이벤트가 생기는 문제가 있었다. 큐에 적재해두고 연결 복구 시 재전송하는 방식으로 바꿨다. 단순한 구현이지만 신뢰성 차이는 체감될 거라고 본다.
SetupActivity와 인증코드 흐름
사용자가 처음 앱을 켰을 때 서버 엔드포인트와 인증코드를 입력하는 초기 설정 화면이다. 기존엔 URL만 입력받았는데, 이번에 인증코드 필드를 추가하면서 SetupActivity를 상당 부분 손댔다.
인증코드 방식의 장점은 앱 배포 없이 사용자별로 다른 설정값을 넣을 수 있다는 거다. 서버에서 코드로 사용자를 식별하고, 해당 사용자의 파싱 규칙이나 알림 필터 설정을 내려주는 구조로 이어질 수 있다. 지금은 인증 수단으로만 쓰고 있지만, FCM 채널이 열렸으니 나중에 조합하면 설정 동적 갱신까지 연결 가능하다.
입력값 검증은 앱에서 기본 형식 체크만 하고, 실제 유효성 확인은 서버 응답으로 처리한다. 코드가 틀렸을 때 에러 메시지를 바로 보여주려면 서버와 한 번 통신해야 해서, 이 부분이 SetupActivity의 첫 API 호출 지점이 된다. 여기서 오류 핸들링을 제대로 안 하면 UI가 먹통처럼 보이는 경험을 주게 되니까 재시도 처리와 로딩 상태 표시를 같이 붙였다.
ProGuard 설정도 이번에 같이 정비했다. 접근성 서비스나 MyBatis 관련 클래스는 리플렉션으로 참조되는 경우가 있어서, 난독화 예외 처리를 빠뜨리면 릴리즈 빌드에서만 터지는 문제가 생긴다. 디버그 빌드에서 잘 돌아가는 코드가 릴리즈에서 죽으면 항상 ProGuard를 먼저 의심하게 되는데, 이번에 -keep 규칙을 좀 더 꼼꼼하게 정리해뒀다.
이번 v3.0이 완성형이라기보다는 다음 단계로 넘어가기 위한 기반 정비에 가깝다. FCM 채널이 열렸고, 인증코드로 사용자를 식별할 수 있는 구조가 갖춰졌으니, 이걸 활용해서 서버에서 파싱 규칙을 동적으로 내려보내는 흐름을 다음 버전에서 완성할 생각이다.
댓글 0
첫 댓글 달아줘.