SQLiteOpenHelper 구현이 되어있다고 다음과 같이 가정해보자.
public class DatabaseHelper extends SQLiteOpenHelper {... }
자 그럼 여기서 서로 다른 쓰레드에서 다음과 같이 데이터베이스 데이터를 접근하는 코드를 만들어 본다.
// 쓰레드 1
Context context =getApplicationContext();
DatabaseHelperhelper = new DatabaseHelper(context);
SQLiteDatabasedatabase = helper.getWritableDatabase();
database.insert(…);
database.close();
// 쓰레드 2
Context context =getApplicationContext();
DatabaseHelperhelper = new DatabaseHelper(context);
SQLiteDatabasedatabase = helper.getWritableDatabase();
database.insert(…);
database.close();
Logcat은 아래와 같은 메시지를 뿌릴테고, 동시에 둘 중에 하나는 데이터베이스를 변경하지 못하는 상황이 발생되었을 것이다.
android.database.sqlite.SQLiteDatabaseLockedException:database is locked (code 5)
왜냐하면, 새로운 SQLiteOpenHelper 오브젝트를 생성 할 때 마다 사실, 새로운 데이터베이스 연결 이 생기기 때문에이런 일이 발생한다. 만약 동시에 다른 실제 명시적인 연결에서 데이터베이스 쓰기를 시도한다면, 실패 할 것이다.
다중 쓰레드 상에서 데이터베이스를 사용하기 위해서 확실히 하나의데이터베이스 연결만 사용해야만 한다.
그럼, 다음과 같이 단일 SQLiteOpenHelper 오브젝트를 리턴 하고 인스턴스를 가지고 있는 싱글톤 DatabaseManager 클래스를 만들어 보도록 하자.
public class DatabaseManager {
private staticDatabaseManager instance;
private staticSQLiteOpenHelper mDatabaseHelper;
public staticsynchronized void initializeInstance(SQLiteOpenHelper helper) {
if(instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public staticsynchronized DatabaseManager getInstance() {
if(instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initialize(..) method first.");
}
returninstance;
}
publicsynchronized SQLiteDatabase getDatabase() {
return mDatabaseHelper.getWritableDatabase();
}
}
분리된 쓰레드에서 데이터베이스로 데이터를 쓰는 코드를 만드는 것은 다음과 같은 형식이 될 것이다.
// In your application class
DatabaseManager.initializeInstance(new DatabaseHelper());
// Thread 1
DatabaseManager manager =DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
// Thread 2
DatabaseManager manager =DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
이것도 다음 같은 충돌 상황을 만들어 낼 것이다.
java.lang.IllegalStateException: attempt to re-open analready-closed object: SQLiteDatabase
우리가 오직 하나의 데이터베이스 연결을 사용하기 전에, getDatabase() 메서드는 쓰레드1 과 쓰레드2 에대해서 똑같은 SQLiteDatabase 오브젝트를 리턴 해야 한다.
쓰레드2가쓰고 있을 지 모르는 데이터베이스를 쓰레드1 이 닫아 버리는 일이 벌어진 것이고, IllegalStateException 충돌 상황이 발생 한 것이다.
그래서, 데이터베이스가 이제 더 이상 사용하고 있지 않은지그리고 닫아도 되는 지 확실히 알 수 있어야 한다.
stackoveflow에서 말하는 어떤 이는 SQLiteDatabase를 절대 닫지 말아야 한다고 권고했는 데, logcat 은 당신에게 아래와 같은 존경(?)의 메시지를 보낼 것이다. 이는 좋은 생각이 아니라고 생각한다.
Leak found
Caused by: java.lang.IllegalStateException:SQLiteDatabase created and never closed
실제 예제
이에 대한 가능한 솔루션 하나는 데이터베이스 연결이 열렸는지/닫혔는지추적하기 위한 카운터를 만들어 주는 것이다.
public class DatabaseManager {
privateAtomicInteger mOpenCounter = new AtomicInteger();
private staticDatabaseManager instance;
private staticSQLiteOpenHelper mDatabaseHelper;
privateSQLiteDatabase mDatabase;
public staticsynchronized void initializeInstance(SQLiteOpenHelper helper) {
if(instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public staticsynchronized DatabaseManager getInstance() {
if(instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) methodfirst.");
}
returninstance;
}
publicsynchronized SQLiteDatabase openDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
//Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
returnmDatabase;
}
publicsynchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
//Closing database
mDatabase.close();
}
}
}
그리고 다음과 같이 쓰면 된다.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correctway
데이터베이스가 필요할 때마다 매번 DatabaseManager 클래스의 openDatabase() 메서드를 호출 해야만 한다.
이 메서드 내에 얼만큼 데이터베이스가 열려졌는지 카운터를 만들어 놓는다.
만약 카운터가 1과 같다면, 새로운 데이터베이스 연결을만들어도 된다는 뜻이고, 아니라면 데이터베이스 연결이 이미 열려진 상태라는 것을 알 수 있다.
closeDatabase() 메서드도 마찬가지이다. 이 메서드를 매번 호출해서 카운트를 감소시키고, 0이 되는 때에 데이터베이스 연결을 닫아야만 한다.
이렇게 되면 안전하게 데이터베이스를 사용할 준비가 된 것이다 – 쓰레드에안전하다라는 말이다.
참고사이트
'모바일프로그래밍 > 안드로이드' 카테고리의 다른 글
[안드로이드&자바]자바와 안드로이드 자바 간 비교 (0) | 2023.01.15 |
---|---|
[ProGuard] 안드로이드 애플리케이션의 최적화, 난독화 및 최소화 (0) | 2023.01.14 |
안드로이드 확장 리스트 뷰에 대한 자습서 (0) | 2023.01.12 |
안드로이드 APK 분석 참고 문서 (0) | 2023.01.04 |
안드로이드 이해하기 - 첫 걸음 떼기 (0) | 2023.01.02 |