Skip to content

Latest commit

 

History

History
executable file
·
146 lines (105 loc) · 4.66 KB

Android_SQLite_best_practice.md

File metadata and controls

executable file
·
146 lines (105 loc) · 4.66 KB

#Android SQLite最佳实践

##SqliteOpenHelper

它保持一个数据的连接,看起来它可以提供读写连接,实际上则不是,即使向它要一个Read Only的连接,它也会返回一个write连接。

因此,一个SqliteOpenHelper对应着一个connection。即使用多个线程去使用它,SqliteDatabase实例也会使用锁来保证顺序化的操作。

因此,无论有多少个线程,使用同一个SqliteOpenHelper的实例可以保证代码的正确性。如果多个线程同时使用多个SqliteOpenHelper的实例进行读写,则会引发冲突,导致有部分写不成功,更严重的是,如果此时的写操作代码有误,也不会抛出异常,而只是会在Logcat里面打印一条message。

##错误示例一

假设自己实现了SQLiteOpenHelper

public class DatabaseHelper extends SQLiteOpenHelper { ... }

如果我们分别通过不同的线程去使用它

// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();

// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();

这是你将会得到一个错误信息

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

如上面提到的,DatabaseHelper将会建立一个connection,多个connection同时进行写操作会导致错误。

##错误示例二

我们尝试改进上述代码,将DatabaseHelper改为单例模式

public class DatabaseManager {

	private static DatabaseManager instance;
	private static SQLiteOpenHelper mDatabaseHelper;

	public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
		if (instance == null) {
			instance = new DatabaseManager();
			mDatabaseHelper = helper;
		}
	}

	public static synchronized DatabaseManager getInstance() {
		if (instance == null) {
			throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
				" is not initialized, call initialize(..) method first.");
		}

		return instance;
	}

	public SQLiteDatabase getDatabase() {
		return new mDatabaseHelper.getWritableDatabase();
	}

}

我们尝试调用如下

// In your application class
DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
// 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 an already-closed object: SQLiteDatabase

原因是我们使用了同一个数据库连接,getDatabase方法对两个线程返回同一个SQLiteDatabase对象。在线程一关掉它之后,线程二会崩溃。我们需要保证不关闭有人正在使用的SQLiteDatabase,有些人推荐永远不关闭SQLiteDatabase,但是它会导致异常

Leak found Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

##可用版本

我们可以自行实现一个引用计数

public class DatabaseManager {

	private int mOpenCounter;

	private static DatabaseManager instance;
	private static SQLiteOpenHelper mDatabaseHelper;
	private SQLiteDatabase mDatabase;

	public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
		if (instance == null) {
			instance = new DatabaseManager();
			mDatabaseHelper = helper;
		}
	}

	public static synchronized DatabaseManager getInstance() {
		if (instance == null) {
			throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
				" is not initialized, call initializeInstance(..) method first.");
		}

		return instance;
	}

	public synchronized SQLiteDatabase openDatabase() {
		mOpenCounter++;
		if(mOpenCounter == 1) {
			// Opening new database
			mDatabase = mDatabaseHelper.getWritableDatabase();
		}
		return mDatabase;
	}

	public synchronized void closeDatabase() {
		mOpenCounter--;
		if(mOpenCounter == 0) {
			// Closing database
			mDatabase.close();
		}
	}
}

使用方法如下

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); 不应该直接关闭它
DatabaseManager.getInstance().closeDatabase(); //正确的关闭方式

这个例子中,我们需要做的是在application里面调用initializeInstance进行一次实例化,然后随用随关。