解析 SQLiteOpenHelper

“SQLiteOpenHelper” 是一个用来管理数据库的建立和版本管理的辅助类。它是一个抽象类,要使用它必须建立一个子类继承 SQLiteOpenHelper,并实现 onCreate,onUpgrade 这两个抽象方法。这样,若是数据库存在,它就会打开;若是不存在,就会建立这个数据库,而且若是必要的话会自动升级数据库。为了确保数据库始终处于一个合理的状态,它会使用事务。它便于 ContentProvider 实如今第一次使用以前延迟打开和升级数据库,以免数据库升级时间过长阻塞应用的启动。javascript

构造方法

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) {
    if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);

    mContext = context;
    mName = name;
    mFactory = factory;
    mNewVersion = version;
    mErrorHandler = errorHandler;
}复制代码

能够看到,第一个 4 个参数的构造方法,会调用第二个 5 个参数的构造方法,但 4 个参数的构造方法会很快的返回。构造方法的做用是建立一个辅助对象来建立、打开、管理一个数据库。须要注意的是若是没有调用过 getWriteableDatabase 或者 getReadableDatabase 数据库不会自动建立或者打开。构造方法中的参数除了context 和 version 以外,其余参数能够为 null。当 name 为 null 时,会在内存中建立数据库;若是CursorFactory 为 null,会使用一个默认的。java

从 5 个参数的构造方法中,能够看到 version 不能小于 1 ,不然会抛出非法参数异常。数据库

onCreate

public abstract void onCreate(SQLiteDatabase db);复制代码

很明显这是一个抽象方法,前面咱们也说过了,它是继承 SQLiteOpenHelper 必需要实现的方法之一。缓存

第一次建立数据库时调用,在这个方法中建立表和表的初始化。并发

onUpgrade

public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);复制代码

这也是一个抽象方法,它也是继承 SQLiteOpenHelper 必需要实现的方法之一。ide

当数据库须要升级时,会调用这个方法。应该使用这个方法来实现删除表、添加表或者作一些须要升级新的策略版本的事情。此方法在事务中执行。若是抛出异常,全部更改将自动回滚。优化

onDowngrade

public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    throw new SQLiteException("Can't downgrade database from version " +
            oldVersion + " to " + newVersion);
}复制代码

当数据库须要降级时调用。这个方法与 onUpgrade(SQLiteDatabase, int, int) 方法很是类似,它是在数据库以前版本比当前的版本新的时候,才会被调用。可是这个方法不是抽象的,所以它不强制要求重写。若是这个方法没有被重写,默认的实现会拒绝数据库版本降级,并抛出SQLiteException异常。这个方法是在事务中执行的。若是有异常被抛出,全部的改变都会被回滚。ui

这里我要说一下,这种状况是怎么发生的,好比用户当前数据库版本号为 2,可是Ta又覆盖安装了一个旧版本(版本号为1),这个时候数据库版本就从 2 降到了 1,就会触发 onDowngrade 方法了。this

getDatabaseName

public String getDatabaseName() {
    return mName;
}复制代码

这个方法再简单不过了,就是返回数据库的名字,它和构造方法中传入的名字是同样的。spa

setWriteAheadLoggingEnabled

public void setWriteAheadLoggingEnabled(boolean enabled) {
    synchronized (this) {
        if (mEnableWriteAheadLogging != enabled) {
            if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
                if (enabled) {
                    mDatabase.enableWriteAheadLogging();
                } else {
                    mDatabase.disableWriteAheadLogging();
                }
            }
            mEnableWriteAheadLogging = enabled;
        }
    }
}复制代码

这个方法的做用是:启用或禁用数据库的预写日志。

预写日志不能用于只读的数据库,所以若是数据库是以只读的方式被打开,这个标记值会被忽略。

源代码的第 3 行 if (mEnableWriteAheadLogging != enabled) 的使用方法能够借鉴一下,这行代码从逻辑上是无关紧要的,它的妙处在于减小了没必要要的代码执行,提升了效率(若是设置的值和当前值相等,就不做任何操做)。

那么这个预写日志究竟是什么鬼呢?

从第 五、6 行代码能够看到若是 enabled 的值为 true,就会调用 mDatabase.enableWriteAheadLogging();

调用此方法后,只要数据库保持打开,则并行执行查询操做。用于并行执行查询的链接的最大数目取决于设备内存和其余属性。若是一个查询是事务的一部分,那么它就在同一个数据库句柄上执行。

它经过打开数据库的多个链接并为每一个查询使用不一样的数据库链接来实如今同一数据库中并行执行多个线程的查询,同时数据库的日志模式也被更改以启用写入与读取同时进行。

当预写日志没有启用时(默认状态),同一时间在数据库上读取和写入是不可能的。由于写入线程会在修改数据库以前隐式地获取数据库上的互斥锁,以防止写入操做完成以前其余线程读取访问数据库。

相反,当启用预写日志时,写入操做会在分离的日志文件中进行,该文件容许读取同时进行。当数据库正在进行写入操做时,其余线程上的读取操做将在写入开始前会感知数据库的状态,当写入操做完成后,其余线程上的读取操做将感知数据库的新状态。

当数据库同时被多个线程同时访问和修改时,启用预写日志是一个好办法。然而,开启预写日志功能,会比普通日记使用更多的内存,由于同一个数据库有多个链接。所以,若是一个数据库只有一个线程使用,或者优化并发不是很重要,那么应该禁用预写日志功能。

getWritableDatabase 和 getReadableDatabase

public SQLiteDatabase getWritableDatabase() {
    synchronized (this) {
        return getDatabaseLocked(true);
    }
}复制代码

这个方法用于建立或打开用于读取和写入的数据库。第一次被调用时数据库将被打开,onCreate、onUpgrade、onOpen 将被调用。一旦成功打开,数据库将被缓存,所以每次须要写入数据库时,均可以调用此方法(确保当再也不须要对这个数据库进行操做时,调用 close方法关闭)。权限问题或磁盘已满等问题可能会致使这个方法打开数据库失败,但若是问题是固定的,屡次尝试可能会成功。数据库升级可能须要很长时间,不该该从应用程序主线程调用此方法。

public SQLiteDatabase getReadableDatabase() {
    synchronized (this) {
        return getDatabaseLocked(false);
    }
}复制代码

这个方法用于建立或打开数据库。它和 getWriteableDatabase() 方法返回的是同一个对象,可是由于它只须要返回只读的数据库,因此不会有磁盘已满等问题。

这两个方法的代码都很简单,调用了 getDatabaseLocked(true/false);下面来看一下这个方法。

getDatabaseLocked

private SQLiteDatabase getDatabaseLocked(boolean writable) {
    if (mDatabase != null) {
        if (!mDatabase.isOpen()) {
            // Darn! The user closed the database by calling mDatabase.close().
            mDatabase = null;
        } else if (!writable || !mDatabase.isReadOnly()) {
            // The database is already open for business.
            return mDatabase;
        }
    }

    if (mIsInitializing) {
        throw new IllegalStateException("getDatabase called recursively");
    }

    SQLiteDatabase db = mDatabase;
    try {
        mIsInitializing = true;

        if (db != null) {
            if (writable && db.isReadOnly()) {
                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 {
                    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(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.close();
        }
    }
}复制代码

首先这是一个私有的方法。在这个方法中会根据各类状况建立或者打开一个数据库并赋值给局部变量 db;以后会调用 onConfigure(db) 这个方法下面会介绍,默认是一个空方法,咱们能够重写此方法,对 db 进行配置;以后会对当前数据库的版本与最新版本进行对比,若是 db 是只读的,会抛出异常(不能对只读数据库进行版本的更新);若是 db 可写,就开启一个事务,进行版本更新;版本更新完毕后,会调用 onOpen(db) 方法,一样这个方法下面会介绍,也是一个空方法,能够重写此方法;最后,会关闭数据库。

onConfigure

在配置数据库链接时调用,从上面 getDatabaseLocked 方法中,咱们能够看出 onConfigure() 在 onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int), onDowngrade(SQLiteDatabase, int, int), onOpen(SQLiteDatabase) 这些方法以前调用。在这个方法中不该修改数据库,除非根据须要配置数据库链接,启用预写日志或外键支持等功能。

onOpen

数据库被打开时,会调用这个方法。在升级数据库以前,这个方法的实现应该调用 isReadOnly() 方法检查数据库是不是只读的。

此方法在配置数据库链接和建立数据库模式、升级或必要的降级以后调用。若是数据库链接的配置必须以某种方式建立,升级,或降级,那么在onConfigure方法中作这些事情。

简书地址:www.jianshu.com/p/842609bfe…
CSDN:blog.csdn.net/xiaomai9498…

相关文章
相关标签/搜索