一、構(gòu)造方法
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;
}
二、獲取數(shù)據(jù)庫(kù)
1. 獲取可讀寫數(shù)據(jù)庫(kù)
/**
* 1. 該方法可能會(huì)比較耗時(shí),不能在 main 線程調(diào)用
* 2. 數(shù)據(jù)庫(kù)不存在時(shí)會(huì)調(diào)用 onCreate()
* 3. 如果需要升級(jí)會(huì)調(diào)用 onUpgrade()
* 4. 如果需要降級(jí)會(huì)調(diào)用 onDowngrade(), 該方法默認(rèn)會(huì)拋出異常
* 5. 會(huì)調(diào)用 onOpen(db) 方法, 該方法為空實(shí)現(xiàn)
* 6. 在磁盤已滿的情況下,該方法會(huì)調(diào)用失敗,拋出 SQLiteException
* 7. 如果已存在只讀數(shù)據(jù)庫(kù),則對(duì) SQLiteConnectionPool 做處理,更改連接池為可寫
*/
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
2. 獲取只讀數(shù)據(jù)庫(kù)
/**
* 1. 如果已存在可用數(shù)據(jù)庫(kù)(只讀、可讀寫均可),則直接返回已有數(shù)據(jù)庫(kù)
* 2. 在不存在可用數(shù)據(jù)庫(kù)的情況下,調(diào)用該方法同調(diào)用 getWritableDatabase() 效果一致
* 3. 在一些特殊情況下,如磁盤已滿,則打開可讀寫數(shù)據(jù)庫(kù)失敗,會(huì)再次嘗試打開只讀數(shù)據(jù)庫(kù)
* 4. 該方法的調(diào)用不能放在 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. 獲取數(shù)據(jù)庫(kù)邏輯
/**
*
* @param writable true 代表要打開的是可讀寫數(shù)據(jù)庫(kù),
* false 代表要打開的是只讀數(shù)據(jù)庫(kù)
* @return
*/
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
// mDatabase 已被關(guān)閉,需要重新獲取
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
// 要獲取的是只讀數(shù)據(jù)庫(kù),或者之前獲取的 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 是只讀的,現(xiàn)在要求獲取可寫數(shù)據(jù)庫(kù)的情況
// 對(duì) SQLiteConnectionPool 做了些處理,更改連接池中的連接為可寫
db.reopenReadWrite();
}
} else if (mName == null) {
// 創(chuàng)建只存在于內(nèi)存中的數(shù)據(jù)庫(kù)
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 {
// 優(yōu)先以可寫的模式打開數(shù)據(jù)庫(kù),mEnableWriteAheadLogging 默認(rèn)為 false, 參數(shù) 0 為"可讀寫"模式
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
// 打開數(shù)據(jù)庫(kù)時(shí)出現(xiàn)異常
if (writable) {
// 如果要打開的可寫數(shù)據(jù)庫(kù),則直接拋出異常
throw ex;
}
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
// 如果要打開的是只讀數(shù)據(jù)庫(kù),則再次嘗試以"只讀"模式打開
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);
}
// 注意此處,開啟事務(wù)
db.beginTransaction();
try {
if (version == 0) {// 數(shù)據(jù)庫(kù)不存在的情況,調(diào)用 onCreate()
onCreate(db);
} else {
if (version > mNewVersion) {
// 數(shù)據(jù)庫(kù)需要降級(jí)的情況,默認(rèn)實(shí)現(xiàn)是拋出異常
onDowngrade(db, version, mNewVersion);
} else {
// 數(shù)據(jù)庫(kù)需要升級(jí)的情況
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
// 結(jié)束事務(wù)
db.endTransaction();
}
}
// 該方法為空實(shí)現(xiàn)
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 語(yǔ)句中發(fā)生了異常
// 把新產(chǎn)生的 db close 掉
db.close();
}
}
}
三、開啟數(shù)據(jù)庫(kù)并發(fā)功能
/**
* 1. 該功能開啟多線程并發(fā)查詢功能,每個(gè)查詢使用單獨(dú)的一條連接(連接池中的連接數(shù)有限制)
* 2. 該功能默認(rèn)是未開啟狀態(tài). 這種情況下不能進(jìn)行并發(fā)讀寫
* 3. 該功能開啟后,寫操作將會(huì)寫入一個(gè)單獨(dú)的日志文件. 這樣在寫進(jìn)行時(shí),其它線程可以同時(shí)
* 進(jìn)行讀操作,獲取的是寫操作開始前的數(shù)據(jù). 寫操作完成后,讀的線程可以察覺(jué)到數(shù)據(jù)庫(kù)的新狀態(tài)
* 4. 開啟該功能將會(huì)占用更多內(nèi)存,因?yàn)橥瑫r(shí)有多個(gè)數(shù)據(jù)庫(kù)的連接存在
* 5. 如果僅僅是單線程操作數(shù)據(jù)庫(kù),或者對(duì)于并發(fā)的需求并不強(qiáng)烈,不建議啟用該功能
* 6. 有事務(wù)在進(jìn)行時(shí)不能調(diào)用該方法,否則會(huì)拋出異常
* 7. 該方法的調(diào)用建議放在打開數(shù)據(jù)庫(kù)前,避免出現(xiàn)異常
*
* 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) {
// 只對(duì)可寫的數(shù)據(jù)庫(kù)起作用
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
// 如果數(shù)據(jù)庫(kù)已存在,則調(diào)用 mDatabase.enableWriteAheadLogging() 或者 mDatabase.disableWriteAheadLogging()
// 這部分的調(diào)用可能會(huì)拋出異常,需要在沒(méi)有事務(wù)進(jìn)行時(shí)調(diào)用
if (enabled) {
mDatabase.enableWriteAheadLogging();
} else {
mDatabase.disableWriteAheadLogging();
}
}
mEnableWriteAheadLogging = enabled;
}
}
}
四、打開或創(chuàng)建數(shù)據(jù)庫(kù)
1. 打開數(shù)據(jù)庫(kù)時(shí),如果不存在就會(huì)創(chuàng)建
// 優(yōu)先以可寫的模式打開數(shù)據(jù)庫(kù),mEnableWriteAheadLogging 默認(rèn)為 false, 參數(shù) 0 為"可讀寫"模式
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
2. Android N 及以上不支持 MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE, 會(huì)拋出異常
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
DatabaseErrorHandler errorHandler) {
// Android N 及以上不支持 MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE,會(huì)拋出異常
checkMode(mode);
File f = getDatabasePath(name);
// CREATE_IF_NECESSARY 默認(rèn)就會(huì)有,不需要外部設(shè)置
int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
// 是否開啟數(shù)據(jù)庫(kù)并發(fā)功能
if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
}
// 是否支持本地化配頁(yè)?
if ((mode & MODE_NO_LOCALIZED_COLLATORS) != 0) {
flags |= SQLiteDatabase.NO_LOCALIZED_COLLATORS;
}
// 創(chuàng)建數(shù)據(jù)庫(kù)
SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);
// 修改數(shù)據(jù)庫(kù)文件的權(quán)限
setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
}
3. 調(diào)用 SQLiteDatabase.openDatabase() 打開數(shù)據(jù)庫(kù)
/**
* 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);
// 創(chuàng)建連接池,打開主連接
db.open();
return db;
}