GreenDao源码分析(一)

本文是对GreenDao框架的源码解析,若您对GreenDao的基本使用还不了解,可以先快速学习GreenDao,对GreenDao有了基本了解后在来阅读本文章。

GreenDao的初始化

为了更好地解析GreenDao的初始化过程,我们先创建一个实体类——Character类。这个类很简单,就只有id和名字。

@Entity
public class Character {
    @Id(autoincrement = true)
    Long id;

    @NotNull
    String name;
}

然后make一下项目,就会自动生成三个java文件。
在这里插入图片描述
接下来看看我们的常规初始化操作。官方其实也推荐我们在Application的onCreate方法里进行初始化。

public class MyApp extends Application {
	private DaoSession mDaoSession;
	
    @Override
    public void onCreate() {
        super.onCreate();
        initGreenDao();
    }

    private void initGreenDao() {
    	//1
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "user.db");
        SQLiteDatabase db = helper.getWritableDatabase();
        //2
        DaoMaster daoMaster = new DaoMaster(db);
        //3
        daoSession = daoMaster.newSession();
    }

    public DaoSession getDaoSession() {
        return mDaoSession;
    }
}

代码1这里的Helper用的是DevOpenHelper,跳进去看看是怎么初始化的。

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

        DevOpenHelper是DaoMaster的一个静态内部类,上面有句警告说:更新的时候会把所有的表都给删了,只能在开发阶段使用!再看看onUpgrade方法,确实如此。其实官方说明文档也强调了这一点。
        深入父构造方法看看。

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

没有什么特殊的,只是继续调用了它的父构造方法,再深入看看。

public DatabaseOpenHelper(Context context, String name, CursorFactory factory, int version) {
        super(context, name, factory, version);
        this.context = context;
        this.name = name;
        this.version = version;
    }

        做的事情也很简单,就是初始化数据库名、版本号。
        回过来看看上一级onCreate方法,就调用了createAllTables方法,看样子是要创建数据库表了。进去看看。

/** Creates underlying database table using DAOs. */
    public static void createAllTables(Database db, boolean ifNotExists) {
        CharacterDao.createTable(db, ifNotExists);
    }

我们一开始写的Character类,在make之后就生成了CharacterDao类,这个地方调用了调用了createTable方法,进去看看。

/** Creates the underlying database table. */
    public static void createTable(Database db, boolean ifNotExists) {
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "\"WEAPON\" (" + //
                "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
                "\"NAME\" TEXT);"); // 1: name
    }

        可以看到,这个地方就是真正的create了,使用了SQL的create语句来创建Character表。记得Character只有id和name,且id是自增主键。这里的sql语句都是自动生成的。到这里,helper的初始化就算是走了一遍了。
        接下来是DaoMaster的初始化。

public DaoMaster(SQLiteDatabase db) {
        this(new StandardDatabase(db));
    }
...
public DaoMaster(Database db) {
        super(db, SCHEMA_VERSION);
        registerDaoClass(CharacterDao.class);
    }

DaoMaster的构造函数很简单,就是注册一下CharacterDao类。这里可以做一个有趣的实验,再创建一个实体类看看。

@Entity
public class Weapon {
    @Id(autoincrement = true)
    Long id;
    
    String name;
}

make一下项目,再回到刚才那个构造函数。

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

构造函数自动添加了注册WeaponDao的代码,跳进registerDaoClass看看。

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

代码很简单,就是把Dao的设置信息保存到一个HashMap里,key值就是这个Dao的类名。再看看DaoConfig的初始化。代码较长,一点点分析。

//DaoConfig.java
//1
this.tablename = (String) daoClass.getField("TABLENAME").get(null);
//2
Property[] properties = reflectProperties(daoClass);
this.properties = properties;
allColumns = new String[properties.length];
...


//CharacterDao.java
...
//A
public static final String TABLENAME = "CHARACTER";
/**
 * Properties of entity Character.<br/>
 * Can be used for QueryBuilder and for referencing column names.
 */
//B
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");
}
...

        代码1获取CharacterDao对应的表的名字,也就是CHARACTER表(代码A处)。
        当我们make项目后,就会自动生成对应于实体类的Properties类。我们写的Character类只有id和name,于是这里自动生成了两个Property分别对应于id和name。Properties是CharacterDao的静态内部类,等会反射会用到。
        回到代码2处,为了获取CharacterDao的各Property,就需要把它们映射出来。跳进reflectProperties方法看看,主要代码如下。

private static Property[] reflectProperties(Class<? extends AbstractDao<?, ?>> daoClass)
            throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException {
        //1
        Class<?> propertiesClass = Class.forName(daoClass.getName() + "$Properties");
        //2
        Field[] fields = propertiesClass.getDeclaredFields();

        ArrayList<Property> propertyList = new ArrayList<Property>();
        //3
        final int modifierMask = Modifier.STATIC | Modifier.PUBLIC;
        //4
        for (Field field : fields) {
            // There might be other fields introduced by some tools, just ignore them (see issue #28)
            //5
            if ((field.getModifiers() & modifierMask) == modifierMask) {
                Object fieldValue = field.get(null);
                if (fieldValue instanceof Property) {
                    propertyList.add((Property) fieldValue);
                }
            }
        }
        ...

代码1会获取一个xxx$Properties的类,例如传进来的daoClass是CharacterDao,那得到的就是CharacterDao$Properties,可以查看一下自动生成的class文件。
在这里插入图片描述
        这里给大家推荐一个文件搜索工具,叫EveryThing。搜索的速度比Windows自带的搜索工具快非常多。
        代码2通过反射获取各Property,对CharacterDao而言就是Id和Name。
        从代码3可以知道GreenDao只关心static变量和public变量。
        再看看代码块4,里面有一句英文注释,意思是说实体类的域也有可能是其他框架或工具产生的,如果是这样的域就直接无视掉。代码块5说明了一切,只要这个域不是Property类就无视掉。
        现在终于知道GreenDao为什么要大费周折来生成Property类的代码了,其他框架也有可能会在实体类的代码上添加东西,为了避免它们的影响,每个GreenDao标记的域都会生成对应的Property,实体类上没有对应Property的域就会被GreenDao无视了。这个方法真的很巧妙。
        这么一来就获得了CharacterDao所有的Property,继续分析。

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

这一部分的工作很简单,就是把主键和非主键分离(一个表的主键可以有多个,也可以没有),继续。

...
			if (pkProperty != null) {
                Class<?> type = pkProperty.type;
                //1
                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;
            }
...

        代码1处会分析主键是数字型还是字符串型,这里可以看到GreenDao是可以识别基本数据类型和包装类型的,而且数字型主键也不局限与Long类型,其他数字类型也是可以的。所以部分GreenDao教程文章说@Id标签只能用在Long类型上面其实是不对的。
        这么一来,DaoConfig的初始化就完成了,再把它存入HashMap里DaoMaster的初始化也就完成了。
        接下来就是DaoSession的初始化了。

...
//MyApp.java
daoSession = daoMaster.newSession();
...

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

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

		//1
        registerDao(Character.class, characterDao);
        //2
        registerDao(Weapon.class, weaponDao);
    }
...
protected <T> void registerDao(Class<T> entityClass, AbstractDao<T, ?> dao) {
        entityToDao.put(entityClass, dao);
}
...

        代码1,代码2就把刚刚CharacterDao和WeaponDao注册进去了,这么一来DaoSession的初始化也就完成了,这也表示GrennDao的初始化完成了。

注意事项

就本文章的项目而言,GreenDao为其生成的java文件有4个。
在这里插入图片描述
在这里插入图片描述
        这些由GreenDao生成的java文件并不是只读文件(read-only),最好不要去修改。经过刚才的源码分析我们也看到大量自动生成的代码,很多都是一环扣一环的,修改里面的代码可能会导致工程无法编译成功。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值