악기를 불면서 악보를 볼 때는 한동안 화면을 건드리지 않습니다. 반대로 악보를 열어 둔 채 자리를 비울 수도 있습니다. 얼쑤의 성장 화면에 연습 시간을 표시하려면 이 두 상황을 구분할 규칙이 필요했습니다. 저는 팀과 집계 기준을 정리하고 서버의 구간 저장 API를 구현했습니다. 앱에서 시간을 재고 기기에 보관해 보내는 부분은 프론트엔드 팀원이 구현했습니다.

화면 표시 시간과 인정 시간

악보가 앞에 보이기 시작하면 구간을 하나 엽니다. 화면을 벗어나거나 앱이 뒤로 가면 구간을 닫고, 돌아왔을 때는 새 구간을 엽니다. 구간마다 화면이 보인 시간과 그중 연습으로 인정한 시간을 따로 남깁니다. 성장 화면의 연습 시간은 인정 시간의 합입니다.

재생·녹음·메트로놈이 동작하는 동안은 화면 입력이 없어도 연습으로 셉니다. 그 밖에는 마지막 활동 후 9분 30초에 한 번 확인을 묻습니다. 응답하면 그 시점부터 다시 셉니다. 응답이 없으면 10분에서 인정 시간을 멈춥니다. 9분 30초와 10분은 팀이 정한 기준입니다.

앱 테스트에서는 실제로 기다리는 대신 테스트 시계를 움직여 이 경계를 확인했습니다.

테스트에서 만든 상황관찰한 결과
활동 없이 악보를 29분 30초 열어 두고 알림에 응답하지 않음9분 30초에 알림 한 번, 보인 시간 29분 30초, 인정 시간 10분
화면 표시 90초 → 백그라운드 120초 → 화면 표시 90초서로 다른 키를 가진 구간 두 개, 인정 시간 합계 180초

마지막 저장 시각으로 구간 닫기

앱은 열린 구간의 키와 시각, 두 종류의 경과 시간을 30초마다 기기에 저장합니다. 구간을 닫으면 최종 값을 전송 대기열에 넣습니다. 연결이 끊겨 전송하지 못한 구간은 대기열에 두고, 다음에 보낼 때 같은 키와 본문을 사용합니다. 서버는 같은 기록이 도착하면 기존 결과를 반환하므로, 응답을 놓쳐 다시 보내도 연습 시간이 두 번 더해지지 않습니다. 서버 통합 테스트에서는 최초 요청이 201, 같은 요청이 200, 같은 키에 본문이 달라진 요청이 409였고 DB에는 구간 한 행이 남았습니다.

앱이 구간을 닫기 전에 종료되면, 다음 실행에서 저장해 둔 시각을 끝으로 구간을 닫습니다. 테스트에서는 시작 후 270초에 상태를 저장하고, 299초에 앱이 종료된 상황을 만들었습니다. 복구한 구간의 보인 시간과 인정 시간은 모두 270초였습니다. 별도의 대기열 테스트에서는 이 구간이 첫 실행에 한 번 전송됐고, 시작 처리를 다시 실행해도 추가 전송은 없었습니다.

이 경우 마지막 저장 뒤의 29초가 빠졌습니다. 30초는 저장이 제때 완료되는 경우에 복구할 수 있는 시점의 간격입니다. 앱 데이터가 지워지거나 로그아웃 때 남은 전송이 실패하면 기록이 더 빠질 수 있습니다. 앱의 시간 계산과 복구 결과는 고정 시계와 저장소·전송 대역에서 관찰한 값입니다.