Anddroid SQLite源码分析

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

猜你喜欢

转载自blog.csdn.net/xiaol206/article/details/80154462