一、构造方法
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
// version 必须 >= 1
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
二、获取数据库
1. 获取可读写数据库
/**
* 1. 该方法可能会比较耗时,不能在 main 线程调用
* 2. 数据库不存在时会调用 onCreate()
* 3. 如果需要升级会调用 onUpgrade()
* 4. 如果需要降级会调用 onDowngrade(), 该方法默认会抛出异常
* 5. 会调用 onOpen(db) 方法, 该方法为空实现
* 6. 在磁盘已满的情况下,该方法会调用失败,抛出 SQLiteException
* 7. 如果已存在只读数据库,则对 SQLiteConnectionPool 做处理,更改连接池为可写
*/
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
2. 获取只读数据库
/**
* 1. 如果已存在可用数据库(只读、可读写均可),则直接返回已有数据库
* 2. 在不存在可用数据库的情况下,调用该方法同调用 getWritableDatabase() 效果一致
* 3. 在一些特殊情况下,如磁盘已满,则打开可读写数据库失败,会再次尝试打开只读数据库
* 4. 该方法的调用不能放在 main 线程
*
* @throws SQLiteException if the database cannot be opened
* @return a database object valid until {@link #getWritableDatabase}
* or {@link #close} is called.
*/
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
3. 获取数据库逻辑
/**
*
* @param writable true 代表要打开的是可读写数据库,
* false 代表要打开的是只读数据库
* @return
*/
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
// mDatabase 已被关闭,需要重新获取
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
// 要获取的是只读数据库,或者之前获取的 mDatabase 是可写的,则直接返回 mDatabase
return mDatabase;
}
}
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
if (writable && db.isReadOnly()) {
// 之前获取的 mDatabase 是只读的,现在要求获取可写数据库的情况
// 对 SQLiteConnectionPool 做了些处理,更改连接池中的连接为可写
db.reopenReadWrite();
}
} else if (mName == null) {
// 创建只存在于内存中的数据库
db = SQLiteDatabase.create(null);
} else {
try {
if (DEBUG_STRICT_READONLY && !writable) {
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else {
// 优先以可写的模式打开数据库,mEnableWriteAheadLogging 默认为 false, 参数 0 为"可读写"模式
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
// 打开数据库时出现异常
if (writable) {
// 如果要打开的可写数据库,则直接抛出异常
throw ex;
}
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
// 如果要打开的是只读数据库,则再次尝试以"只读"模式打开
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler);
}
}
onConfigure(db);
final int version = db.getVersion();
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
// 注意此处,开启事务
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(db);
if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
// db != mDatabase 意味着 try 语句中发生了异常
// 把新产生的 db close 掉
db.close();
}
}
}
三、开启数据库并发功能
/**
* 1. 该功能开启多线程并发查询功能,每个查询使用单独的一条连接(连接池中的连接数有限制)
* 2. 该功能默认是未开启状态. 这种情况下不能进行并发读写
* 3. 该功能开启后,写操作将会写入一个单独的日志文件. 这样在写进行时,其它线程可以同时
* 进行读操作,获取的是写操作开始前的数据. 写操作完成后,读的线程可以察觉到数据库的新状态
* 4. 开启该功能将会占用更多内存,因为同时有多个数据库的连接存在
* 5. 如果仅仅是单线程操作数据库,或者对于并发的需求并不强烈,不建议启用该功能
* 6. 有事务在进行时不能调用该方法,否则会抛出异常
* 7. 该方法的调用建议放在打开数据库前,避免出现异常
*
* Enables or disables the use of write-ahead logging for the database.
*
* Write-ahead logging cannot be used with read-only databases so the value of
* this flag is ignored if the database is opened read-only.
*
* @param enabled True if write-ahead logging should be enabled, false if it
* should be disabled.
*
* @see SQLiteDatabase#enableWriteAheadLogging()
*/
public void setWriteAheadLoggingEnabled(boolean enabled) {
synchronized (this) {
if (mEnableWriteAheadLogging != enabled) {
// 只对可写的数据库起作用
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
// 如果数据库已存在,则调用 mDatabase.enableWriteAheadLogging() 或者 mDatabase.disableWriteAheadLogging()
// 这部分的调用可能会抛出异常,需要在没有事务进行时调用
if (enabled) {
mDatabase.enableWriteAheadLogging();
} else {
mDatabase.disableWriteAheadLogging();
}
}
mEnableWriteAheadLogging = enabled;
}
}
}
四、打开或创建数据库
1. 打开数据库时,如果不存在就会创建
// 优先以可写的模式打开数据库,mEnableWriteAheadLogging 默认为 false, 参数 0 为"可读写"模式
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
2. Android N 及以上不支持 MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE, 会抛出异常
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
DatabaseErrorHandler errorHandler) {
// Android N 及以上不支持 MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE,会抛出异常
checkMode(mode);
File f = getDatabasePath(name);
// CREATE_IF_NECESSARY 默认就会有,不需要外部设置
int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
// 是否开启数据库并发功能
if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
}
// 是否支持本地化配页?
if ((mode & MODE_NO_LOCALIZED_COLLATORS) != 0) {
flags |= SQLiteDatabase.NO_LOCALIZED_COLLATORS;
}
// 创建数据库
SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);
// 修改数据库文件的权限
setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
}
3. 调用 SQLiteDatabase.openDatabase() 打开数据库
/**
* Open the database according to the flags {@link #OPEN_READWRITE}
* {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
*
* <p>Sets the locale of the database to the the system's current locale.
* Call {@link #setLocale} if you would like something else.</p>
*
* <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
* used to handle corruption when sqlite reports database corruption.</p>
*
* @param path to database file to open and/or create
* @param factory an optional factory class that is called to instantiate a
* cursor when query is called, or null for default
* @param flags to control database access mode
* @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption
* when sqlite reports database corruption
* @return the newly opened database
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
// 初始化 SQLiteDatabase
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
// 创建连接池,打开主连接
db.open();
return db;
}
网友评论