使用注意事项:
- getWritableDatabase() 不要放在 UI 线程,存在阻塞和操作耗时的情况;
- getReadableDatabase() 优先返回 getWritableDatabase(), 发生 SQLiteException 异常后,才尝试获取只读模式的数据库
- getReadableDatabase() 由于调用了 getWritableDatabase(), 同样不能放在 UI 线程
一、构造方法
/**
* 该方法会快速返回,并不会创建或打开数据库
*
* Create a helper object to create, open, and/or manage a database.
* This method always returns very quickly. The database is not actually
* created or opened until one of {@link #getWritableDatabase} or
* {@link #getReadableDatabase} is called.
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, new DefaultDatabaseErrorHandler());
}
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
if (errorHandler == null) {
throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null.");
}
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
二、getWritableDatabase()
/**
* 第一次调用该方法时,将会打开数据库,并会视情况调用 onCreate(), onUpgrade(), onDowngrade(), onOpen()
*
* 该方法的调用不要放在 UI 线程,存在阻塞和操作耗时的情况
*
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
* called.
*
* <p>Once opened successfully, the database is cached, so you can
* call this method every time you need to write to the database.
* (Make sure to call {@link #close} when you no longer need the database.)
* Errors such as bad permissions or a full disk may cause this method
* to fail, but future attempts may succeed if the problem is fixed.</p>
*
* <p class="caution">Database upgrade may take a long time, you
* should not call this method from the application main thread, including
* from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened for writing
* @return a read/write database object valid until {@link #close} is called
*/
public synchronized SQLiteDatabase getWritableDatabase() {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// 若是 mDatabase 已被关闭,则置空, 重新获取
// darn! the user closed the database by calling mDatabase.close()
mDatabase = null;
} else if (!mDatabase.isReadOnly()) {
// mDatabase 已满足条件,直接返回
// The database is already open for business
return mDatabase;
}
}
if (mIsInitializing) {
// 该方法不允许被递归调用
throw new IllegalStateException("getWritableDatabase called recursively");
}
// If we have a read-only database open, someone could be using it
// (though they shouldn't), which would cause a lock to be held on
// the file, and our attempts to open the database read-write would
// fail waiting for the file lock. To prevent that, we acquire the
// lock on the read-only database, which shuts out other users.
boolean success = false;
SQLiteDatabase db = null;
if (mDatabase != null){
/**
* 应对之前获取的是只读数据库的情况
* 等待该线程获取到锁对象后返回,此处可能造成阻塞
*/
mDatabase.lock();
}
try {
mIsInitializing = true;
if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
/**
* 层层跟进,最终:
* db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum)
* 注意此处的 mode:0 == SQLiteDatabase.OPEN_READWRITE, 即可读写模式
*/
db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
}
int version = db.getVersion();
if (version != mNewVersion) {
db.beginTransaction();
try {
if (version == 0) {
/**
* 数据库不存在时才会调用 onCreate()
* 由子类实现数据库的创建
*/
onCreate(db);
} else {
if (version > mNewVersion) {
// 需要注意降版本的情况,该方法的默认实现是抛出异常
onDowngrade(db, version, mNewVersion);
} else {
// 由子类实现升级数据库版本
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
// onOpen 为空实现
onOpen(db);
success = true;
return db;
} finally {
// 注意此处对 try 语句的用法,只有 finally 语句块,没有 catch 语句块
mIsInitializing = false;
if (success) {
/**
* 成功获取到可读写的数据库
*/
if (mDatabase != null) {
try { mDatabase.close(); } catch (Exception e) { }
mDatabase.unlock();
}
// 注意此处,把获取到的可读写数据库赋值给 mDatabase
mDatabase = db;
} else {
if (mDatabase != null){
mDatabase.unlock();
}
if (db != null) db.close();
}
}
}
三、getReadableDatabase()
/**
* 该方法的调用不要放在 UI 线程,存在阻塞和操作耗时的情况
* 优先返回 getWritableDatabase()
* getWritableDatabase() 发生 SQLiteException 异常后,才尝试获取只读模式的数据库
*
* Create and/or open a database. This will be the same object returned by
* {@link #getWritableDatabase} unless some problem, such as a full disk,
* requires the database to be opened read-only. In that case, a read-only
* database object will be returned. If the problem is fixed, a future call
* to {@link #getWritableDatabase} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned
* in the future.
*
* <p class="caution">Like {@link #getWritableDatabase}, this method may
* take a long time to return, so you should not call it from the
* application main thread, including from
* {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened
* @return a database object valid until {@link #getWritableDatabase}
* or {@link #close} is called.
*/
public synchronized SQLiteDatabase getReadableDatabase() {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// darn! the user closed the database by calling mDatabase.close()
// 若是 mDatabase 已被关闭,则置空, 重新获取
mDatabase = null;
} else {
// 如果 mDatabase 未被 close(),则直接返回
return mDatabase; // The database is already open for business
}
}
if (mIsInitializing) {
throw new IllegalStateException("getReadableDatabase called recursively");
}
try {
// 注意此处,优先获取可读写的数据库
return getWritableDatabase();
} catch (SQLiteException e) {
if (mName == null) throw e; // Can't open a temp database read-only!
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
}
SQLiteDatabase db = null;
try {
mIsInitializing = true;
String path = mContext.getDatabasePath(mName).getPath();
/**
* 层层跟进,最终:
* db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum)
* 注意此处的 SQLiteDatabase.OPEN_READONLY, 即只读模式
*/
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler);
if (db.getVersion() != mNewVersion) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + path);
}
// 该方法为空实现
onOpen(db);
Log.w(TAG, "Opened " + mName + " in read-only mode");
mDatabase = db;
return mDatabase;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase){
db.close();
}
}
}