개발 일지

2026년에도 수면 앱을 밤새 재생하는 일은 여전히 놀라울 만큼 어렵다

휴대폰에서 아침까지 선풍기 소리가 났으면 했습니다. 2026년이면 이미 해결된 문제라고 생각했죠. 그런데 개발자로서 예상보다 훨씬 많은 시간을 쓰게 됐습니다.

오랫동안 White Noise Lite를 썼습니다. 광고가 있고 제게는 오래되어 보이는 화면이었지만 대체로 잘 작동했습니다. 그게 중요했죠. 그러다 자주 멈추기 시작했습니다. 충돌이라고 생각했지만 로그가 없어 원인은 몰랐습니다. 현대적인 화면과 함께 녹음 대신 기기에서 생성하는 소리, 정밀한 조절 기능도 원했습니다. 결국 Veil을 만들었고 안정성 문제를 훨씬 깊이 이해하게 됐습니다.

iOS의 백그라운드 재생은 비교적 수월했고, 제 경험상 Veil은 처음부터 매우 안정적이었습니다. Android는 더 긴 작업이 됐습니다. 다양한 하드웨어, 메모리가 적은 구형 기기, 제조사별 배터리 관리, 재생 도중 설치되는 업데이트가 있습니다. 제 코드에도 문제가 많았습니다. 새벽 세 시에 깬 사람에게 그 차이는 별 의미가 없습니다.

충돌 보고부터 시작했지만 더 많은 맥락이 필요했습니다. 종료된 프로세스는 예외를 보고하지 못할 수 있고, 살아 있는 프로세스도 오디오가 무음일 수 있습니다. 포그라운드 서비스와 재생 알림도 스피커에서 소리가 난다는 증거는 아닙니다. 네이티브 재생 기록, 프로세스 종료 정보, 메모리 측정, 서비스 상태, 오디오 이벤트, 의도적인 정지 기록을 추가했습니다. 관련 증거는 프로세스가 끝나도 남아 다음 실행 때 수집됩니다.

밤의 상황을 재구성해 가능한 원인을 구분하려 했습니다. 여전히 재생하려던 상태였나? 타이머가 끝났나? 메모리가 늘었나? 충전 중이었나? Android가 패키지를 교체했나? 신호가 무엇을 말해 주는지 이해할 때 정보가 많을수록 도움이 됐습니다.

먼저 측정 자체를 고쳐야 했습니다. 최근 앱에서 Veil을 밀어 닫으면 소리는 멈춰도 기록된 세션은 닫히지 않을 수 있었습니다. 몇 시간 후 Android가 캐시된 프로세스를 회수하면 보고서는 재생 실패로 계산했습니다. 아침의 의도적인 정지가 ‘야간 종료’가 됐죠. 반대로 실제 재생을 끊는 업데이트는 정상 시스템 종료로 제외됐습니다.

정의를 고치자 오디오를 더 고치기 전부터 실패 양상이 달라졌습니다. 이후 재생 개선과 측정 개선을 구분합니다. 버전, 기기군, 세션 길이, 종료 이유를 정기적으로 분석하며 테스트 기기와 중복 보고는 제외합니다. 지연 보고도 중요합니다. 새 버전의 짧은 세션 몇 번으로 밤새 버틸지는 알 수 없습니다.

그 패턴이 기기 테스트를 이끕니다. 실제 휴대폰에서 장시간과 밤샘 테스트를 하고 Samsung 원격 실험실도 이용합니다. 지속적인 logcat, 메모리와 메모리 압박 측정, 실패 시 오디오와 서비스 상태 저장을 사용합니다. 직접 문제 순간을 보지 않아도 증거가 모입니다.

테스트 환경도 결과를 바꿀 수 있습니다. USB는 전원을 공급하므로 USB 연결 테스트는 충전기를 뺀 배터리 테스트가 아닙니다. dumpsys meminfo를 반복하면 오히려 소리 이상이 생기기도 했습니다. 문제없는 하룻밤은 그 조건에서 실패를 재현하지 못했다는 뜻일 뿐, 모든 기기와 상태를 보장하지 않습니다.

뜻밖의 발견은 Android Auto Backup이었습니다. 제 Pixel 로그에는 재생 중 백업을 하면서 BackupManager가 Veil을 종료하는 장면이 남았습니다. 전체 설치 데이터에서는 2026년 8월 14일부터 9월 11일까지 93개 설치에서 보호된 프로세스의 SIGKILL 190건이 있었고, 약 3일 간격의 반복이 두드러졌습니다. 백업 설명을 뒷받침하지만 모든 종료 원인을 입증하지는 않습니다.

클라우드 복원 기능의 제약을 받아들이고 Auto Backup을 껐습니다. Android 문서는 백업이 앱을 종료하지만 보통 포그라운드 앱은 제외한다고 설명합니다. 이 사례에 대응할 수 있었던 것은 실제 기기의 기록 덕분입니다. Android 백업 문서

제 코드의 문제도 있었습니다. 이전 오디오 컨텍스트가 닫히기 전에 복구가 새 컨텍스트를 만들면서 문제가 있는 상황에 리소스까지 겹쳤습니다. 이제 제한 시간 안에서 종료를 기다리고 복구와 재생 작업을 순서대로 처리합니다. 더 최근의 사용자 의도도 존중해야 합니다. 수리가 대기 중일 때 정지를 눌렀다면 수리가 소리를 다시 켜면 안 됩니다.

수정 하나가 다른 실패를 만들기도 했습니다. 150ms 검사로 오디오 컨텍스트 시계가 움직이는지 확인해 실행 중이라고 주장하는 멈춘 컨텍스트를 찾았습니다. 하지만 JavaScript 타이머를 기다렸기 때문에 백그라운드에서는 앱이 앞으로 돌아올 때까지 대기가 멈출 수 있었습니다. 이제 전면에서만 검사합니다. 복구 경로도 화면을 끈 채 테스트해야 하는 이유입니다.

유용한 변화 중에는 제품 결정도 있었습니다. 기기 측정에서 광고 WebView 프로세스와 관련된 상주 메모리가 약 90MB 추가됐습니다. 이전 조사에서도 SDK 관련 심각한 메모리 오류가 발견됐습니다. 처음에는 부담을 줄이려 했지만 결국 광고와 SDK를 모두 제거했습니다. 밤새 작동해야 하는 앱에서는 안정성이 더 중요했습니다.

원격 측정에는 사용자가 정지를 예상했는지 빠져 있었습니다. 관련 중단 이후 Veil은 시간과 함께 ‘예상한 중단이었나요?’라는 작은 카드를 보여 줄 수 있습니다. 표현은 의도적입니다. ‘직접 멈췄나요?’는 동반자가 멈췄거나 직접 만지지 않았어도 예상한 중단인 경우를 놓칩니다.

예상한 정지라면 배터리 문제 해결로 보내지 않습니다. 아니라면 중단을 인정하고 증거에 맞는 도움을 제공합니다. 답은 경험에 대한 직접적인 정보이지 기술적 원인을 단정하는 근거가 아닙니다. 새 Android 사용자 모두에게 불안한 권한 요청과 배터리 설정 찾기를 강요하지 않아도 됩니다.

설정은 양쪽 모두에게 까다롭습니다. 적절할 때 설정을 열거나 예외를 요청할 수 있지만 결정은 사용자의 몫입니다. Android의 예외 절차도 모든 제조사의 정리 동작을 제어하지 않습니다. 첫 소리를 듣기도 전에 낯선 설정을 뒤지게 하기보다 문제 이후 구체적인 다음 단계를 주는 편이 낫습니다. Android 전원 관리 안내

업데이트는 특히 답답합니다. Play의 앱별 자동 업데이트 설정을 안내할 수는 있지만 Veil이 제어하지는 못합니다. 수동 업데이트는 추가 작업이고, Google은 중요한 보안 업데이트에 예외를 둡니다. 밤의 안정성을 높이려는 업데이트가 그 밤을 끊을 수도 있습니다. Google Play 업데이트 설정

업데이트 후 자동 복원은 아직 완성되지 않았습니다. 오디오 그래프뿐 아니라 타이머 상태와 사용자 의도도 정확하게 이어져야 합니다. 일부 제조사별 패턴은 확신 있게 설명하기 전에 더 잘 재현해야 합니다. 앱이 실패에서 회복하길 바라지만 의도적으로 멈춘 사용자와 싸우게 하고 싶지는 않습니다.

가장 큰 진전은 정기 분석, 장시간 테스트, 구체적인 수정, 직접적인 피드백을 결합한 데서 나왔습니다. 각각이 다른 방법의 빈틈을 찾습니다. 앱의 재생 중이라는 말보다 그 증거에 관심이 커졌습니다. 잠들기 전 재생을 누른 사람은 당연히 믿을 수 있어야 합니다. 예상보다 훨씬 많은 작업이 필요했더라도요.

휴대폰에서 해볼 수 있는 해결 방법을 찾나요? 함께 읽을 가이드를 확인하세요: Android 오디오 끊김 해결하기.