Android开发者必知的Java知识(三) 结合注解分析ActiveAndroid的实现

关于Android开发者必知的Java知识系列,我们开头刚刚讲过了java中的反射机制和注解的用法,下面我们结合一个实际的例子来深入这方面的理解。相信大家对ActiveAndroid这个github上的开源项目,一定是不陌生的。它实际上就是一个对象关系映射模型(ORM),这一讲中我们一边结合ActiveAndroid的使用方法,一边来分析它的源码,并加深对反射和注解的了解。

转载请注明出处: 西木的博客

关于ActiveAndroid的安装和入门,大家可以参考它的文档,我在这里就不赘述了,直接进主题。

1.数据库模型的创建

数据库模型的创建非常简单,我们只要创建自己的类继承自Model,然后给类和成员加上注解,像这样:

@Table(name = "Items")
public class Item extends Model {
        // If name is omitted, then the field name is used.
        @Column(name = "Name")
        public String name;

        @Column(name = "Category")
        public Category category;

        public Item() {
                super();
        }

        public Item(String name, Category category) {
                super();
                this.name = name;
                this.category = category;
        }
}

我们已经看到了ActiveAndroid中得两个注解: @Table 和 @Column,顾名思义,这两个注解能辅助我们生成对应的数据库表模型。接下来,就该分析源码了,我们先来看看这个两个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {

    public static final String DEFAULT_ID_NAME = "Id";
    public String name();
    public String id() default DEFAULT_ID_NAME;
}

Table注解的作用范围为类,作用时间为runtime,并且只有两个注解元素,name和id,其中id有默认值为”id”, name没有默认值,意味着我们必须给他赋值,我们再来看Column:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public enum ConflictAction {
        ROLLBACK, ABORT, FAIL, IGNORE, REPLACE
    }

    public enum ForeignKeyAction {
        SET_NULL, SET_DEFAULT, CASCADE, RESTRICT, NO_ACTION
    }

    public String name() default "";

    public int length() default -1;

    public boolean notNull() default false;

    public ConflictAction onNullConflict() default ConflictAction.FAIL;

    public ForeignKeyAction onDelete() default ForeignKeyAction.NO_ACTION;

    public ForeignKeyAction onUpdate() default ForeignKeyAction.NO_ACTION;

    public boolean unique() default false;

    public ConflictAction onUniqueConflict() default ConflictAction.FAIL;

    /*
     * If set uniqueGroups = {"group_name"}, we will create a table constraint with group.
     *
     * Example:
     *
     * @Table(name = "table_name")
     * public class Table extends Model {
     *     @Column(name = "member1", uniqueGroups = {"group1"}, onUniqueConflicts = {ConflictAction.FAIL})
     *     public String member1;
     *
     *     @Column(name = "member2", uniqueGroups = {"group1", "group2"}, onUniqueConflicts = {ConflictAction.FAIL, ConflictAction.IGNORE})
     *     public String member2;
     *
     *     @Column(name = "member3", uniqueGroups = {"group2"}, onUniqueConflicts = {ConflictAction.IGNORE})
     *     public String member3;
     * }
     *
     * CREATE TABLE table_name (..., UNIQUE (member1, member2) ON CONFLICT FAIL, UNIQUE (member2, member3) ON CONFLICT IGNORE)
     */
    public String[] uniqueGroups() default {};

    public ConflictAction[] onUniqueConflicts() default {};

    /*
     * If set index = true, we will create a index with single column.
     *
     * Example:
     *
     * @Table(name = "table_name")
     * public class Table extends Model {
     *     @Column(name = "member", index = true)
     *     public String member;
     * }
     *
     * Execute CREATE INDEX index_table_name_member on table_name(member)
     */
    public boolean index() default false;

    /*
     * If set indexGroups = {"group_name"}, we will create a index with group.
     *
     * Example:
     *
     * @Table(name = "table_name")
     * public class Table extends Model {
     *     @Column(name = "member1", indexGroups = {"group1"})
     *     public String member1;
     *
     *     @Column(name = "member2", indexGroups = {"group1", "group2"})
     *     public String member2;
     *
     *     @Column(name = "member3", indexGroups = {"group2"})
     *     public String member3;
     * }
     *
     * Execute CREATE INDEX index_table_name_group1 on table_name(member1, member2)
     * Execute CREATE INDEX index_table_name_group2 on table_name(member2, member3)
     */
    public String[] indexGroups() default {};
}

Column注解相对复杂,我们一项一项来分析,
* name,不必多说,
* 其次是length,因为像varchar这样的类型是需要提供长度的,
* 然后是限制符notNull,是否可以为空值,默认是false不可以
* 接下来是ConflictAction类型的元素onNullConflict 是一个事先定义好的enum类型,就是发生field值不能为空却赋予了空值如何解决,默认是添加失败
* ForeignKeyAction类型的onDelete元素,也是enum类型,指定了在删除时对外键如何处理,默认是不处理
* onUpdate同样
* boolean类型的unique,是否值唯一,默认非
* 接下来就是如果unique,发生冲突时如何解决,默认是失败
* 再接下来uniqueGroups和onUniqueConflicts,是对一组field值唯一,和冲突时的解决措施
* 最后是index,如果为true,就在该列创建索引,以及indexGroups,就是在某一组列上创建索引

初始化TableInfo

我们现在已经知道了这两个注解的定义,那么你们肯定会问注解处理器在哪里呢?我先告诉你们:有一部分在TableInfo这个类中大家看TableInfo类的构造函数, 它是通过一个继承子Model类的Class对象来构建的。

public TableInfo(Class<? extends Model> type) {
        mType = type;
        //1.第一步获取类上得table注解
        final Table tableAnnotation = type.getAnnotation(Table.class);
        //2.获取table注解上的name和id,表示数据库表名和id
        if (tableAnnotation != null) {
            mTableName = tableAnnotation.name();
            mIdName = tableAnnotation.id();
        }
        else {
            mTableName = type.getSimpleName();
        }
        //3.手动为表添加上id列
        // Manually add the id column since it is not declared like the other columns.
        Field idField = getIdField(type);
        mColumnNames.put(idField, mIdName);
        //4.反射方法ReflectionUtils.getDeclaredColumnFields获取type类以及祖先类上定义了Column注解的field
        List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));
        Collections.reverse(fields);

        for (Field field : fields) {
            //5.再次检查filed上否有column注解
            if (field.isAnnotationPresent(Column.class)) {
                final Column columnAnnotation = field.getAnnotation(Column.class);
                //6.获取column注解的name
                String columnName = columnAnnotation.name();
                if (TextUtils.isEmpty(columnName)) {
                    //7.如果name为空,则用field的名字代替
                    columnName = field.getName();
                }
                //8.将注解和列名一一对应保存起来
                mColumnNames.put(field, columnName);
            }
        }

    }

我们可以看到,这里仅仅只是提取了column注解的name,其他元素并没有提取,没错,我们在这里仅仅只是为了初步获取table和column的属性,到实际建立表的时候再去从保存的field上获取详细的属性。

2. 创建数据库表

很多用过activeAndroid的人也许会问,那我们究竟时候创建的表呢,刚才的tableInfo什么时候构造的呢?没错,我们在使用时完全没接触到数据库,而且创建模型,只要创建一个类对象,然后调用save就可以了。其实悬念就在Application中。我们在使用ActiveAndroid时,我们需要使用ActiveAndroid提供的Application或者使我们的application继承自AA的Application。这是为何呢?带着悬念我们来研究AA中得Application。

public class Application extends android.app.Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ActiveAndroid.initialize(this);
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        ActiveAndroid.dispose();
    }
}

我们知道Application的onCreate 方法在安卓程序开始运行时调用,后续的activity或service启动并不调用。继续看ActiveAndroid.initialize

public static void initialize(Context context) {
        initialize(new Configuration.Builder(context).create());
    }

这里调用了一个叫做Configuration.Builder的类从Context中创建了一个Configuration类的对象。这里暂且不管Configuration,接着往下看。

一步一步,摩擦摩擦^_^,我们发现最后调到了Cache.initialize,这里实际上做了真正的初始化操作。

public static synchronized void initialize(Configuration configuration) {
        if (sIsInitialized) {
            Log.v("ActiveAndroid already initialized.");
            return;
        }

        sContext = configuration.getContext();
        sModelInfo = new ModelInfo(configuration);
        sDatabaseHelper = new DatabaseHelper(configuration);

        // TODO: It would be nice to override sizeOf here and calculate the memory
        // actually used, however at this point it seems like the reflection
        // required would be too costly to be of any benefit. We'll just set a max
        // object size instead.
        sEntities = new LruCache<String, Model>(configuration.getCacheSize());

        openDatabase();

        sIsInitialized = true;

        Log.v("ActiveAndroid initialized successfully.");
    }

cache中的初始化分为如下几步:
1. 创建ModelInfo,modelInfo又会创建关于tableInfo和TypeSerializer的信息
2. 创建DatabaseHelper,这个DatabaseHelper就是继承自SQLiteOpenHelper,别跟我说你不知道SQLiteOpenHelper(不知道请移步Android官方文档)。
3. 创建一个LruCache用来对数据库对象进行缓存,加快存取操作
4. 调用openDatabase(),这个函数实际上调用了sDatabaseHelper.getWritableDatabase(), 我们知道在第一次getDatabase时,SQLiteOpenHelper的onCreate函数会执行,我们往往在其中执行真正的创建数据库的操作。现在我们移步DatabaseHelper的onCreate函数:

@Override
public void onCreate(SQLiteDatabase db) {
    executePragmas(db);
    executeCreate(db);
    executeMigrations(db, -1, db.getVersion());
    executeCreateIndex(db);
}

从字面意思上看,executeCreate就是执行创建数据库,我们直接看这个函数

private void executeCreate(SQLiteDatabase db) {
    db.beginTransaction();
    try {
        for (TableInfo tableInfo : Cache.getTableInfos()) {
            db.execSQL(SQLiteUtils.createTableDefinition(tableInfo));
        }
        db.setTransactionSuccessful();
    }
    finally {
        db.endTransaction();
    }
}

这个函数言简意赅,从tableInfos表中取出每个tableInfo,然后创建对应的数据库表,生成数据库表定义是在SQLiteUtils.createTableDefinition中,再次跳转。

public static String createTableDefinition(TableInfo tableInfo) {
    final ArrayList<String> definitions = new ArrayList<String>();

    for (Field field : tableInfo.getFields()) {
        String definition = createColumnDefinition(tableInfo, field);
        if (!TextUtils.isEmpty(definition)) {
            definitions.add(definition);
        }
    }

    definitions.addAll(createUniqueDefinition(tableInfo));

    return String.format("CREATE TABLE IF NOT EXISTS %s (%s);", tableInfo.getTableName(),
            TextUtils.join(", ", definitions));
}

首先从tableinfo中提取出field,生成对应的列定义

生成列定义

public static String createColumnDefinition(TableInfo tableInfo, Field field) {
    StringBuilder definition = new StringBuilder();

    //1.获取field定义的类型,列名,TypeSerializer
    Class<?> type = field.getType();
    final String name = tableInfo.getColumnName(field);
    final TypeSerializer typeSerializer = Cache.getParserForType(field.getType());
    //2.重新提取出Column注解对象
    final Column column = field.getAnnotation(Column.class);

    if (typeSerializer != null) {
        type = typeSerializer.getSerializedType();
    }
    //4.0.如果是基本类型
    if (TYPE_MAP.containsKey(type)) {
        definition.append(name);
        definition.append(" ");
        definition.append(TYPE_MAP.get(type).toString());
    }//4.1.如果是Model类型,表示是外键,用integer表示
    else if (ReflectionUtils.isModel(type)) {
        definition.append(name);
        definition.append(" ");
        definition.append(SQLiteType.INTEGER.toString());
    }//4.2枚举类型,
    else if (ReflectionUtils.isSubclassOf(type, Enum.class)) {
        definition.append(name);
        definition.append(" ");
        definition.append(SQLiteType.TEXT.toString());
    }

    if (!TextUtils.isEmpty(definition)) {
        //id列默认作为主键
        if (name.equals(tableInfo.getIdName())) {
            definition.append(" PRIMARY KEY AUTOINCREMENT");
        }else if(column!=null){
            //如果定义了column注解上定义了长度
            if (column.length() > -1) {
                definition.append("(");
                definition.append(column.length());
                definition.append(")");
            }
            //如果限制为非空
            if (column.notNull()) {
                definition.append(" NOT NULL ON CONFLICT ");
                definition.append(column.onNullConflict().toString());
            }
            //如果限制为unique
            if (column.unique()) {
                definition.append(" UNIQUE ON CONFLICT ");
                definition.append(column.onUniqueConflict().toString());
            }
        }
        //处理外键的情况
        if (FOREIGN_KEYS_SUPPORTED && ReflectionUtils.isModel(type)) {
            definition.append(" REFERENCES ");
            definition.append(Cache.getTableInfo((Class<? extends Model>) type).getTableName());
            definition.append("("+tableInfo.getIdName()+")");
            definition.append(" ON DELETE ");
            definition.append(column.onDelete().toString().replace("_", " "));
            definition.append(" ON UPDATE ");
            definition.append(column.onUpdate().toString().replace("_", " "));
        }
    }
    else {
        Log.e("No type mapping for: " + type.toString());
    }

    return definition.toString();
}

createUniqueDefinition是创建uniqueGroup相关的定义,我们可以暂时忽略,createTableDefinition最后一部分即返回整个sql表创建语句。执行这一语句,我们就可以出所有应用程序所需的数据库表信息了。大家可能会问,我们数据库的名字是什么,版本如何制定的,熟悉SQlite的人都知道,我们在SQliteOpenHelper的构造函数中可以指定,我们来看DatabaseHelper的构造函数

初始化数据库信息

super(configuration.getContext(), configuration.getDatabaseName(), null, configuration.getDatabaseVersion());

所有有关数据库的信息都在configuration里面,那么configuration从何而来?回到我们刚才忽略的Configuration.Builder类,这个Builder是一个经典的java用法,就是我们需要创建一个对象时,如果我们需要的参数太多,我们可以额外先创建一个builder类,这个builder类 有很多跟原始类一样的属性和set方法,我们先调用这些set方法设置好参数,然后再一次性创建出我们需要的对象。请原谅我蹩脚的描述^_^
有哪些参数需要我们设置呢?

private Context mContext;

private Integer mCacheSize;
private String mDatabaseName;
private Integer mDatabaseVersion;
private String mSqlParser;
private List<Class<? extends Model>> mModelClasses;
private List<Class<? extends TypeSerializer>> mTypeSerializers;

那么这些builder从哪里去获得这些参数呢?我们以mDatabaseName这个参数为例,从builder.onCreate方法中找到这段代码:

if (mDatabaseName != null) {
    configuration.mDatabaseName = mDatabaseName;
} else {
    configuration.mDatabaseName = getMetaDataDatabaseNameOrDefault();
}

如果mDatabaseName没有设置,就调用getMetaDataDatabaseNameOrDefault去获取,这个函数最后实际调用的是ReflectionUtils.getMetaData(mContext, AA_DB_NAME);

public static <T> T getMetaData(Context context, String name) {
    try {
        final ApplicationInfo ai = context.getPackageManager(). getApplicationInfo(context.getPackageName(),
                 PackageManager.GET_META_DATA);

        if (ai.metaData != null) {
            return (T) ai.metaData.get(name);
        }
    }
    catch (Exception e) {
        Log.w("Couldn't find meta-data: " + name);
    }

    return null;
}

实际上我们拿得时ApplicationInfo中得meta-data,然后从meta-data中提取AA_DB_NAME的值,至此我们就明白了为什么要在AndroidManifest文件的Application标签中定义meta-data,

<meta-data android:name="AA_DB_NAME" android:value="Pickrand.db" />
<meta-data android:name="AA_DB_VERSION" android:value="5" />

类似的,databaseVersion,以及model 以及TypeSerializer 的定义都在meta-data元素中。

加载Model和Serializer

其实Model不一定要声明在meta-data中,因为ModelInfo这个类在构造对象时,会去搜索当前包src目录下所有class对象,只要继承了Model了类,都会被初始化为tableInfo,然而需要注意的是只要meta-data中声明了一个Model类,所有的Model类和TypeSerializer类都必须声明在这里,因为这个时候就不会去扫描。

好了,到这里,关于ActiveAndroid初始化数据库以及创建数据库表的操作已经基本完成,为了大家更方便的梳理,附上一张流程图:
这里写图片描述

这是activeAndroid中最基本也是最重要的一部。理解了这些,相信大家对ActiveAndroid的基本实现原理,以及注解在ActiveAndroid中的通途有了一个更深的体会,随后的章节中我会再讲解一些关于ActiveAndroid的东西,但更多的只是跟数据库的操作相关,感谢大家耐心的看到这里,bye-bye,要去洗澡了,哈^_^

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值