SQLite源码分析
构造方法
在 SQLite基础介绍 中介绍到了涉及到的类,SQLiteDatabase代表数据库的类,要想获得SQLiteDatabase类的对象,就必须要重写SQLiteOpenHelper抽象类。onCreate()方法和onUpgrade()方法是必须重写的方法。四个和五个参数的构造方法写其一或都写都可以。例:
public class DBOpenHelper extends SQLiteOpenHelper {
private Context mContext ;
public static String batabaseName = "message.db";
public static String tableName = "message" ;
public static String product_name = "product_name";
public static String product_type = "product_type";
public static String sale_price = "sale_price";
public static String purcgase_price = "purcgase_price";
public static String regist_date = "regist_date";
public static String sendname = "sendname";
/**
*四个参数的构造方法
* @param context
* @param name 数据库的名字
* @param factory
* @param version 数据库的版本号
*/
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context ;
}
/**
*五个参数的构造方法
* @param context
* @param name 数据库的名字
* @param factory
* @param version 数据库的版本号
*/
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
mContext = context ;
}
@Override
public void onCreate(SQLiteDatabase db) {
String createTable = "create table if not exists "+ tableName + "( product_id char(4) primary key , product_name varchar(100) not null , product_type varchar(32) not null , " +
"sale_price int , purcgase_price int , regist_date date )";
db.execSQL(createTable);
Log.d("TAG" , "数据库创建");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d("TAG" , "onUpgrade oldVersion = "+ oldVersion + " newVersion = "+ newVersion);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
super.onDowngrade(db, oldVersion, newVersion);
Log.d("TAG" , "onDowngrade oldVersion = "+ oldVersion + " newVersion = "+ newVersion);
}
}
从上面的构造方法中可以看到,不管是四个还是五个参数的构造方法里面都调用super()方法。我们再看SQLiteOpenHelper类:
public abstract class SQLiteOpenHelper {
private static final boolean DEBUG_STRICT_READONLY = false;
....
/**
*四个参数构造方法,里面调用五个参数的构造方法。
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
/**
*五个参数构造方法,里面调用六个参数的构造方法。
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
this(context, name, factory, version, 0, errorHandler);
}
/**
*六个参数构造方法
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
}
.....
}
从上面的构造方法中看到,最终都会调用到六个参数的构造方法。传入进来的重要参数有数据库名称name,数据库的版本号version,支持的最小版本minimumSupportedVersion。这些值并赋值给相应的属性。在六个参数的方法中我们,根据传入进来的版本号version做判断,如果小于1会抛出异常。所以我们在创建SQLiteOpenHelper类继承类的对象的时候,传入进去的版本号不能小于1。
getReadableDatabase()和getWritableDatabase()
获取数据库不能直接new SQLiteDatabase();而实通过SQLiteOpenHelper类的getReadableDatabase()和getWritableDatabase()方法获取到SQLiteDatabase类对象。看源码:
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
从上面的源码中看到getReadableDatabase()和getWritableDatabase()方法都调用了getDatabaseLocked(boolean writable);方法,只是传入的参数不同,true和false有什么不同呢?具体看getDatabaseLocked()方法:
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) { // 首次调用时mDatabase必为null
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close(). 这句注释写着mDatabase.close()后,需要重新创建。
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business. 只读的方式
return mDatabase;
}
}
if (mIsInitializing) { // 这个mIsInitializing属性只有在这个方法中复制,并且最后被复制flase
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) { // 首次必为null
if (writable && db.isReadOnly()) {
db.reopenReadWrite(); // 重置打开方式 只读 - > 读写
}
} else if (mName == null) { // 数据库的名字不为null
db = SQLiteDatabase.create(null);
} else {
try {
if (DEBUG_STRICT_READONLY && !writable) { // DEBUG_STRICT_READONLY为final值且false ,所以不走这里。
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else { // 最终调用到这
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(); // 获取数据库的当前版本号。首次调用时为0。
if (version != mNewVersion) { // 数据库当前版本号与传进来的最新版本号不相等时(小于或大于)调用
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
if (version > 0 && version < mMinimumSupportedVersion) {
File databaseFile = new File(db.getPath());
onBeforeDelete(db);
db.close();
if (SQLiteDatabase.deleteDatabase(databaseFile)) {
mIsInitializing = false;
return getDatabaseLocked(writable);
} else {
throw new IllegalStateException("Unable to delete obsolete database "
+ mName + " with version " + version);
}
} else {
db.beginTransaction();
try {
if (version == 0) { // 首次调用时,获取到的版本号为0,所以onCreate()方法得到调用。
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else { // 当数据库当前的版本号小于传进来的版本号时,onUpgrade()方法得到调用。
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; // 最终都会走这里,所有mIsInitializing最终会被赋值为false 。
if (db != null && db != mDatabase) {
db.close();
}
}
}
/**
* SQLiteDatabase类的方法
* Reopens the database in read-write mode.以读写模式重新打开数据库。
* If the database is already read-write, does nothing.
* @throws SQLiteException if the database could not be reopened as requested, in which
* case it remains open in read only mode.
* @throws IllegalStateException if the database is not open.
* @see #isReadOnly()
* @hide
*/
public void reopenReadWrite() {
synchronized (mLock) {
throwIfNotOpenLocked();
if (!isReadOnlyLocked()) {
return; // nothing to do 如果数据库已经读写了,什么也不做。
}
// Reopen the database in read-write mode.
final int oldOpenFlags = mConfigurationLocked.openFlags;
mConfigurationLocked.openFlags = (mConfigurationLocked.openFlags & ~OPEN_READ_MASK)
| OPEN_READWRITE;
try {
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
} catch (RuntimeException ex) {
mConfigurationLocked.openFlags = oldOpenFlags; // 如果数据库不能按要求重新打开,则
//它以只读模式保持打开。
throw ex;
}
}
}
writable为trues并且只能读的时候,尝试重置读写方式,即只读变为读写,但如果失败,就重置为只读方式。
onCreate()方法
在上面的源码中我们看到,调用getWritableDatabase()或getReadableDatabase()方法时,都会调用到getDatabaseLocked();方法,当是首次调用时,获取到的版本号为0,所以onCreate()方法得到调用,之后把最新的版本好设置给数据库,最新的版本号是我们传递进去的,在分析构造方法时我们说到不能传小于1的,所以除了首次调用,后面获取数据库的版本号都不可能为了0了,所以onCreate()方法只调用一次。即onCreate()方法帮不是在在创建SQLiteOpenHelper对象时嗲用调用的。
onUpgrade()方法
从上面的源码中我看到,当我们传递进去的最新版本号(mNewVersion)大于数据可的当前版本号时,onUpgrade()方法得到调用。但是有个疑问,mNewVersion是从SQLiteOpenHelper中的构造方法中传进去的,即mNewVersion想得到新的值必须要重新掉用SQLiteOpenHelper类的构造方法。传入新的mNewVersion值。当我们重新new SQLiteOpenHelper对象时, 就不再是以前SQLiteOpenHelper类的那个对象了。那我们在调用这个对象的getWritableDatabase()或getReadableDatabase() 方法得到的SQLiteDatabase对象还是以前那个对象吗?通过这个对象来操作数据库能操作到以前的数据库吗?回答1:对象不在是以前那个对象。回答2:对象虽然不在是以前那个对象了,但还是操作到以前的数据库的。应为数据库的名字没有变,数据库的唯一标识就是数据库的名字。就像文件一样。通过同一路径,创建很多个不同的文件的对象,但这些文件对象操作的都是同一个文件。
数据库的创建及位置
数据库存储的数据一般是比较多的,默认的数据库是存储在内部的,我们在文件存储一节中分析到,数据盘庞大的不应设为内部存储。那数据库的存储位置可以改变或设为外部存储吗?答案是可以的。我们在上面的源码中getDatabaseLocked();方法中看到,创建SQLiteDatabase数据库对象,其实是通过context类的openOrCreateDatabase()方法来完成的。我们来看context的openOrCreateDatabase()方法的源码。
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
DatabaseErrorHandler errorHandler) {
checkMode(mode);
File f = getDatabasePath(name);// 获取数据库文件(路径)
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;
}
/**
*获取数据库的存储路径
*/
@Override
public File getDatabasePath(String name) {
File dir;
File f;
//数库的名称包含路劲时,获取路径
if (name.charAt(0) == File.separatorChar) {
String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
dir = new File(dirPath);
name = name.substring(name.lastIndexOf(File.separatorChar));
f = new File(dir, name);
if (!dir.isDirectory() && dir.mkdir()) {
FileUtils.setPermissions(dir.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
}
} else {//数库的名称不包含路劲时,获取数据库的存储的默认路径/data/data/<应用包名>/databases
dir = getDatabasesDir();
f = makeFilename(dir, name);
}
return f;
}