阅读 260

GreenDao源码解析

为了理解android数据库操作内容,从分析GreenDao源码解析开始,在解析之前先对android自带数据库操作做一下回顾。

/**
 * @desc
 * @auth pans
 * time 2019-07-25 16:27
 */
@RequiresApi(Build.VERSION_CODES.P)
class DatabaseOpenHelper(context: Context?, name: String?, version: Int, openParams: SQLiteDatabase.OpenParams) :
/**
 *
 * @param context:Context
 * @param name:VersionName
 * @param version:Version
 * @param openParams:OpenParams
 */
    SQLiteOpenHelper(context, name, version, openParams) {
    /**
     * Version of Database
     */
    var Version : Int = 1

    /**
     * @param db :SQLiteDatabase
     * Create Database
     */
    override fun onCreate(db: SQLiteDatabase?) {
       var sql :String="create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))"
        db?.execSQL(sql)
    }
    /**
     * @param db :SQLiteDatabase
     * @param oldVersion:Old Version of Database
     * @param newVersion:New Version of Database
     * Create Database
     */
    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
      var sql :String="alter table person add sex varchar(8)"
        db?.execSQL(sql)
    }

}
复制代码

这里是比较简单的数据库创建和更新操作,对数据的具体操作需要SQLiteDatabase对象,这里顺便把增删改查也封装了一下

/**
 *@desc
 *@auth ${user}
 *time 2019-07-26 15:53
 */
/**
 * @param context:Context
 * @param name:VersionName
 * @param version:Version
 * @param openParams:OpenParams
 */
class DatabaseManager(context: Context,name: String,version:Int){
    lateinit var helper:DatabaseOpenHelper
    /**
     * @param context:Context
     * @param name:VersionName
     * @param version:Version
     * @param openParams:OpenParams
     * @return DatabaseOpenHelper
     */
    @SuppressLint("NewApi")
    fun getHelper(context: Context?, name: String?, version: Int, openParams: SQLiteDatabase.OpenParams) : SQLiteOpenHelper {
        this.helper=DatabaseOpenHelper(context,name,version,openParams)
        return helper
    }

    /**
     * @param helper: DatabaseOpenHelper
     */
    fun Create(helper: DatabaseOpenHelper){
        var sqLiteDatabase:SQLiteDatabase=helper.writableDatabase
    }
    /**
     * @param helper: DatabaseOpenHelper
     */
    fun Upgrade(helper: DatabaseOpenHelper){
        var sqLiteDatabase:SQLiteDatabase=helper.writableDatabase
    }
    /**
     * @param helper: DatabaseOpenHelper
     */
    fun Insert(helper: DatabaseOpenHelper ,hashMap: HashMap<String,String>,table: String){
        var sqLiteDatabase:SQLiteDatabase=helper.writableDatabase
        var values:ContentValues= ContentValues()
        hashMap.forEach{
            values.put(it.key,it.value)
        }
        sqLiteDatabase.insert(table,null,values)
        sqLiteDatabase.close()
    }
    /**
     * @param helper: DatabaseOpenHelper
     */
    fun Query(helper: DatabaseOpenHelper){
        var sqLiteDatabase:SQLiteDatabase=helper.readableDatabase
        var cursor:Cursor=sqLiteDatabase.query("user", arrayOf("name"),
            "id=?", arrayOf("1"),null,null,null
        )
        var id: String? =null
        var name: String? =null
        while (cursor.moveToNext()){
            id=cursor.getString(cursor.getColumnIndex("id"))
            name=cursor.getString(cursor.getColumnIndex("name"))
        }
        sqLiteDatabase.close()
    }
    /**
     * @param helper: DatabaseOpenHelper
     * @param key:String
     * @param value:String
     * @param table:String
     * @param whereClause:String
     * @param whereArgs:Array<String>
     */
    fun Notify(helper: DatabaseOpenHelper,key:String,value:String,table:String,whereClause:String,whereArgs:Array<String>){
        var sqLiteDatabase:SQLiteDatabase=helper.writableDatabase
        var values:ContentValues= ContentValues()
        values.put(key,value)
        sqLiteDatabase.update(table,values,whereClause,whereArgs)
        sqLiteDatabase.close()
    }
    /**
     * @param helper: DatabaseOpenHelper
     * @param table:String
     * @param whereClause:String
     * @param whereArgs:Array<String>
     */
    fun Delete(helper: DatabaseOpenHelper,table:String,whereClause:String,whereArgs:Array<String>){
        var sqLiteDatabase:SQLiteDatabase=helper.writableDatabase
        sqLiteDatabase.delete(table,whereClause, whereArgs)
        sqLiteDatabase.close()
    }
}
复制代码

以上就完成了数据库的基本操作包括创建,更新,增,删,改,查操作。然后依据SQLiteOpenHelper为切入点,进入GreenDao来分析一下源码操作。

DevOpenHelper是DaoMaster的内部类和SQLiteOpenHelper的关系为: DaoMaster.DevOpenHelper->DaoMaster.OpenHelper->DatabaseOpenHelper->SQLiteOpenHelper

DatabaseOpenHelper:封装了对于数据库的加解密; DaoMaster.OpenHelper:在OnCreate中调用了createAllTables(Database, boolean); DaoMaster.DevOpenHelper:在升级的时候删除掉了所有表,然后再重新创建,这会造成数据丢失,只在开发时候使用。

DaoMaster 针对代码: daoMaster = new DaoMaster(devOpenHelper.getWritableDatabase());

DaoMaster是使用greenDAO的入口,其持有数据库对象(SQLiteDatabase)并管理XyzDao类(注意并不是XyzDao对象)。它拥有创建table和删除table的静态方法;并且,DaoMaster.DevOpenHelper和DaoMaster.OpenHelper就是它的内部类。 public DaoMaster(SQLiteDatabase db) { this(new StandardDatabase(db)); }

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

StandardDatabase是SQLiteDatabase代理类,很经典的代理模式。 AbstractDaoMaster是DaoMaster,顺着调用,我们到AbstractDaoMaster中继续看:

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

    ……
}
复制代码

AbstractDaoMaster有个比较重要的类变量值得我们关注: daoConfigMap,它的key是 XyzDao.class,value是DaoConfig对象。

DaoConfig: 存储了XyzDao中的基本数据(如表名,列名,主键),这些基本数据主要通过反射XyzDao类中的静态内部类Properties而获得。

public final class DaoConfig implements Cloneable {

    ……
    
    public final TableStatements statements;
    private IdentityScope<?, ?> identityScope;
    
    ……
}
复制代码

DaoConfig中有两个类变量需要说明一下:

IdentityScope:greenDAO缓存相关,后续会详细介绍 TableStatements:帮助类,供greenDAO内部使用,目的是为表格创建SQL语句。

DaoSession 针对代码:

daoSession = daoMaster.newSession();        

public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap) {
    super(db);

    androidMonsterDaoConfig = daoConfigMap.get(AndroidMonsterDao.class).clone();
    androidMonsterDaoConfig.initIdentityScope(type);

    androidMonsterDao = new AndroidMonsterDao(androidMonsterDaoConfig, this);
    registerDao(AndroidMonster.class, androidMonsterDao);
}
复制代码

从以上代码,我们知道,DaoSession主要做了下面几件事:

初始化DaoConfig是否启用缓存:通过DaoConfig的initIdentityScope()函数 生成XyzDao对象,并将DaoConfig作为参数,注入到XyzDao对象中。从而使得XyzDao对象知晓了自己的所有情况。 注册XyzDao对象:虽然通过DaoSession就可以操作数据库,但真正执行操作的还是通过XyzDao对象

XyzDao 针对代码: andoridMonsterDao = daoSession.getAndroidMonsterDao();

说到XyzDao,首先要先说一下其静态内部类:Properties

public static class Properties {
    public final static Property Id = new Property(0, Long.class, "id", true, "_id");
    public final static Property Name = new Property(1, String.class, "name", false, "NAME");
    public final static Property SuperPower = new Property(2, String.class, "superPower", false, "SUPER_POWER");
}
复制代码

它是描述数据列的属性的元数据。一般有两个用处:

query builder使用他去创建WhereCondition对象 DaoMaster初始化时,DaoConfig对象内部变量的赋值,绝大部分是通过反射这个类(上文已有所提及)。

XyzDao类本身,主要是一些与业务相关的函数,比如创建&删除表格;用于greenDAO框架回调的一些业务函数(bindValues(), readEntity(), getKey()等等) XyzDao类的父类AbstractDao,内容则相当丰富:

支持获得到对应表格的各种信息(通过DaoConfig) 支持获得基本的SQL语句(通过TableStatements) 支持各种各样增删查改的方法 对于缓存的支持(IdentityScope) 对于RX的支持

XyzEntity 针对代码: AndoridMonster andoridMonster= new AndoridMonster ();

XyzEntity比较简单,我们定义了类名和类变量后,greenDAO会帮我们自动生成:

XyzEntity构造函数 针对每个类变量的Getter/Setter函数

这里是增删改查的时序图,可以看出和之前基本没有区别,还是 获得SQLiteStatement对象 SQLiteStatement通过bindLong(), bindString()等等函数,将业务数据保存到SQLiteStatement的父类SQLiteProgram的mBindArgs数组中(牵扯到具体业务,所以需要回调到XyzEntity的bindValues(),从而拿到业务数据) SQLiteStatement通过executeInsert()执行插入

另外,我们可以看到,在插入成功后,会调用updateKeyAfterInsertAndAttach(),这其中会涉及到XyzEntity对象的key的更新,以及缓存的存储。 新增的就是会开启事务,然后加锁,主要是为了数据的安全性考虑的。

greenDAO的源代码,有一部分是固有代码,另一部分则是编译生成的,他们协同合作完成了greenDAO的功能,即ORM(Object-relational mapping)。 关于ORM,我们可以参考官网的图:

greenDAO-ORM官网图.png

从上图,我们可以很清楚的看到,greenDAO提供了从关系型数据库到Java对象的映射,从而使得开发者不用直接操作数据库而操作Java对象就可以了,减少了开发者的负担。

主流程梳理 简易的类关系图,可以参考官网的图:

简易的类关系图.jpg,至于详细的UML图有时间再整理一下

缓存 greenDAO的缓存机制,值得我们说道说道: 1.缓存的初始化 在介绍DaoSession时,我们就知道,DaoSession通过DaoConfig的initIdentityScope()函数,来初始化DaoConfig是否启用缓存。然后DaoSession生成XyzDao对象,并将DaoConfig作为参数,注入到XyzDao对象中。从而使得XyzDao对象知晓了自己的所有情况,包括刚刚设置的缓存。 关键代码如下:

public void initIdentityScope(IdentityScopeType type) {
    if (type == IdentityScopeType.None) {
        identityScope = null;
    } else if (type == IdentityScopeType.Session) {
        if (keyIsNumeric) {
            identityScope = new IdentityScopeLong();
        } else {
            identityScope = new IdentityScopeObject();
        }
    } else {
        throw new IllegalArgumentException("Unsupported type: " + type);
    }
}
复制代码

如果入参是IdentityScopeType.None,那么不开启缓存;如果入参是IdentityScopeType.Session,则开启缓存。 2.判断是否开启缓存的依据 greenDAO判断是否开启缓存的依据比较原始,不是通过状态变量,而是通过判断AbstractDao中的identityScope或identityScopeLong是否为空。 关键代码如下:

final protected T loadCurrent(Cursor cursor, int offset, boolean lock) {
    if (identityScopeLong != null) {
        if (offset != 0) {
            // Occurs with deep loads (left outer joins)
            if (cursor.isNull(pkOrdinal + offset)) {
                return null;
            }
        }

        long key = cursor.getLong(pkOrdinal + offset);
        T entity = lock ? identityScopeLong.get2(key) : identityScopeLong.get2NoLock(key);
        if (entity != null) {
            return entity;
        } else {
            entity = readEntity(cursor, offset);
            attachEntity(entity);
            if (lock) {
                identityScopeLong.put2(key, entity);
            } else {
                identityScopeLong.put2NoLock(key, entity);
            }
            return entity;
        }
    } else if (identityScope != null) {
        K key = readKey(cursor, offset);
        if (offset != 0 && key == null) {
            // Occurs with deep loads (left outer joins)
            return null;
        }
        T entity = lock ? identityScope.get(key) : identityScope.getNoLock(key);
        if (entity != null) {
            return entity;
        } else {
            entity = readEntity(cursor, offset);
            attachEntity(key, entity, lock);
            return entity;
        }
    } else {
        // Check offset, assume a value !=0 indicating a potential outer join, so check PK
        if (offset != 0) {
            K key = readKey(cursor, offset);
            if (key == null) {
                // Occurs with deep loads (left outer joins)
                return null;
            }
        }
        T entity = readEntity(cursor, offset);
        attachEntity(entity);
        return entity;
    }
}
复制代码

identityScope或identityScopeLong不为空,开启缓存。先尝试从缓存中读取数据,如果缓存数据不为空,则直接返回数据;如果缓存数据为空,那么读取数据库,将数据库中读取到的数据保存到缓存中,并返回该数据。 identityScope或identityScopeLong为空,不开启缓存。直接读取数据库,并返回数据库中读取到的数据。 3.缓存的使用 在以上增删查改等持久化方法的分析中,我们已夹带着分析了缓存的操作,此处不再赘述了。 性能 greenDAO性能之所以好,必有其原因,我们就从以下几点进行说明: 1.编译期生成类 不像其他框架通过运行期反射来创建ORM映射,greenDAO而是在编译期就帮忙生成了绝大部分类文件,从而提升了运行期的速度。 2.直接操作底层类 从以上增删查改等持久化方法的分析中,我们发现,greenDAO直接调用了SQLiteStatement,而没有调用SQLite开放给我们的API,如 insert(String table, String nullColumnHack, ContentValues values)

再如: execSQL(String sql)

而从Android framework的源代码中我们可以发现,其实SQLite开放给我们的API也是调用到了SQLiteStatement,比如:

private int executeSql(String sql, Object[] bindArgs) throws SQLException {
    acquireReference();
    try {
        if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
            boolean disableWal = false;
            synchronized (mLock) {
                if (!mHasAttachedDbsLocked) {
                    mHasAttachedDbsLocked = true;
                    disableWal = true;
                }
            }
            if (disableWal) {
                disableWriteAheadLogging();
            }
        }

        // 这里这里
        SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
        try {
            return statement.executeUpdateDelete();
        } finally {
            statement.close();
        }
    } finally {
        releaseReference();
    }
}
复制代码

正是由于greenDAO直接调用了更底层的类SQLiteStatement,节省了代码,从而增加了速度。 3.每个线程一个只对应一个Query,不用锁 先上图:

时序图--query-plus.jpg

上图红框中涉及的代码,是加速的重点。 QueryData是Query的静态内部类,相当于一个单例的实现,其内部有个有个Map类型变量:queriesForThreads,key是线程ID,value是query的软引用。 根据时序图,在获得Query对象时候,会调用到queryData.forCurrentThread()

Q forCurrentThread() {
    long threadId = Thread.currentThread().getId();
    synchronized (queriesForThreads) {
        WeakReference<Q> queryRef = queriesForThreads.get(threadId);
        Q query = queryRef != null ? queryRef.get() : null;
        if (query == null) {
            gc();
            query = createQuery();
            queriesForThreads.put(threadId, new WeakReference<Q>(query));
        } else {
            System.arraycopy(initialValues, 0, query.parameters, 0, initialValues.length);
        }
        return query;
    }
}
复制代码

从源代码中可以看到,每一个查询线程都只分配一个单独的Query对象:如果queriesForThreads中不存在当前线程对应的Query,那么就创建一个Query,并放入到queriesForThreads中;如果queriesForThreads中存在当前线程对应的Query,就更新Query的参数。最后返回Query对象。 在GreenDAO多线程查询时,这种设计机制,避免引入上锁解锁,提高了效率,同时也让代码会更简洁。 4.缓存机制 合理的使用的缓存,肯定可以提高效率。greenDAO中存在两种缓存:

IdentityScope缓存(Map类型对象,key为主键),上文已讲述。 线程与Query缓存(Map类型对象,key为线程ID),上文已讲述。

5.查询懒加载 真正需要用的时候,才会生成目标XyzEntity对象,以达到节省内存的目的。 主要体现两个方法中:listLazy() (使用缓存) & listLazyUncached()(不使用缓存)。 如果查询时候,我们使用 LazyList list = androidMonsterDao.queryBuilder().where(AndroidMonsterDao.Properties.SuperPower.eq("coding")).listLazy();

或者 LazyList list = androidMonsterDao.queryBuilder().where(AndroidMonsterDao.Properties.SuperPower.eq("coding")).listLazyUncached();

这时会得到LazyList类型的对象。这个对象内部的List类型变量entities存储着真正的查询结果数据。 初始化时,listLazy()与listLazyUncached()的区别是,构造函数的第三个参数cacheEntities:listLazy(),cacheEntities为true;listLazyUncached(),cacheEntities为false。 源代码如下:

LazyList(InternalQueryDaoAccess<E> daoAccess, Cursor cursor, boolean cacheEntities) {
    this.cursor = cursor;
    this.daoAccess = daoAccess;
    size = cursor.getCount();
    if (cacheEntities) {
        entities = new ArrayList<E>(size);
        for (int i = 0; i < size; i++) {
            entities.add(null);
        }
    } else {
        entities = null;
    }
    if (size == 0) {
        cursor.close();
    }

    lock = new ReentrantLock();
}
复制代码

从以上源代码可以看到:如果cacheEntities为false,那么entities直接被设为null;如果cacheEntities为true,那么entities的value值均被人为设置为null。 LazyList的“懒”主要体现在get(int location),源代码如下:

public E get(int location) {
    if (entities != null) {
        E entity = entities.get(location);
        if (entity == null) {
            lock.lock();
            try {
                entity = entities.get(location);
                if (entity == null) {
                    entity = loadEntity(location);
                    entities.set(location, entity);
                    // Ignore FindBugs: increment of volatile is fine here because we use a lock
                    loadedCount++;
                    if (loadedCount == size) {
                        cursor.close();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
        return entity;
    } else {
        lock.lock();
        try {
            return loadEntity(location);
        } finally {
            lock.unlock();
        }
    }
}
复制代码

对于listLazy(),在第一次调用LazyList的get(int location)时,才会触发读取数据库中的数据,并填充entities;而再次调用get(int location)时,则可直接返回entities中已缓存的数据。 listLazyUncached()区别就是,不管第几次调用LazyList的get(int location),由于entities为null,都会读取数据库。这种做法无疑最节省内存,但在获取数据的速度上有所牺牲。 大家可以根据实际情况(速度vs内存)来选择list(),listLazy() 或者listLazyUncached()。 以上就是对GreenDao的源码分析,另外关于对缓存策略和注解原理有兴趣的小伙伴可以看我以后写的两篇文章

链接

GreenDAO系列(三) GreenDAO源码整体流程梳理
https://www.jianshu.com/p/10b04b86c29a复制代码
关注下面的标签,发现更多相似文章
评论