Sqlite 源码分析 -- SQLiteOpenHelper (API 24)

一、构造方法

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;
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容