Android Greendao 緩存分析:源碼分析

需要掌握的基本:

1.反射

2.泛型

3.註解

4.clone

 

 

greenDAO 使用「Code generation(代碼生成)」的方式,這也是其性能能大幅提升的原因。

 

Java的Code Generation: 

拼字節碼,讀懂JAVA的CLASS文件的字節碼規範,然後自己按規範拼一個一模一樣的字節碼,然後用ClassLoader的defineClass讀入並生成

————————————————

 

看完源碼:

1.修改數據庫的路徑

2.創建不同數據庫對應不同的table

3.怎麼保證強原子性的

4.自己寫帶有緩存的處理

 

自定義SQL帶來的數據不同步問題

有沒有insertOrUpdate呢,insertOrReplace有的時候會把之前的字段清空了

 

 

初始化的步驟:

1.helper對象

2.拿到Db對象

3.得到master對象

4.得到dao對象

private void setDatabase() {

    // 通過 DaoMaster 的內部類 DevOpenHelper,你可以得到一個便利的 SQLiteOpenHelper 對象。
    // 可能你已經注意到了,你並不需要去編寫「CREATE TABLE」這樣的 SQL 語句,因爲 greenDAO已經幫你做了。
    // 注意:默認的 DaoMaster.DevOpenHelper 會在數據庫升級時,刪除所有的表,意味着這將導致數據的丟失。
    // 所以,在正式的項目中,你還應該做一層封裝,來實現數據庫的安全升級。
    mHelper = new DaoMaster.DevOpenHelper(this, "notes-db", null);

    db = mHelper.getWritableDatabase();

    // 注意:該數據庫連接屬於 DaoMaster,所以多個 Session 指的是相同的數據庫連接。
    mDaoMaster = new DaoMaster(db);

    mDaoSession = mDaoMaster.newSession();

    setDebugMode(true);

 

 

DaoMaster

DaoMaster是GreenDao的入口

mHelper = new DaoMaster.DevOpenHelper(this, "notes-db", null);
/** Creates underlying database table using DAOs. */
public static void createAllTables(Database db, boolean ifNotExists) {
    UserDao.createTable(db, ifNotExists);
}

/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
    UserDao.dropTable(db, ifExists);
}

/**
 * WARNING: Drops all table on Upgrade! Use only during development.
 * Convenience method using a {@link DevOpenHelper}.
 */
public static DaoSession newDevSession(Context context, String name) {
    Database db = new DevOpenHelper(context, name).getWritableDb();
    DaoMaster daoMaster = new DaoMaster(db);
    return daoMaster.newSession();
}

public DaoMaster(SQLiteDatabase db) {
    this(new StandardDatabase(db));
}

public DaoMaster(Database db) {
    super(db, SCHEMA_VERSION);
    registerDaoClass(UserDao.class);
}

public DaoSession newSession() {
    return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}

public DaoSession newSession(IdentityScopeType type) {
    return new DaoSession(db, type, daoConfigMap);
}

/**
 * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
 */
public static abstract class OpenHelper extends DatabaseOpenHelper {
    public OpenHelper(Context context, String name) {
        super(context, name, SCHEMA_VERSION);
    }

    public OpenHelper(Context context, String name, CursorFactory factory) {
        super(context, name, factory, SCHEMA_VERSION);
    }

    @Override
    public void onCreate(Database db) {
        Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
        createAllTables(db, false);
    }
}

/** WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
    public DevOpenHelper(Context context, String name) {
        super(context, name);
    }

    public DevOpenHelper(Context context, String name, CursorFactory factory) {
        super(context, name, factory);
    }

    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
        dropAllTables(db, true);
        onCreate(db);
    }
}

可以看出來:主要是創建數據庫,創建表,還有數據庫的升級

 

數據庫創建和我們原生的一樣,用SQLiteOpenHelper 

public abstract class DatabaseOpenHelper extends SQLiteOpenHelper {

getDatabaseLocked

 

  private SQLiteDatabase getDatabaseLocked(boolean writable) {
         // 首先方法接收一個是否可讀的參數
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                //數據庫沒有打開,關閉並且置空
                mDatabase.close().
                mDatabase = null;
            } else if (!writable || !mDatabase.isReadOnly()) {
                //只讀或者數據庫已經是讀寫狀態了,則直接返回實例
                return mDatabase;
            }
        }

注意:得到數據庫都時候,如果沒有打開,先關閉並且質空,然後重新開。



DaoMaster構造方法中會把所有的Dao類註冊到Map中,每個Dao對應一個DaoConfig配置類。

public abstract class AbstractDaoMaster {
    protected final Database db;
    protected final int schemaVersion;
    protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;

    public AbstractDaoMaster(Database db, int schemaVersion) {
        this.db = db;
        this.schemaVersion = schemaVersion;

        daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
    }

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

 

userDaoConfig = daoConfigMap.get(UserDao.class).clone();

 

public final class DaoConfig implements Cloneable {

Cloneable接口是一個標記接口,也就是沒有任何內容,定義如下:

這裏分析一下這個接口的用法,clone方法是在Object種定義的,而且是protected型的,只有實現了這個接口,纔可以在該類的實例上調用clone方法,否則會拋出CloneNotSupportException。Object中默認的實現是一個淺拷貝,也就是表面拷貝,如

 

public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass) {
    this.db = db;
    try {
        this.tablename = (String) daoClass.getField("TABLENAME").get(null);
        Property[] properties = reflectProperties(daoClass);
        this.properties = properties;

        allColumns = new String[properties.length];

        List<String> pkColumnList = new ArrayList<String>();
        List<String> nonPkColumnList = new ArrayList<String>();
        Property lastPkProperty = null;
        for (int i = 0; i < properties.length; i++) {
            Property property = properties[i];
            String name = property.columnName;
            allColumns[i] = name;
            if (property.primaryKey) {
                pkColumnList.add(name);
                lastPkProperty = property;
            } else {
                nonPkColumnList.add(name);
            }
        }
        String[] nonPkColumnsArray = new String[nonPkColumnList.size()];
        nonPkColumns = nonPkColumnList.toArray(nonPkColumnsArray);
        String[] pkColumnsArray = new String[pkColumnList.size()];
        pkColumns = pkColumnList.toArray(pkColumnsArray);

        pkProperty = pkColumns.length == 1 ? lastPkProperty : null;
        statements = new TableStatements(db, tablename, allColumns, pkColumns);

        if (pkProperty != null) {
            Class<?> type = pkProperty.type;
            keyIsNumeric = type.equals(long.class) || type.equals(Long.class) || type.equals(int.class)
                    || type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class)
                    || type.equals(byte.class) || type.equals(Byte.class);
        } else {
            keyIsNumeric = false;
        }

    } catch (Exception e) {
        throw new DaoException("Could not init DAOConfig", e);
    }
}
public class UserDao extends AbstractDao<User, Long> {
AbstractDao

 
public abstract class AbstractDao<T, K> {
protected final DaoConfig config;
protected final Database db;
protected final boolean isStandardSQLite;
protected final IdentityScope<K, T> identityScope;
protected final IdentityScopeLong<T> identityScopeLong;
protected final TableStatements statements;

protected final AbstractDaoSession session;
protected final int pkOrdinal;

private volatile RxDao<T, K> rxDao;
private volatile RxDao<T, K> rxDaoPlain;

 
long insert(T entity) {
    return executeInsert(entity, statements.getInsertStatement(), true);
}
public void update(T entity) {
    assertSinglePk();
    DatabaseStatement stmt = statements.getUpdateStatement();
    if (db.isDbLockedByCurrentThread()) {
        synchronized (stmt) {
            if (isStandardSQLite) {
                updateInsideSynchronized(entity, (SQLiteStatement) stmt.getRawStatement(), true);
            } else {
                updateInsideSynchronized(entity, stmt, true);
            }
        }
    } else {
        // Do TX to acquire a connection before locking the stmt to avoid deadlocks
        db.beginTransaction();
        try {
            synchronized (stmt) {
                updateInsideSynchronized(entity, stmt, true);
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }

插入的時候用實務的操作

private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
    long rowId;
    if (db.isDbLockedByCurrentThread()) {
        rowId = insertInsideTx(entity, stmt);
    } else {
        // Do TX to acquire a connection before locking the stmt to avoid deadlocks
        db.beginTransaction();
        try {
            rowId = insertInsideTx(entity, stmt);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
    if (setKeyAndAttach) {
        updateKeyAfterInsertAndAttach(entity, rowId, true);
    }
    return rowId;
}

 
private long insertInsideTx(T entity, DatabaseStatement stmt) {
synchronized (stmt) {
if (isStandardSQLite) {
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
bindValues(rawStmt, entity);
return rawStmt.executeInsert();
} else {
bindValues(stmt, entity);
return stmt.executeInsert();
}
}
}

 

@Override
protected final void bindValues(DatabaseStatement stmt, User entity) {
    stmt.clearBindings();

    Long id = entity.getId();
    if (id != null) {
        stmt.bindLong(1, id);
    }

    String name = entity.getName();
    if (name != null) {
        stmt.bindString(2, name);
    }
}

描述index和數據的關係,最終保存進mBindArgs。到這裏,應該就能明白greenDao的核心作用。greenDao將我們熟悉的對象,轉換成sql語句和執行參數,再提交SQLite執行。

 

更新數據庫:如果當前線程持有,可以直接更新,然後被別的線程持有,要通過實務的操作

}

當前線程獲取數據庫鎖的情況下,直接執行insert操作即可,否則需要使用事務保證操作的原子性和一致性。

public void update(T entity) {
    assertSinglePk();
    DatabaseStatement stmt = statements.getUpdateStatement();
    if (db.isDbLockedByCurrentThread()) {
        synchronized (stmt) {
            if (isStandardSQLite) {
                updateInsideSynchronized(entity, (SQLiteStatement) stmt.getRawStatement(), true);
            } else {
                updateInsideSynchronized(entity, stmt, true);
            }
        }
    } else {
        // Do TX to acquire a connection before locking the stmt to avoid deadlocks
        db.beginTransaction();
        try {
            synchronized (stmt) {
                updateInsideSynchronized(entity, stmt, true);
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
}

生成Sql

sql的獲取需要用到TableStatements,

實體類:

 

public class UserDao extends AbstractDao<User, Long> {

 

緩存:

}

在執行真正的數據加載前,標記1處先查找緩存,如果有就直接返回,無就去查數據庫。標記2處準備sql語句和參數,交給rawQuery查詢,得到Cursor。

 

AbstractDao裏面:

 

 

 

public T load(K key) {
    assertSinglePk();
    if (key == null) {
        return null;
    }
    if (identityScope != null) {
        T entity = identityScope.get(key);
        if (entity != null) {
            return entity;
        }
    }
    String sql = statements.getSelectByKey();
    String[] keyArray = new String[]{key.toString()};
    Cursor cursor = db.rawQuery(sql, keyArray);
    return loadUniqueAndCloseCursor(cursor);
}

 

 

是一個接口,相當於一個map,增刪改查。

 

/**
 * Common interface for a identity scopes needed internally by greenDAO. Identity scopes let greenDAO re-use Java
 * objects.
 * 
 * @author Markus
 * 
 * @param <K>
 *            Key
 * @param <T>
 *            Entity
 */
public interface IdentityScope<K, T> {

    T get(K key);

    void put(K key, T entity);

    T getNoLock(K key);

    void putNoLock(K key, T entity);

    boolean detach(K key, T entity);

    void remove(K key);

    void remove(Iterable<K> key);

    void clear();

    void lock();

    void unlock();

    void reserveRoom(int count);

}

 

 

清除緩存:

identityScope 一個map清除

 

 

public void deleteAll() {
    // String sql = SqlUtils.createSqlDelete(config.tablename, null);
    // db.execSQL(sql);

    db.execSQL("DELETE FROM '" + config.tablename + "'");
    if (identityScope != null) {
        identityScope.clear();
    }
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章