Android 數據庫 SQLite 第三方框架源碼的分析

[TOC]html

Android 中的數據庫 SQLite

SQLite 的簡介

Sqlite數據庫是一種輕量級數據庫,它具有跨平臺,多語言操做等優勢,它普遍用於包括瀏覽器、IOS,Android以及一些便攜需求的小型web應用系統。它具有佔用資源低,處理速度快等優勢。java

Android 中操做 SQLite 的方式

  1. SQLiteOpenHelper 和 SQLiteDatabase,Android 內部封裝的用於管理數據庫建立和版本管理的幫助類。
  2. Room,Google 官方推薦的使用的操做 SQLite 的方式,也是一個 ORM 數據庫框架,也是經過註解對象映射到 SQLite 數據庫中。
  3. GreenDao,一款輕量級的 ORM 數據庫框架,能夠經過註解對象映射到 SQLite 數據庫中。
  4. LitePal,經過 XML 文件配置數據庫屬性,經過 module 的繼承實現將對象映射到 SQLite 數據庫中,並進行操做數據庫。

SQLiteOpenHelper 和 SQLiteDatabase

SQLiteOpenHelper 是 Android 對於管理 SQLite 數據庫建立、打開、關閉、升級、降級以及一些操做回調的幫助類。android

SQLiteOpenHelper 建立和打開 SQLite 數據庫

明確: 分析建立和打開數據庫是分析打開數據庫的鏈接git

注意: 建立 SQLiteOpenHelper 對象並未進行建立或打開 SQLite 數據庫,須要調用 getWritableDatabase 或 getReadableDatabase 方法纔可建立或打開數據庫。github

//@Link #SQLiteOpenHelper
// 建立或打開一個可用於讀、寫操做的數據庫
public SQLiteDatabase getWritableDatabase() {
    synchronized (this) {//同步鎖
        return getDatabaseLocked(true);
    }
}
// 建立或打開一個可用於讀、寫操做的數據庫
public SQLiteDatabase getReadableDatabase() {
    synchronized (this) {
        return getDatabaseLocked(false);
    }
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
    if (mDatabase != null) {
        if (!mDatabase.isOpen()) {
            // 數據庫關閉須要從新打開
            mDatabase = null;
        } else if (!writable || !mDatabase.isReadOnly()) {
            // 不須要寫入數據庫,當前數據庫也是僅讀數據庫,則能夠返回
            return mDatabase;
        }
    }
	...
    SQLiteDatabase db = mDatabase;
    try {
        mIsInitializing = true;
        if (db != null) {
            // 若須要寫數據庫,當前僅可讀,須要從新打開數據庫
            if (writable && db.isReadOnly()) {
                db.reopenReadWrite();
            }
            // 數據庫名字爲 null,則再內存中建立一個數據庫,數據庫關閉,內容消失。
        } else if (mName == null) {
            db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
        } else {
            final File filePath = mContext.getDatabasePath(mName);
            SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
            try {
                // 在此進行打開數據庫操做
                db = SQLiteDatabase.openDatabase(filePath, params);
                setFilePermissionsForDb(filePath.getPath());
            } catch (SQLException ex) {
                if (writable) {
                    throw ex;
                }
                //.... 出現異常,則嘗試打開可寫數據庫
                params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
                db = SQLiteDatabase.openDatabase(filePath, params);
            }
        }
		// 數據庫配置完成回調
        onConfigure(db);

        final int version = db.getVersion();
        if (version != mNewVersion) {
           //...在此進行對數據庫的版本處理
           //可能會進行 onBeforeDelete 刪除以前、onCreate 建立、onDowngrade 降級、onUpgrade 升級回調
        }
		// 數據庫打開回調
        onOpen(db);
        ...
        mDatabase = db;
        return db;
    } finally {
        mIsInitializing = false;
        if (db != null && db != mDatabase) {
            db.close();
        }
    }
}
複製代碼

在上面的代碼中,數據庫名字 null 時經過 SQLiteDatabase.createInMemory(mOpenParamsBuilder.build()); 在內存中建立一個臨時數據庫,不然經過 SQLiteDatabase.openDatabase(filePath, params); 打開或建立一個指定路徑、名字的數據庫,下面主要分析打開數據庫操做。web

//@Link #SQLiteDatabase
public static SQLiteDatabase openDatabase(@NonNull File path, @NonNull OpenParams openParams) {
    return openDatabase(path.getPath(), openParams);
}
private static SQLiteDatabase openDatabase(@NonNull String path, @NonNull OpenParams openParams) {
    Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
    // SQLiteDatabase 的構造函數中進行數據庫的參數配置
    SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
                                           openParams.mCursorFactory, openParams.mErrorHandler,
                                           openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
                                           openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
    db.open(); //打開數據庫
    return db;
}

複製代碼

上面的方法都是一些配置信息,最終會調用 db.open() 進行真正的數據庫打開操做,進行建立數據庫鏈接池,以及建立初步的數據庫鏈接(SQLiteConnection)。sql

//@Link #SQLiteDatabase
private void open() {
    try {
        try {
            openInner();
        } catch (SQLiteDatabaseCorruptException ex) {
            onCorruption();
            openInner();
        }
    } catch (SQLiteException ex) {
        Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
        close();
        throw ex;
    }
}
private void openInner() {
    synchronized (mLock) {
        assert mConnectionPoolLocked == null;
        // 初始化數據庫鏈接池
        mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
        mCloseGuardLocked.open("close");
    }
	//...
}

//@Link #SQLiteConnectionPool
 public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
     //...
     SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
     pool.open();//打開數據庫鏈接池
     return pool;
 }
private void open() {
    // 建立主要數據庫鏈接,經過 SQLiteConnection 能夠經過 SQL 語句對數據庫進行操做(相似 JDBC)
    mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
                                                       true /*primaryConnection*/); 
    synchronized (mLock) {
        if (mIdleConnectionHandler != null) {
            mIdleConnectionHandler.connectionReleased(mAvailablePrimaryConnection);
        }
    }
	//...
}
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration, boolean primaryConnection) {
    final int connectionId = mNextConnectionId++;
    // 在此打開數據庫鏈接,並返回 SQLiteConnection
    return SQLiteConnection.open(this, configuration,
                                     connectionId, primaryConnection); // might throw
}
複製代碼

經過上面代碼分析,能夠知道 SQLiteDatabase 數據庫維護一個 SQLiteConnectionPool 數據庫鏈接池,在任什麼時候候,數據庫鏈接 SQLiteConnection 都屬於數據庫鏈接池。數據庫鏈接池是線程安全的,內部大多數方法都經過 synchronized (mLock) 來實現加鎖,可是數據庫鏈接不是線程安全的。數據庫

總結一下:SQLiteDatabase 是對數據庫鏈接池和數據庫鏈接的維護以及對操做數據庫的方法的封裝,數組

SQLiteOpenHelper 是一個用於管理數據庫建立、關閉、升級、降級的管理類。最終的數據庫操做都是經過 SQLiteConnection 調用數據庫 native 層方法來進行。打開數據庫至關於建立數據庫鏈接池和建立數據庫鏈接瀏覽器

SQLiteDatabase 操做數據庫

明確: 操做數據庫是分析如何經過 SQLiteDatabase 獲取 SQLiteConnection,並經過數據庫鏈接調用 native 的過程.

SQLiteDatabase 內封裝了對數據庫操做的方法,能夠經過封裝的方法,無需進行書寫 SQL 語句進行操做數據庫,不只如此,SQLiteDatabase 還支持執行原生的 SQL 語句。

SQLiteDatabase 封裝操做數據庫的方法是經過傳入的參數進行拼接成 SQL 語句,再進行操做數據庫。選取 Insert 插入操做進行分析,其餘操做數據庫的方法思想大同小異。

//@Link #SQLiteDatabase
//插入操做 table:表名,nullColumnHack :處理插入空行, values :字段名和值
public long insert(String table, String nullColumnHack, ContentValues values) {
    try {
        return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
    } catch (SQLException e) {
        Log.e(TAG, "Error inserting " + values, e);
        return -1;
    }
}
public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) {
    acquireReference();
    try {
        StringBuilder sql = new StringBuilder();
        // 拼接 SQL 語句的類型
        sql.append("INSERT");
        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
        sql.append(" INTO ");
        sql.append(table);
        sql.append('(');
        Object[] bindArgs = null;
        int size = (initialValues != null && !initialValues.isEmpty())
            ? initialValues.size() : 0;
        if (size > 0) {
            bindArgs = new Object[size];
            int i = 0;
            // 拼接字段名字
            for (String colName : initialValues.keySet()) {
                sql.append((i > 0) ? "," : "");
                sql.append(colName);
                bindArgs[i++] = initialValues.get(colName);
            }
            sql.append(')');
            sql.append(" VALUES (");
            // 拼接字段對應的值
            for (i = 0; i < size; i++) {
                sql.append((i > 0) ? ",?" : "?");
            }
        } else {
            sql.append(nullColumnHack + ") VALUES (NULL");
        }
        sql.append(')');
		// 經過 SQL 語句建立 SQLiteStatement 對象
        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
        try {
            // 執行插入操做
            return statement.executeInsert();
        } finally {
            statement.close();
        }
    } finally {
        releaseReference();
    }
}
複製代碼

其實在上面已經構建好一個 SQL 語句,以及經過 SQL 語句構建了一個 SQLiteStatement 對象,(SQLiteStatement 是對 SQLiteSession 的一層封,SQLiteSession 用於處理執行 SQL 語句的細節,如:事務、異常等。SQLiteStatement 則是用於調用 SQLiteSession 的方法,及調用方法過程當中出現異常的回調,如:onCorruption())裝,那麼接下來,須要去挖到底是如何到 SQLConnection 執行這條 SQL 語句。

//@Link #SQLiteStatement
public long executeInsert() {
    acquireReference();
    try {
        // getSession() 返回的是 SQLiteSession 對象,能夠理解爲相 SQLiteDatabase 控制 SQLiteConnection 的類。
        return getSession().executeForLastInsertedRowId(
            getSql(), getBindArgs(), getConnectionFlags(), null);
    } catch (SQLiteDatabaseCorruptException ex) {
        onCorruption();
        throw ex;
    } finally {
        releaseReference();
    }
}

//@Link #SQLiteSession
public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, CancellationSignal cancellationSignal) {
    //...在這裏向 SQLiteConectionPool 數據庫鏈接池請求獲取 SQLiteConnection 數據庫鏈接
    acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
    try {
        // 在這裏就到了經過 SQLiteConnection 數據庫鏈接進行執行上面構建好了的 SQL 語句
        return mConnection.executeForLastInsertedRowId(sql, bindArgs,
                                                       cancellationSignal); // might throw
    } finally {
        releaseConnection(); // might throw
    }
}
複製代碼

在這不進行對在數據庫鏈接池獲取 SQLiteConnection 數據庫鏈接的過程進行分析。

總結一下: 在獲取到 SQLiteDatabase 後進行數據庫操做時,填入的參數首先會進行構建成 SQL 語句,經過 SQL 語句構建成 SQLiteStatement,再獲取到 SQLiteSession,經過 SQLiteSession 獲取到數據庫鏈接池中的 SQLiteConnection 執行最初構建的 SQL 語句。

SQLiteDatabase 還支持直接執行原生的 SQL 語句(除了 Query 查詢操做的 SQL 語句),到 SQLiteStatement 以後的分析與上面的 Insert 插入操做的分析類同,不予再分析。

//@Link #SQLiteDatabase
public void execSQL(String sql) throws SQLException {
    executeSql(sql, null);
}
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
    acquireReference();
    try {
        // DatabaseUtils.getSqlStatementType(sql) 是獲取當前 SQL 語句的操做類型
        final int statementType = DatabaseUtils.getSqlStatementType(sql);
        // DatabaseUtils.STATEMENT_ATTACH 是能夠用來 建立或者附加數據庫數據庫
        if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
          	//... 一些配置操做
        }
		// 建立 SQLiteStatement 對象
        try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
            // 經過 executeUpdateDelete 執行 SQL 語句
            // 從這能夠看出 SQLiteDatabase 不支持經過原生 SQL 進行查詢操做。
            return statement.executeUpdateDelete();
        } finally {
            if (statementType == DatabaseUtils.STATEMENT_DDL) {
                mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
            }
        }
    } finally {
        releaseReference();
    }
}
複製代碼

Room

Room是一個數據庫對象映射庫,能夠輕鬆訪問Android應用程序上的數據庫。Room 提供方便的 API 來查詢數據庫,並在編譯的時候驗證這些查詢。

Room 有 3 個主要的組件

  1. @Database:該註解是做用於一個繼承了 RoomDatabase 數據庫的抽象類(abstract)類,在運行期間,能夠經過 Room.databaseBuilder 或 Room.inMemoryDatabaseBuilder 獲取 RoonmDatabase 實例。該數據庫類定義了數據庫的實體(Entity)表和數據庫訪問對象(Dao),也是底層鏈接的主要訪問點。
  2. @Entity:該註解做用於一個實體類,實體類對應與數據庫中一個表,實體類的各個成員屬性對應於數據庫表中的字段。數據庫對於每個用 @Entity 註解了的類都建立一個對應的表進行存儲。
  3. @Dao:該註解做用於一個類或者一個接口,做爲訪問數據庫的對象。訪問數據庫的對象是 Room 的主要組件,負責定義訪問數據庫的方法。在被 @Database 做用的類中,必須有一個無參返回 @Dao 做用類對象的抽象方法。在編譯期間會經過註解處理器自動生成被 @Dao 做用的類的具體實現。

在編譯期間,Room 的註解處理器會自動幫咱們生成訪問數據庫的類(Dao 類)和生成建立數據庫的類(Database 類)。經過 Room 註解處理器生成的類,也至關於普通的寫代碼,只不過該代碼是經過 JDK 自動生成,JDK 搜索全部註解處理器,註解處理器識別註解,反射獲取註解做用的類或方法獲取到信息參數,再根據參數生成相應的類對應的邏輯。

註解處理器有點像模板功能,咱們輸入點內容,而後註解處理器經過咱們輸入的內容生成模板代碼,避免在開發過程當中反覆寫這些模板代碼,讓開發者更加關注業務的邏輯實現。

Room 初始化 @Database

明確: 框架都是對底層東西進行的封裝,對數據庫的封裝天然是對 SQLiteDatabase 和 SQLiteOpenHelper 的封裝,因此先挖一下是在建立 SQLiteDatabase 實例和是哪一個類操做 SQLiteDatabase。

下面是開發過程當中本身實現的 AppDatabase 類,經過該類能夠獲取到 RoomDatabase 數據庫的實例。

//@Link #AppDatabase
@Database(entities = {ArticleEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    private static final String DB_NAME = "WanAndroid.db";
    private static volatile AppDatabase sAppDatabase;
    // 一般一個數據庫的訪問入口在 App 運行期間只有一個實例,因此使用單例模式。
    public static synchronized AppDatabase getInstance(Context context) {
        if (sAppDatabase == null) {
            synchronized (AppDatabase.class) {
                if (sAppDatabase == null) {
                    sAppDatabase = create(context);
                }
            }
        }
        return sAppDatabase;
    }
    private static AppDatabase create(final Context context) {
        // 建立 RoomDatabase
        return Room.databaseBuilder(
                context,
                AppDatabase.class,
                DB_NAME).build();
    }
	// 對應上面說的,在被 @Database 做用的類中,必須有一個無參返回 @Dao 做用類對象的抽象方法
    public abstract ArticleDao getArticleDao();
}
複製代碼

建立 RoomDatabase 實例是經過 Builder 模式建立的,接着來看一下 build() 方法的代碼。

//@Link #RoomDatabase.Builder
public T build() {
    //...進行一些判斷,以及查詢線程池和事務線程池(Executor)的建立或初始化
    
    //...

    if (mFactory == null) {
        // 構建 FrameworkSQLiteOpenHelperFactory 實例
        mFactory = new FrameworkSQLiteOpenHelperFactory();
    }
        DatabaseConfiguration configuration =
            new DatabaseConfiguration(
            mContext,
            mName,
            mFactory,
            mMigrationContainer,
            mCallbacks,
            mAllowMainThreadQueries,
            mJournalMode.resolve(mContext),
            mQueryExecutor,
            mTransactionExecutor,
            mMultiInstanceInvalidation,
            mRequireMigration,
            mAllowDestructiveMigrationOnDowngrade,
            mMigrationsNotRequiredFrom);
        T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);// 經過反射獲取到 mDatabaseClass 子類的實例(例子中子類爲:AppDatabase_Impl, mDatabaseClass 是 AppDatabase 類)
        db.init(configuration); // 配置 mDatabaseClass 子類實例
        return db;
	}
}

//@Link #FrameworkSQLiteOpenHelperFactory
// FrameworkSQLiteOpenHelperFactory 是用於構建 FrameworkSQLiteOpenHelper 的工廠類
public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
    // 全部相關的類對應的對象都要等工廠類調用 create() 方法才被構建。
    @Override
    public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
        return new FrameworkSQLiteOpenHelper(
                configuration.context, configuration.name, configuration.callback);
    }
}

//@Link #FrameworkSQLiteOpenHelper
// FrameworkSQLiteOpenHelper 代理了它的內部類 OpenHelper,OpenHelper(繼承了 SQLiteOpenHelper) 維護 FrameworkSQLiteDatabase 數據庫的建立以及一些回調
FrameworkSQLiteOpenHelper(Context context, String name, Callback callback) {
    mDelegate = createDelegate(context, name, callback);// 建立代理 OpenHelper 實例
}
private OpenHelper createDelegate(Context context, String name, Callback callback) {
    // FrameworkSQLiteDatabase 是 SQLiteDatabase 的代理類
    // 建立 FrameworkSQLiteDatabase 數組,只是存在引用,未構建實例
    final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
    return new OpenHelper(context, name, dbRef, callback);
}

//@Link #FrameworkSQLiteOpenHelper.OpenHelper
// OpenHelper 被 FrameworkSQLiteOpenHelper 類代理,是 FrameworkSQLiteOpenHelper 的內部類
OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
           final Callback callback) {
    super(context, name, null, callback.version,
          new DatabaseErrorHandler() {
              // 這應該是數據庫出錯/損壞之類的回調
              @Override
              public void onCorruption(SQLiteDatabase dbObj) {
                  // 雖然 getWrappedDb(dbRef, dbObj) 是實例化 FrameworkSQLiteDatabase 的實例
                  // 可是在此回調未實例化代理 SQLiteDatabase 的 FrameworkSQLiteDatabase 的實例
                  callback.onCorruption(getWrappedDb(dbRef, dbObj));
              }
          });
    mCallback = callback;
    mDbRef = dbRef;
}

//@Link #FrameworkSQLiteOpenHelper.OpenHelper
// 此方法是真正實例化代理了 SQLiteDatabase 的 FrameworkSQLiteDatabase 對象
static FrameworkSQLiteDatabase getWrappedDb(FrameworkSQLiteDatabase[] refHolder, SQLiteDatabase sqLiteDatabase) {
    FrameworkSQLiteDatabase dbRef = refHolder[0];
    if (dbRef == null || !dbRef.isDelegate(sqLiteDatabase)) {
        
        refHolder[0] = new FrameworkSQLiteDatabase(sqLiteDatabase);
    }
    return refHolder[0];
}
複製代碼

經過上面的分析,能夠獲得 Room 都是經過代理模式來對原生的 SQLiteOpenHelper 和 SQLiteDatabase 進行封裝。上面提到,全部相關的類對應的對象都要等 FrameworkSQLiteOpenHelperFactory 調用 create() 方法才被構建,可是上面並未進行調用 create() 方法,而是將全部的 Room 配置信息都存到了 DatabaseConfiguration 中,那麼接着來看 Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); 的方法調用。

//@Link #Room
@SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
@NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
    //... 獲取 klass 類的信息
    // 這是獲取 klass 對應的子類包名+類名,(如例子中的 klass 類名 = AppDatabase,implName = AppDatabase_Impl)
    final String implName = postPackageName.replace('.', '_') + suffix;
    try {

        @SuppressWarnings("unchecked")
        final Class<T> aClass = (Class<T>) Class.forName(
            fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
        // 在此構建 AppDatabase 的子類 AppDatabase_Impl 的實例
        return aClass.newInstance();
    }
    //... 異常處理
}

複製代碼

上面提到 AppDatabase_Impl,這是 @Database 註解對應的註解處理器生成的類(生成路基:{project_root}\app\build\generated\source\apt\debug\{包名}\db\AppDatabase_Impl.java)。在獲取到 AppDatabase_Impl 實例後,進行調用 db.init(configuration); 進行配置數據庫。接下來要分析配置數據庫的過程,不出意外,以前說的 ’FrameworkSQLiteOpenHelperFactory 調用 create() 方法‘ 將會在配置數據庫的時候調用。

//@Link #RoomDatabase
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
    // 在此建立 SupportSQLiteOpenHelper(FrameworkSQLiteOpenHelper 是具體實現,代理了 OpenHelper)
    // createOpenHelper 是抽象方法,具體實如今其子類(例子中爲 AppDatabase_Impl)
    mOpenHelper = createOpenHelper(configuration);
    // 下面都是一些 RoomDatabase 的成員變量的賦值操做
    boolean wal = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
        mOpenHelper.setWriteAheadLoggingEnabled(wal);
    }
    mCallbacks = configuration.callbacks;
    mQueryExecutor = configuration.queryExecutor;
    mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
    mAllowMainThreadQueries = configuration.allowMainThreadQueries;
    mWriteAheadLoggingEnabled = wal;
    if (configuration.multiInstanceInvalidation) {
        mInvalidationTracker.startMultiInstanceInvalidation(configuration.context,
                                                            configuration.name);
    }
}

//@Link #AppDatabase_Impl
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
        @Override
        public void createAllTables(SupportSQLiteDatabase _db) {
            //... 建立全部的表
        }
		//...數據庫操做的回調處理
    }, "3b4f5822228d6c99c8dc8fa0e6468f7f", "4953443b8f2c213519c76f3b51546987");
    final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
        .name(configuration.name)
        .callback(_openCallback)
        .build();
    // create() 方法在此調用,進行實例化代理 SQLiteOpenHelper 實例
    // Room 到此就算初始化完成
    final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
    return _helper;
}

複製代碼

總結一下: 經過上面的代碼能夠發現,Room 的實現,是經過代理來進行對原生 SQLiteDatabase 和原生 SQLiteOpenHelper 進行封裝,而且經過註解處理器(AnnotationProcessor)進行生成 Database 和 Dao 的代碼。

到如今,Room 已經初始化完成,可是還沒建立原生 SQLiteDatabase 的實例和代理 SQLiteDatabase 的 FrameworkSQLiteDatabase 實例還沒建立,僅存在它的引用,上面提到真正實例化 FrameworkSQLiteDatabase 的方法是 getWrappedDb(FrameworkSQLiteDatabase[] refHolder, SQLiteDatabase sqLiteDatabase)。那麼能夠初步猜想是在進行數據庫操做的時候,會進行調用 getWrappedDb 的方法。

Room 操做數據庫 @Dao

選取經過 Room 插入數據來分析 Room 操做數據庫。

被 @Dao 註解做用的接口主要是定義了一些進行操做的方法,@Dao 做用接口配合 @Insert 做用方法的使用,能夠進行插入數據進入數據庫,經過 RoomDatabase 能夠獲取 Dao 實例進行操做數據庫。

//@Link #ArticleDao
@Dao
public interface ArticleDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(ArticleEntity articleEntities);
}
// 使用
AppDatabase.getInstance(App.getContext()).getArticleDao().insert(entities);

//@Link #AppDatabase_Impl
//getArticleDao() 是獲取到 ArticleDao_Impl 的實例(單例模式)
public ArticleDao getArticleDao() {
    if (_articleDao != null) {
      return _articleDao;
    } else {
      synchronized(this) {
        if(_articleDao == null) {
          _articleDao = new ArticleDao_Impl(this);
        }
        return _articleDao;
      }
    }
  }

複製代碼

上面提到 ArticleDao_Impl 類,這個類是咱們寫的 ArticleDao 接口的具體實現,固然這個類也是 @Dao 註解的註解處理器進行生成的代碼,在獲取到 ArticleDao 實例後,能夠進行執行 ArticleDao 裏面定義的操做數據庫的方法。下面來看看經過 ArticleDao 來進行插入操做。

//@Link #ArticleDao_Impl
@Override
public void insert(final ArticleEntity... articleEntities) {
    __db.assertNotSuspendingTransaction();// 確認沒有暫停的事務
    // 開始事務,在這裏面會進行 SQLiteDatabase (經過代理類 FrameworkSQLiteOpenHelper.OpenHelper 打開)的打開
    __db.beginTransaction();
    try {
        __insertionAdapterOfArticleEntity.insert(articleEntities);// 插入操做
        __db.setTransactionSuccessful();// 設置事務成功
    } finally {
        __db.endTransaction();// 結束事務
    }
}

//@Link #RoomDatabase
@Deprecated
public void beginTransaction() {
    assertNotMainThread();// 確認不是在主線程
    SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
    mInvalidationTracker.syncTriggers(database);
    database.beginTransaction();//開始事務
}

//@Link #FrameworkSQLiteOpenHelper
// mDelegate 的引用類型是 FrameworkSQLiteOpenHelper.OpenHelper
@Override
public SupportSQLiteDatabase getWritableDatabase() {
    return mDelegate.getWritableSupportDatabase();
}

//@Link #FrameworkSQLiteOpenHelper.OpenHelper
synchronized SupportSQLiteDatabase getWritableSupportDatabase() {
    mMigrated = false;
    // 在此也進行打開 SQLiteDatabase 數據庫的操做(就是原生數據庫打開的方式)
    SQLiteDatabase db = super.getWritableDatabase();
    if (mMigrated) {
        close();
        return getWritableSupportDatabase();
    }
    return getWrappedDb(db);
}
FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
     // 在這就對應了上面所說的調用 getWrappedDb(mDbRef, sqLiteDatabase) 進行實例化 FrameworkSQLiteDatabase 實例代理 SQLiteDatabase
    return getWrappedDb(mDbRef, sqLiteDatabase);
}

複製代碼

注意:其中的以 Support 或 Framework 開頭的都是 androidx.sqlite 包下的類,也就是說 Room 依賴 androidx 實現

總結一下:在 Room 初始化的時候僅實例化了代理 SQLiteOpenHelper 的 FrameworkSQLiteOpenHelper.OpenHelper,當經過 Room 操做數據庫的時候,纔會打開 SQLiteDatabse 數據庫並將代理 SQLiteDatabase 的 FrameworkSQLiteDatabase 實例化。

接着看經過 Room 進行插入操做 __insertionAdapterOfArticleEntity.insert(articleEntities);。EntityInsertionAdapter 是用於讓 Room 知道如何去插入一個實體的類,好比插入一條數據庫記錄能夠經過該實例進行將實體的各個屬性進行綁定到 SQL 語句中。

//@Link #EntityInsertionAdapter 
public final void insert(T entity) {
    final SupportSQLiteStatement stmt = acquire();// 底層經過 RoomDatabase 獲取數據庫聲明(SQLiteStatement)
    try {
        // 實體和 SQLiteStatement 進行綁定(通俗說就是將實體的內容裝到 SQL 語句的佔位符中)
        bind(stmt, entity); 
        // 在這就進行執行插入操做了
        stmt.executeInsert();
    } finally {
        release(stmt);
    }
}

複製代碼

到這就到了上面分析的原生數據庫的操做方式了,就不繼續向下分析,詳情往上看。

Room 操做的實體 @Entity

經過 @Entity 註解的類對應的是數據庫中的一張表,類中的成員變量是表中的各個字段名,該類也是 Room 操做數據庫的對象。(注意:必須有 set 和 get 方法)

Room 主要是經過代理來實現封裝底層 SQLiteOpenHelper 和 SQLiteDatabase ,以及經過註解處理器的方式來進行生成模板代碼,簡化開發的代碼量,讓開發者更快速地操做數據庫。

GreenDao

GreenDao 和 Room 差很少,也是將 JAVA 對象映射到 SQLite 數據庫中。GreenDao 是一款開源的 Android ORM 數據庫框架,具備API 簡單,易於使用,支持加密,框架小的特色。

GreenDao 是經過 FreeMarker 來生成模板代碼。Room 是經過註解處理器生成模板代碼。 FreeMarker 暫未研究。

GreenDao 使用

GreenDao 的使用很簡單,只須要經過 @Entity 的註解一個實體類,類對應一張表,類的成員變量對應表中的字段。

//@Link #Note
@Entity(indexes = {
        @Index(value = "text, date DESC", unique = true)
})
public class Note {
    @Id
    private long id;
    private String text;
    private Date date;
    @Generated(hash = 1395965113)
    public Note(long id, String text, Date date) {
        this.id = id;
        this.text = text;
        this.date = date;
    }
    public Note(long id) {
        this.id = id;
    }
    @Generated(hash = 1272611929)
    public Note() {
    }
	//... set get 方法
}


複製代碼

經過 @Entity 註解,而後再 Make Project,FreeMarker 就自動幫咱們生成相應的操做數據庫的代碼,生成代碼的路徑在{project_root}\app\build\generated\source\greendao\{包名},生成路徑裏面有 3 個生成類,分別是 DaoMaster(初始化 GreenDao 數據庫的類),DaoSession (提供訪問 Dao 的方法,會話緩存的類),NoteDao(對數據庫進行增刪改查的類)。

明確:下面分析這 3 個生成的類是如何封裝底層數據庫的鏈接,主要看如何進行初始化底層數據庫,以及在操做數據庫的過程當中的方法調用棧。

GreenDao 初始化

一般咱們在 Application 的 onCreate 裏面進行初始化,可是考慮到懶加載提升性能,能夠在須要使用 GreenDao 數據庫的時候再進行初始化。初始化 GreenDao 的步驟有 4 步,實例化 DevOpenHelper、打開 SQLiteDatabase、實例化 DaoMaster、實例化 DaoSession。

//@Link #App
private void initGreenDao() {
    DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "note_db");
    SQLiteDatabase db = helper.getWritableDatabase();
    DaoMaster daoMaster = new DaoMaster(db);
    sDaoSession = daoMaster.newSession();
}

複製代碼

上面的代碼包含了 GreenDao 初始化的 4 步,它的方法調用的時序圖以下,一步步進行分析

GreenDao_init

第一步是實例化 DevOpenHelper, DevOpenHelper 繼承了 OpenHelper,OpenHelper 繼承 了 DatabaseOpenHelper, DatabaseOpenHelper 繼承了原生的 SQLiteDatabase,也就是說 GreenDao 初始化第一步就進行了實例化底層的 SQLiteOpenHelper。

//@Link #DaoMaster.OpenHelper
public static class DevOpenHelper extends OpenHelper {
    public DevOpenHelper(Context context, String name) {
        super(context, name);
    }
	//...
    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        dropAllTables(db, true);// 刪除全部的表
        onCreate(db);// 從新建立全部的表
    }
}

//@Link #DaoMaster.OpenHelper
// DatabaseOpenHelper 繼承了 SQLiteOpenHelper
public static abstract class OpenHelper extends DatabaseOpenHelper {
    public OpenHelper(Context context, String name) {
        super(context, name, SCHEMA_VERSION);
    }
    //...
     @Override
    public void onCreate(Database db) {
        createAllTables(db, false);
    }
}

複製代碼

3 個 Helper 的各自做用

  1. 在 DatabaseOpenHelper 裏面,對全部返回數據庫的方法都包裝了一下,返回的是 GreenDao 的數據庫類 StandardDatabase;
  2. 在 OpenHelper 主要是實如今 onCreate 回調中進行建立全部的表操做;
  3. 在 DevOpenHelper 中主要處理在數據庫升級 onUpgrade 回調中進行刪除全部的表和從新建立全部的表操做;

第二步是進行打開數據庫的操做,SQLiteDatabase db = helper.getWritableDatabase();,因爲 GreenDao 3 個 Helper 類都沒有進行重寫 getWritableDatabase 方法,全部在此是直接調用原生的 SQLiteOpenHelper 方法,打開數據庫,並獲取數據庫實例。

第三步是實例化 DaoMaster,能夠經過 DaoMaster 獲取到數據庫會話 DaoSession,全部的 Dao 類都要註冊到 DaoMaster 裏面,DaoMaster 經過 Map 進行存儲。

//@Link #DaoMaster
public DaoMaster(SQLiteDatabase db) {
    this(new StandardDatabase(db));
}
public DaoMaster(Database db) {
    super(db, SCHEMA_VERSION);
    registerDaoClass(NoteDao.class);
}

//@Link #AbstractDaoMaster
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
    DaoConfig daoConfig = new DaoConfig(db, daoClass);
    daoConfigMap.put(daoClass, daoConfig);
}

複製代碼

第四步是實例化 DaoSession,在實例化 DaoSession 的過程當中也會進行實例化全部的 Dao 類,並將全部的 Dao 實例進行緩存,用於後面數據庫操做時候取用。

//@Link #DaoMaster
public DaoSession newSession() {
    return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}

//@Link #DaoSession
public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap) {
    super(db);
    noteDaoConfig = daoConfigMap.get(NoteDao.class).clone();
    noteDaoConfig.initIdentityScope(type);
    noteDao = new NoteDao(noteDaoConfig, this); // 實例化 NoteDao,可用於操做數據庫
    registerDao(Note.class, noteDao);
}

//@Link #AbstractDaoSession
protected <T> void registerDao(Class<T> entityClass, AbstractDao<T, ?> dao) {
    entityToDao.put(entityClass, dao); //緩存 Dao 實例
}

複製代碼

總結一下: 到此,GreenDao 數據庫就初始化完成,在初始化的過程當中就已經將 SQLiteOpenHelper 實例化和 SQLiteDatabse 進行打開,並且將數據庫中的全部的表建立以及處理了數據庫升級回調,還實例化全部操做數據庫的 Dao 類並將其存到 Map 集合中。

思考:是否能夠在須要數據庫的時候再進行初始化?是否能夠將初始化的 4 步拆開進行優化?

GreenDao 操做數據庫

上面提到,DaoSession 裏面經過 Map 緩存了全部操做數據庫 Dao 的實例,因此在使用的過程當中能夠經過 DaoSession 來獲取各張表所對應的 Dao ,進行操做數據庫中的表。

daoSession.getNoteDao().insert(new Note(0));

//@Link #AbstractDao
public long insert(T entity) {
    return executeInsert(entity, statements.getInsertStatement(), true);
}
private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
    long rowId;
    if (db.isDbLockedByCurrentThread()) {
        rowId = insertInsideTx(entity, stmt);
    } else {
        db.beginTransaction();
        try {
            rowId = insertInsideTx(entity, stmt);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
    if (setKeyAndAttach) {
        updateKeyAfterInsertAndAttach(entity, rowId, true);
    }
    return rowId;
}

複製代碼

GreenDao 操做數據庫的方法調用棧比較淺,經過上面代碼能夠看出,都是經過 GreenDao 的數據庫 StandardDatabase(代理原生 SQLiteDatabase 數據庫)完成。在這不過多分析。

LitePal

LitePal是一個開源的Android庫,容許開發人員很是容易地使用SQLite數據庫。 您無需編寫SQL語句便可完成大部分數據庫操做,包括建立或升級表,crud操做,聚合函數等.LitePal的設置也很是簡單,您能夠在不到5個時間內將其集成到項目中。

LitePal 初始化

在進行使用 LitePal 的時候,首先要在 Application 裏面進行初始化操做。LitePal.initialize(this);,LitePal 的初始化只是將當前的 Application 的 Context 傳入 LitePal 中.

//@Link #LitePal
public static void initialize(Context context) {
    Operator.initialize(context);
}
//@Link #Operator
public static void initialize(Context context) {
    LitePalApplication.sContext = context;
}

複製代碼

在進行經過 LitePal 操做數據庫以前,須要調用 LitePal.getDatabase();,進行打開數據庫、建立數據庫、建立數據庫中的表,進行上面的操做天然離不開原生數據庫的 SQLiteOpenHelper 類。

LitePal.getDatabase();

//@Link #LitePal
public static SQLiteDatabase getDatabase() {
    return Operator.getDatabase();
}

//@Link #Operator
public static SQLiteDatabase getDatabase() {
    synchronized (LitePalSupport.class) {
        return Connector.getDatabase();
    }
}

//@Link #Connector
public static SQLiteDatabase getDatabase() {
    return getWritableDatabase();
}
public synchronized static SQLiteDatabase getWritableDatabase() {
    LitePalOpenHelper litePalHelper = buildConnection();//LitePalOpenHelper 直接繼承 SQLiteOpenHelper
    return litePalHelper.getWritableDatabase(); // 進行打開數據庫操做
}

複製代碼

因爲 LitePal 是經過 {project_root}\app\src\main\assets\litepal.xml 這個 XML 文件來進行配置數據庫的屬性,因此就能夠猜出 buildConnection 是進行解析該 XML 文件,並根據 XML 配置的屬性進行初始化數據庫的操做。接着看 buildConnection() 方法的實現。

private static LitePalOpenHelper buildConnection() {
    LitePalAttr litePalAttr = LitePalAttr.getInstance();// 獲取 XML 數據庫配置文件
    litePalAttr.checkSelfValid();
    if (mLitePalHelper == null) {
        String dbName = litePalAttr.getDbName(); // 解析獲取到數據庫名字
       	//... 進行適配數據庫建立的位置(內存,外置存儲的位置)
        // 初始化 LiteOpenHelper
        mLitePalHelper = new LitePalOpenHelper(dbName, litePalAttr.getVersion());
    }
    return mLitePalHelper;
}

複製代碼

在經過調用 LitePal 的 getDatabase() 後,數據庫被建立打開,其實這裏只是一點點封裝,封裝的內容是經過 XML 文件配置數據庫的屬性。

LitePal 的實體

要使用 LitePal 進行操做數據庫中的表,實體必須繼承 LitePalSupport 類。JAVA 的一個類映射到數據庫中的一個表,因此 LitePalSupport 類包含操做數據庫表的方法(增刪查改等)。

public class Music extends LitePalSupport {
    @Column(unique = true, defaultValue = "unknown")
    private String name;
    private float price;
	// set get 方法
}

複製代碼

LitePal 操做數據庫

經過 LitePal 操做數據庫,即經過繼承了 LitePalSupport 類的實體進行操做數據庫,以插入爲例進行分析。

new Music("name" + i, i).save();

//@Link #LitePalSupport
public boolean save() {
    try {
        saveThrows();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}
public void saveThrows() {
    synchronized (LitePalSupport.class) {
        SQLiteDatabase db = Connector.getDatabase(); // 獲取數據庫
        db.beginTransaction(); // 開啓事務
        try {
            SaveHandler saveHandler = new SaveHandler(db);
            saveHandler.onSave(this); // 在此進行插入操做
            clearAssociatedData();
            db.setTransactionSuccessful(); // 事務成功
        } catch (Exception e) {
            throw new LitePalSupportException(e.getMessage(), e);
        } finally {
            db.endTransaction();// 結束事務
        }
    }
}

複製代碼

SaveHandler 是什麼?LitePal 是將對象映射到數據庫中,可是底層確定仍是 SQL 語句,因此 SaveHandler 的做用是將對象的成員變量進行賦值到 SQL 語句中(類似的還有 UpdateHandelr、QueryHandler等等)。可是對象的類是 LitePalSupport ,因此還須要經過反射獲取子類的屬性。

//@Link #SaveHandler
void onSave(LitePalSupport baseObj) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    // 獲取當前對象的類名
    String className = baseObj.getClassName();
    // 獲取子類成員變量的名字(數據庫字段名)
    List<Field> supportedFields = getSupportedFields(className); 
    // 獲取通用成員變量的名字(數據庫通用字段名)
    List<Field> supportedGenericFields = getSupportedGenericFields(className);
    Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
    if (!baseObj.isSaved()) {
        analyzeAssociatedModels(baseObj, associationInfos);
        doSaveAction(baseObj, supportedFields, supportedGenericFields); // 進行插入操做
        analyzeAssociatedModels(baseObj, associationInfos);
    } else {
        analyzeAssociatedModels(baseObj, associationInfos);
        doUpdateAction(baseObj, supportedFields, supportedGenericFields);
    }
}
private void doSaveAction(LitePalSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    values.clear();
    beforeSave(baseObj, supportedFields, values);
    long id = saving(baseObj, values); // 進行插入操做,values 是 ContentValue 的實例
    afterSave(baseObj, supportedFields, supportedGenericFields, id);
}
private long saving(LitePalSupport baseObj, ContentValues values) {
    if (values.size() == 0) {
        values.putNull("id");
    }
    return mDatabase.insert(baseObj.getTableName(), null, values);// 最終進行插入的操做
}

複製代碼

經過上面的方法棧調用,在進行插入操做的時候,是經過 java 反射獲取到 module 的各個成員變量的字段名以及相應的值,再進行填充到 ContentValue ,最後經過 SQLiteDatabase 進行插入操做。

總結

主要總結 3 個數據庫框架的不一樣。

不管是什麼框架,由頂向下都是從封裝到底層的實現。

打開數據庫的方式(在打開數據庫的時候會在 SQLiteOpenHelper 的 onCreate 回調中進行建立數據庫的表操做)

  1. SQLiteOpenHelper 是經過打開數據庫鏈接池和打開底層數據庫鏈接進行打開數據庫的操做。
  2. Room 是經過繼承了 SQLiteOpenHelper 。在進行操做數據庫的時候,先經過 SQLiteOpenHelper 的子類進行打開數據庫,再操做數據庫。(操做數據庫的時候自動打開數據庫)
  3. GreenDao 是經過繼承 SQLiteOpenHelper。在操做數據庫以前,須要經過 helper.getWritableDatabase();(helper 是SQLiteOpenHelper 的子類)進行打開數據庫。(操做數據庫以前須要主動打開)
  4. LitePal 是經過繼承 SQLiteOpenHelper。在操做數據庫以前,須要經過 LitePal.getDatabase(); 進行打開數據庫。(操做數據庫以前須要主動打開)

生成代碼/進行封裝的方式和內容

  1. SQLiteOpenHelper 和 SQLiteDatabase 是直接封裝底層的數據庫鏈接,經過將 JAVA 對象的成員屬性拼接到 SQL 語句中進行封裝操做數據庫的方式。(無代碼生成)
  2. Room 是經過註解處理器生成建立/打開數據庫代碼(Database_Impl)和生成操做數據庫代碼(Dao_Impl)。
  3. GreenDao 是經過 FreeMarker 生成模板代碼,生成操做數據庫表的 Dao 類、管理數據庫全部表的 DaoSession 類、和操做數據庫的 DaoMaster 類。
  4. LitePal 是經過 JAVA 反射將繼承了 LitePalSupport 的 Module 類成員屬性映射到數據庫中的表中,而且經過 JAVA 反射將 Module 類的成員變量整合到原生數據庫的 ContentValue 進行操做數據庫。(無代碼生成)

最底的封裝操做數據庫方式

  1. SQLiteOpenHelper 和 SQLiteDatabase 是封裝 SQLiteStatement 進行操做數據庫。(封裝操做 SQLiteConnection 數據庫鏈接)
  2. Room 最底封裝的是 SQLiteStatement 進行操做數據庫。(封裝操做 SQLiteConnection 數據庫鏈接)
  3. GreenDao 最底封裝的是 SQLiteStatement 進行操做數據庫。(封裝操做 SQLiteConnection 數據庫鏈接)
  4. LitePal 最底層封裝的是 SQLiteDatabase 進行操做數據庫。(封裝原生的 SQLiteDatabase 數據庫)

使用/閱讀總結:在平常開發中,不建議用原生的 SQLiteOpenHelper 和 SQLiteDatabase 實現,有輪子固然要用要學嘛。在 Room、GreenDao、LitePal 的使用過程,GreenDao 在配置完依賴/插件後是最方便使用了的,可是官方推薦建議使用 Room,仍是最好根據官方的來,可是在學習 Android SQLiteDatabase 的時候,最好仍是讀懂一些第三方框架。在本文中沒有涉及到各個框架的優化問題(好比:鏈接池優化、SQLiteStatement 怎麼緩存優化等)。

相關文章
相關標籤/搜索