package android.database; import android.database.sqlite.SQLiteDatabase; /** * An interface to let the apps define the actions to take when the following errors are detected * database corruption */ public interface DatabaseErrorHandler { /** * defines the method to be invoked when database corruption is detected. * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption * is detected. */ void onCorruption(SQLiteDatabase dbObj); }
前面两篇简单介绍SQLite数据库,包括SQLiteDatabase,实际项目中应该很少使用SQLiteDatabase的方法来打开数据库,而是继承SQLiteOpenHelper开发子类,并通过该子类的getReadableDatabase(),getWritableDatabase()方法打开数据库。
SQLite是Android提供的一个管理数据库的工具类(工具类这个概念很重要),可用于管理数据库的创建和版本更新。一般扩展他的onCreate(SQLiteDatabase db)和onUpgrate(SQLiteDatabase db, int oldVersion, int newVersion)。这是两个抽象方法。
构造函数和常用方法
SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)
SQLiteOpenHelper( Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler)
synchronized void | close() 关闭当前SQLiteOpenHelper对象下的所有数据库对象 |
String | getDatabaseName() 获取构造函数中指定的数据库名对应的数据库对象 |
SQLiteDatabase | getReadableDatabase() 打开或创建一个只读的数据库对象 |
SQLiteDatabase | getWritableDatabase() 打开一个可读可写的数据库对象 |
abstract void | onCreate( SQLiteDatabase db) 当数据库第一次创建时调用 |
void | onDowngrade( SQLiteDatabase db, int oldVersion, int newVersion) 当数据库降级时调用 |
void | onOpen( SQLiteDatabase db) 数据库打开时调用 |
abstract void | onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion) 当数据库升级时调用 |
使用SQLiteOpenHelper时,需要重写oncreate和onUpgrade两个方法
onCreate(SQLiteDatabae db); 用于初次使用软件时创建数据库表,调用getReadableDatabase()或者getWritableDatabase()生成数据库时,如果数据库不存在,则系统会自动生成一个数据库,接着调用onCreate()方法,onCreate()方法在初次生成数据库时才会被调用,重写onCreate()时可以生成数据库表结构并添加一些应用使用到的初始化数据。
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); 用于更新软件时更新数据库表结构,方法在数据库的版本发生变化时会被调用,oldVersion和newVersion分别表示旧版本和新版本,当程序创建SQLiteOpenHelper对象时,必须指定一个version参数,该参数就决定了所使用的数据库的版本,也就是说,数据库的版本由程序员控制,只要某次创建SQLiteOpenHelper对象时指定的数据库版本号高于之前的版本号,系统会自动触发onUpgrade(SQLiteDatabae db, int oldVersion, int newVersion)方法,程序就可以根据原版本号和目标版本号进行判断,既可根据版本号进行必要的表结构更新。
实际上,应用程序在升级表结构的时候完全可能因为现有的数据造成升级失败。后面会专门写一篇分析数据库的升级问题。
SQLiteOpenHelper源码学习
先贴上简化后的源码,可以通过原注释和后面的总结一起理解
public abstract class SQLiteOpenHelper { /** * 创建一个SQLite数据库的帮助类对象用于打开和管理一个数据库 * 这个方法通常返回非常快速,这个数据库对象并不会在这里直接创建, * 知道调用了getWritableDatabase或者getReadableDatabase方法 */ public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { this(context, name, factory, version, new DefaultDatabaseErrorHandler()); } /** * 和上面的构造函数相同,不过这里多传了一个DatabaseErrorHandler对象, * 这个对象用于处理数据库的异常 */ 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; } public String getDatabaseName() { return mName; } /** * 1. 创建或打开一个可读可写的数据库,如果是第一次调用,那么数据库会被打开,oncreate,onUpgrade,open都可能调用。 * 2. 如果打开成功,那么数据库会被缓存,所以你可以在任何时候对其进行读写。 * 3. 如果不需要再使用这个数据库,那么确保调用close方法将其关闭。 * 4. 如果发生错误,比如说磁盘已满,或者权限不允许,则正常调用可能会失败,修复问题后可以再次调用。 * 5. 数据库升级可能会耗费较长时间,所以不应在应用的主线程中调用这个方法,包括ContentProvider。 */ public synchronized SQLiteDatabase getWritableDatabase() { if (mDatabase != null) { if (!mDatabase.isOpen()) { // darn! the user closed the database by calling mDatabase.close() mDatabase = null; } else if (!mDatabase.isReadOnly()) { return mDatabase; // The database is already open for business } } 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 = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler); } int version = db.getVersion(); if (version != mNewVersion) { db.beginTransaction(); try { if (version == 0) { 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); success = true; return db; } finally { mIsInitializing = false; if (success) { if (mDatabase != null) { try { mDatabase.close(); } catch (Exception e) { } mDatabase.unlock(); } mDatabase = db; } else { if (mDatabase != null) mDatabase.unlock(); if (db != null) db.close(); } } } /** * 创建或者打开一个数据库,这个数据库对象和调用getWritableDatabase打开的数据库是同一个,除非发生某些意外情况, * 在这种情况下,一个只读的数据库对象会被返回,如果问题修复,那么下次调用getWritableDatabase将会成功 * 这时只读的数据库会被关闭,可读可写的数据库会被返回。 * 其他的和getWritableDatabase()方法一样。 */ public synchronized SQLiteDatabase getReadableDatabase() { if (mDatabase != null) { if (!mDatabase.isOpen()) { // darn! the user closed the database by calling mDatabase.close() mDatabase = null; } else { 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 = 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); /** * database. 调用已经打开的数据库,实现类在更新表之前应该检查数据库是否是只读的。 */ } 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(); } } public synchronized void close() { if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); if (mDatabase != null && mDatabase.isOpen()) { mDatabase.close(); mDatabase = null; } } public abstract void onCreate(SQLiteDatabase db); public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { throw new SQLiteException("Can't downgrade database from version " + oldVersion + " to " + newVersion); } public void onOpen(SQLiteDatabase db) {} }
1. 从始至中,SQLiteOpenHelper中只持有一个数据库对象,并且一般情况下,通过getWritableDatabase()和getReadableDatabase()创建或打开时获取到的是同一个SQLiteDatabase对象。
2. 如果当前数据库是只读的,那么再次调用getWritableDatabase()获取可读可写数据库对象时会重新打开现有对象。
3. 如果当前不存在数据库对象,并且初始化时没有指定数据库名称,也会创建一个默认数据库。
4. 如果数据库对象不存在,但是指定了数据库的名称,那么系统会先查看是否有指定数据库,如果有则直接打开,没有则先创建再打开数据库。
5. 数据库或降级升级时,会根据版本号进行判断,然后把升级和降级的过程作为一个事务进行操作,确保数据安全。
6. 创建数据库对象的默认版本号为0