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

使用注意事项:

  1. getWritableDatabase() 不要放在 UI 线程,存在阻塞和操作耗时的情况;
  2. getReadableDatabase() 优先返回 getWritableDatabase(), 发生 SQLiteException 异常后,才尝试获取只读模式的数据库
  3. getReadableDatabase() 由于调用了 getWritableDatabase(), 同样不能放在 UI 线程

一、构造方法

/**
 * 该方法会快速返回,并不会创建或打开数据库
 * 
 * Create a helper object to create, open, and/or manage a database.
 * This method always returns very quickly.  The database is not actually
 * created or opened until one of {@link #getWritableDatabase} or
 * {@link #getReadableDatabase} is called.
 *
 * @param context to use to open or create the database
 * @param name of the database file, or null for an in-memory database
 * @param factory to use for creating cursor objects, or null for the default
 * @param version number of the database (starting at 1); if the database is older,
 *     {@link #onUpgrade} will be used to upgrade the database; if the database is
 *     newer, {@link #onDowngrade} will be used to downgrade the database
 */
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
    this(context, name, factory, version, new DefaultDatabaseErrorHandler());
}


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

二、getWritableDatabase()

/**
 * 第一次调用该方法时,将会打开数据库,并会视情况调用 onCreate(), onUpgrade(), onDowngrade(), onOpen()
 *
 * 该方法的调用不要放在 UI 线程,存在阻塞和操作耗时的情况
 *
 * Create and/or open a database that will be used for reading and writing.
 * The first time this is called, the database will be opened and
 * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
 * called.
 *
 * <p>Once opened successfully, the database is cached, so you can
 * call this method every time you need to write to the database.
 * (Make sure to call {@link #close} when you no longer need the database.)
 * Errors such as bad permissions or a full disk may cause this method
 * to fail, but future attempts may succeed if the problem is fixed.</p>
 *
 * <p class="caution">Database upgrade may take a long time, you
 * should not call this method from the application main thread, including
 * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
 *
 * @throws SQLiteException if the database cannot be opened for writing
 * @return a read/write database object valid until {@link #close} is called
 */
public synchronized SQLiteDatabase getWritableDatabase() {
    if (mDatabase != null) {
        if (!mDatabase.isOpen()) {
            // 若是 mDatabase 已被关闭,则置空, 重新获取
            // darn! the user closed the database by calling mDatabase.close()
            mDatabase = null;
        } else if (!mDatabase.isReadOnly()) {
            // mDatabase 已满足条件,直接返回
            // The database is already open for business
            return mDatabase;
        }
    }

    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 = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum)
             * 注意此处的 mode:0 == SQLiteDatabase.OPEN_READWRITE, 即可读写模式
             */
            db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
        }

        int version = db.getVersion();
        if (version != mNewVersion) {
            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 为空实现
        onOpen(db);
        success = true;
        return db;
    } finally {
        // 注意此处对 try 语句的用法,只有 finally 语句块,没有 catch 语句块
        mIsInitializing = false;
        if (success) {
            /**
             * 成功获取到可读写的数据库
             */
            if (mDatabase != null) {
                try { mDatabase.close(); } catch (Exception e) { }
                mDatabase.unlock();
            }
            // 注意此处,把获取到的可读写数据库赋值给 mDatabase
            mDatabase = db;
        } else {
            if (mDatabase != null){
                mDatabase.unlock();
            }
            if (db != null) db.close();
        }
    }
}


三、getReadableDatabase()

/**
 * 该方法的调用不要放在 UI 线程,存在阻塞和操作耗时的情况
 * 优先返回 getWritableDatabase()
 * getWritableDatabase() 发生 SQLiteException 异常后,才尝试获取只读模式的数据库
 *
 * Create and/or open a database.  This will be the same object returned by
 * {@link #getWritableDatabase} unless some problem, such as a full disk,
 * requires the database to be opened read-only.  In that case, a read-only
 * database object will be returned.  If the problem is fixed, a future call
 * to {@link #getWritableDatabase} may succeed, in which case the read-only
 * database object will be closed and the read/write object will be returned
 * in the future.
 *
 * <p class="caution">Like {@link #getWritableDatabase}, this method may
 * take a long time to return, so you should not call it from the
 * application main thread, including from
 * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
 *
 * @throws SQLiteException if the database cannot be opened
 * @return a database object valid until {@link #getWritableDatabase}
 *     or {@link #close} is called.
 */
public synchronized SQLiteDatabase getReadableDatabase() {
    if (mDatabase != null) {
        if (!mDatabase.isOpen()) {
            // darn! the user closed the database by calling mDatabase.close()
            // 若是 mDatabase 已被关闭,则置空, 重新获取
            mDatabase = null;
        } else {
            // 如果 mDatabase 未被 close(),则直接返回
            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 = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum)
         * 注意此处的 SQLiteDatabase.OPEN_READONLY, 即只读模式
         */
        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);
        }
        // 该方法为空实现
        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();
        }
    }
}

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,988评论 25 707
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,803评论 14 507
  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,199评论 1 18
  • 公元2015年11月28日(阴历11月17日)父亲,你走了!每一天里,我无时不再想念您!好多次梦中见到您,可...
    一抹阳光的灿烂时光阅读 244评论 4 0
  • 吃过晚饭,小儿子悄悄的把我拉到一边,说‘’爸爸,李老师真的好牛‘’。我很好奇什么事让儿子对这样一位刚开...
    flyboy168阅读 484评论 0 1