[Android]SQLiteDatabase - 트랜잭션 & 비동기 처리
들어가며
안드로이드 앱 개발 중 가장 까다로운 문제 중 하나는 데이터베이스 작업과 비동기 처리의 조화입니다. 최근 우리 프로젝트에서 발생한 "attempt to re-open an already-closed object: SQLiteDatabase" 오류를 해결하는 과정에서 배운 교훈과 인사이트를 공유하고자 합니다. 이 글이 같은 문제로 고민하는 개발자들에게 도움이 되길 바랍니다.
문제 상황: 데이터베이스 닫힘 오류
우리 앱에서는 서버로부터 다양한 데이터(상품, 광고 이미지 등)를 가져와 SQLite 데이터베이스에 저장한 후 이미지 파일을 다운로드하는 기능이 있었습니다. 특히 문제가 발생한 코드는 광고 이미지 데이터를 처리하는 메소드였습니다.
이 코드는 간헐적으로 "attempt to re-open an already-closed object: SQLiteDatabase" 오류를 발생시켰습니다.
문제 분석: 왜 이런 오류가 발생했을까?
문제의 근원은 트랜잭션 관리와 비동기 작업의 상호작용에 있었습니다. 디버깅을 통해 다음과 같은 문제점을 발견했습니다:
- 메인 데이터 파서 메소드는 하나의 큰 트랜잭션 내에서 여러 파서를 순차적으로 호출했습니다.
- 광고 이미지 파서는 ExecutorService와 HandlerThread를 사용한 비동기 방식으로 이미지 다운로드 및 DB 작업을 수행했습니다.
- 문제는 메인 트랜잭션이 종료된 후에도 비동기 작업들이 계속 실행되면서 이미 닫힌 데이터베이스 객체에 접근하려고 시도한다는 점이었습니다.
해결 방법: 일관된 비동기 패턴 적용
해결 방법은 이미 앱 내에서 잘 작동하고 있던 상품 이미지 처리 패턴(prParser())을 참고하여 일관된 접근 방식을 적용하는 것이었습니다.
구현한 방법은 다음과 같습니다:
- 트랜잭션과 비동기 작업의 분리 광고 이미지 파서를 수정하여 트랜잭션 내에서는 기본 정보만 저장하고, 이미지 다운로드 정보는 별도의 컬렉션(HashMap)에 수집하도록 변경했습니다.
- AsyncTask를 활용한 이미지 일괄 처리 기존에 상품 이미지 처리에 사용하던 ImageDownloadTask(AsyncTask 상속)를 확장하여 광고 이미지도 함께 처리하도록 수정했습니다. 이 작업은 메인 트랜잭션이 완료된 이후에 실행됩니다.
- 별도 트랜잭션을 이용한 안전한 DB 업데이트 이미지 다운로드 후 데이터베이스 경로 업데이트 작업은 AsyncTask의 onPostExecute() 메소드에서 별도의 트랜잭션으로 안전하게 처리하도록 구현했습니다.
+
AsyncTask란 무엇인가?
AsyncTask는 안드로이드 플랫폼에서 제공하는 클래스로, 백그라운드 작업을 실행하고 그 결과를 UI 스레드에 쉽게 게시할 수 있도록 설계되었습니다. 주요 메소드로는:
- doInBackground(): 백그라운드 스레드에서 실행되는 주요 작업
- onPreExecute(): 백그라운드 작업 전 UI 스레드에서 실행
- onProgressUpdate(): 진행 상황을 UI 스레드에 업데이트
- onPostExecute(): 백그라운드 작업 완료 후 UI 스레드에서 결과 처리
AsyncTask가 더 이상 권장되지 않는 이유
AsyncTask는 API 30(Android 11)에서 공식적으로 deprecated 되었으며, 다음과 같은 여러 문제점을 가지고 있습니다:
- 생명주기 문제:
- AsyncTask는 Activity/Fragment 생명주기와 자동으로 연결되지 않습니다.
- 화면 회전이나 앱 백그라운드 전환 시 AsyncTask가 계속 실행되어 메모리 누수나 충돌을 유발할 수 있습니다.
- 스레드 풀 관리 문제:
- API 레벨에 따라 실행 방식이 달라져 예측하기 어렵습니다.
- 기본 스레드 풀 크기가 제한적이어서 많은 작업이 대기해야 할 수 있습니다.
- 설정 변경 후 결과 처리:
- 화면 회전 같은 설정 변경 후 UI 참조가 무효화되어 결과 처리가 어렵습니다.
- 에러 처리 메커니즘 부재:
- 예외 처리를 명시적으로 구현해야 하며, 기본 메커니즘이 없습니다.
- 취소 처리의 복잡성:
- 작업 취소가 복잡하고 즉시 중단을 보장하지 않습니다.
https://hantoluvcoding.tistory.com/37
동기 / 비동기
동기(synchronous)동기 작업은 연산이 완료될 때까지 해당 작업을 호출한 스레드가 기다리는 방식이다. 즉, 작업이 완료될 때까지 다음 작업이 시작되지 않는다. 안드로이드에서는 주로 UI 스레드(Ma
hantoluvcoding.tistory.com
https://hantoluvcoding.tistory.com/82
[Android] Thread, Runnable, Executors
https://mangkyu.tistory.com/258 [Java] Thread와 Runnable에 대한 이해 및 사용법이번에는 자바 초기부터 멀티 쓰레드 기반의 동시성 프로그래밍을 위해 만들어졌던 Thread와 Runnable를 살펴보도록 하겠습니다. 1
hantoluvcoding.tistory.com