Dari 라이브러리 고도화 하기 ~_~

easyhooon·2026년 4월 10일

WebView

목록 보기
4/4
post-thumbnail

서두

웹뷰 브릿지 디버깅을 위한 라이브러리인 Dari 를 배포한 이후, 이를 실제 운영중인 서비스에 도입해보면서 있으면 편리할 것 같은 기능들을 하나씩 추가해봤다.

이번 글에서는 Dari의 새롭게 추가된 기능들을 소개하고, 기능들을 추가하면서 느꼈던 점들을 기록해보려고 한다.

각 버전별 변경 사항은 Dari Github Release Note 에서 확인할 수 있다.

본론

Room 도입 - 앱 재시작해도 로그가 남도록 (1.2.0)

처음 배포 당시에는 브릿지 메시지(로그)들을 인메모리StateFlow로만 관리했다. 때문에 앱을 재시작하거나 프로세스가 죽으면 로그들이 전부 날아갔다. 디버깅 중에 앱이 크래시나거나 재시작이 필요한 상황이 생기면 그동안 쌓인 로그를 전부 잃는 셈이었다.

라이브러리를 개발할때 영감을 받았던 네트워크 디버깅 라이브러리인 Chucker 역시 Room DB를 사용하고 있었고, Dari도 이를 도입하여 데이터의 영속성을 확보할 수 있었다.

인메모리 캐시는 즉각적인 UI 업데이트용으로 유지하면서, Room에 비동기로 동시 저장하는 dual-write 패턴을 적용했다.

DataStore가 내부적으로 인메모리 캐시와 디스크 저장을 동시에 유지하는 방식에서 착안했다.
자세한 내용은 해당 블로그글을 확인해보면 좋을듯 하다.
[Android] DataStore는 데이터를 어떻게 저장할까

또한 로그 목록 화면에서 항목이 지나치게 많아지면 초기 로드가 느려질 수 있기 때문에, maxEntries를 설정해 초과 시 오래된 항목이 자동으로 정리되도록 했다. (ex. 500으로 설정 시 501번째 항목이 추가되는 시점에 가장 오래된 항목 삭제)

TTL 기반 보존 정책 추가 (1.4.0)

Room을 붙인 덕분에 자연스럽게 이어진 기능이다. DariConfigretentionPeriod: Duration?을 추가해, 설정한 시간보다 오래된 메시지를 자동으로 삭제할 수 있으며, 기본값은 null(비활성화)이다.
(ex. 3.days로 설정시 3일이 지난 항목들은 Room DB에서 제거)

requestId를 optional로 - fire-and-forget 이벤트 지원 (1.3.0)

초기 설계에서는 모든 브릿지 메시지가 requestId를 가진다고 가정했다. 요청과 응답을 짝지어 추적하는 구조였는데, 실제 앱에서는 응답이 없는 단방향 이벤트(logEvent, trackScreenView 등)도 빈번하게 발생한다.

이를 해결하기 위해 requestIdnullable로 변경했다. requestIdnull이면 요청-응답 매칭을 건너뛰고 fire-and-forget으로 처리한다.

동시에 브릿지 메세지 목록 화면을 구현할 때 사용하는 LazyColumn의 key를 requestId 대신 Room의 auto-increment id로 교체해 키 충돌 문제도 해소했다.

Dari를 사용하기 위해 브릿지 명세를 Dari의 컨벤션에 끼워 맞춰야만 한다면(requestId 필수), requestId를 새로 추가하는 등 기존 구현을 수정해야하기에 이 부분이 진입장벽이 될 것이라 생각했다.
최대한 기존 코드 수정없이 편하게 Dari를 사용할 수 있기 위한 지원이라 생각하면 될 듯하다.

멀티 브릿지 환경을 위한 tag 지원 (1.3.1)

앱 내에 여러 WebView 브릿지 모듈이 공존하는 경우, 각 메시지가 어느 WebView에서 수집된 것인지 구분하기 어려운 문제가 있었다.

특히 서로 다른 인터셉터가 동일한 requestId를 사용할 경우 메시지가 뒤섞이는 버그도 발생할 수 있었다.

Timber 같은 로깅 라이브러리에서 tag로 로그 출처를 분리하는 것처럼, MessageEntry에도 tag: String? 필드를 추가해 브릿지별로 메시지를 구분할 수 있도록 했다.

기존 코드와의 호환성은 tag가 없는 생성자 오버로드로 유지했다.

TransactionTooLargeException - 치명적 핫픽스 (1.3.2)

실제 앱에 붙이고 나서 맞닥뜨린 가장 치명적인 버그였다. 브릿지의 payload가 클 경우 Android Binder Transaction 한계(1MB)를 초과해 앱이 크래시났다.

사용자의 운동 데이터를 건강 앱으로부터 프론트로 전송하는 브릿지가 존재하는데, 평균 심박수 외에 단위 시간별 심박수까지 함께 담기면서 payload JSON 크기가 상당히 커졌다. 브릿지 전달 자체는 문제없었지만, 해당 로그 Text를 ShareSheet를 통해 Intent로 공유하려는 순간 TransactionTooLargeException이 발생했다.

DariConfigmaxContentLength 옵션을 추가해 request/response 본문을 지정 길이로 truncate하도록 했다. 공유 텍스트도 SHARE_MAX_LENGTH = 100_000으로 제한해 같은 문제를 예방했다.

비록 개발 환경(Debug variant)에서만 사용하는 라이브러리지만, 운영 중인 앱에서 실제로 재현되는 문제였기 때문에 빠르게 핫픽스로 대응했다.

왜 브릿지 통신에서는 문제가 없었는데 shareSheet를 통한 Text Export시에만 문제가 발생했는지는 추가적으로 알아봐야겠다. 궁금

다크모드 지원, 로그 내보내기, 상태 필터 도입 - 대규모 UX 업데이트 (1.4.0)

사용하면서 조금씩 아쉬웠던 부분들을 한번에 몰아서 개선했다.

로그 내보내기: 기존에는 단일 메시지를 텍스트로 공유하는 것만 가능했는데, Chucker 처럼 전체 로그를 JSON 파일로도 내보낼 수 있도록 했다. 또한 SAF(Storage Access Framework) 를 사용해 원하는 위치에 파일을 저장할 수 있는 기능도 추가했다.

SAF는 Android에서 파일 저장 위치를 사용자가 직접 선택할 수 있도록 제공하는 표준 API로, 앱이 특정 저장 경로를 강제하지 않고 시스템 파일 피커를 통해 저장 위치를 지정할 수 있다.

다크모드: 설정 바텀시트내에 System / Light / Dark 세 가지 옵션을 Segment 피커를 통해 제공하였다.

메시지 상태 필터: 메시지가 많아지면 특정 상태(IN_PROGRESS, SUCCESS, ERROR 등)만 보고 싶은 경우가 생긴다. 필터 칩을 추가해 원하는 상태만 골라볼 수 있도록 했다.

Shake-to-open (1.4.0)

Dari의 디버그 패널을 여는 방법은 노티피케이션 알림 클릭과 Dynamic Shortcut 두 가지 였다.

두 방법 모두 디버그 패널을 열기 위해 현재 화면(앱)에서 벗어나야 한다는 번거로움이 있었는데, '조금 더 편한 방법이 있지 않을까?' 고민을 하다가 React Native의 DevTools가 shake gesture로 열리는 것에서 착안하여 폰을 흔들면 바로 열 수 있도록 했다.

설정 바텀시트에서 토글을 통해 이를 활성화/비활성화할 수 있다.

때는 코로나 시국, 카카오톡 앱을 켠 상태로 핸드폰을 흔들면 코로나 QR 체크인이 바로 열리던 시절이 있었는데, 그때도 '이건 어떻게 구현한걸까' 하며 신기해했었다. 그 기능을 이번에 직접 구현해볼 수 있었다.

Shake 감지 로직 개선 (1.4.1)

1.4.0에서 추가한 shake-to-open 의 감지 로직이 불안정했다. 단일 스파이크(2.5G 이상 1회) 기준으로 판정하다 보니 폰을 살짝 내려놓거나 걷는 중에도 패널이 열리는 경우가 있었다.

다른 곳들에선 shake 감지를 어떻게 하고 있을까 레퍼런스를 찾아보다가 React Native Devtools의 감명을 받은 관계로 React Native의 ShakeDetector 내부 구현을 참고해 방향 반전 카운팅 방식으로 교체해보았다.

3초 내에 축 방향 반전이 8회 이상 발생해야 인식하는 방식으로, 대략 4번의 물리적 흔들기에 해당한다. Z축 중력 보정과 최소 샘플링 인터벌도 추가했다. 구현하면서 흥미로웠던 부분으로 이후 자세한 내용을 글로 작성해보도록 하겠다.

결과

오픈소스 라이브러리를 만들고 직접 운영하는 앱에 붙여서 쓰다 보니, 배포 전엔 미처 생각하지 못했던 문제들이 하나씩 드러났다.

설계 단계에서 미처 고려하지 못했던 예외 케이스로 치명적인 버그가 발생하기도 했고, 사소해 보이던 UX도 매일 쓰다 보면 거슬리기 마련이었다.

앞으로도 사용하면서 불편한 점이 생기면 계속 개선해나갈 예정이다.

P.S

기존 심플한 기능만을 제공하는 라이브러리에서 점차 기능을 추가하면서, '이 새롭게 추가된 편리한 기능들을 어디에 배치해야 사용자들이 이를 인지하고 사용할 수 있을까' 고민을 해볼 수 있었다.

기능만_덕지덕지_추가하다가_매우_못생겨진_ActionBar.jpg

심지어 검색어 입력 기능까지 제공하는 ActionBar인 관계로 이건 좀 아니다 싶었다.

고민 끝에 설정 바텀시트를 도입, Save/Share는 Export Icon으로 통합하여 어느정도의 심플함을 유지할 수 있었다.

어려운 디자인의 세계 ~_~

reference)
https://github.com/easyhooon/dari
https://reactnative.dev/docs/react-native-devtools
https://reactnative.dev/docs/debugging
[Android] DataStore는 데이터를 어떻게 저장할까
https://developer.android.com/develop/ui/views/launch/shortcuts?hl=ko
https://rob-coding.tistory.com/50

profile
실력은 고통의 총합이다. Android Developer

0개의 댓글