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

来源:互联网 发布:淘宝偷图技巧 编辑:程序博客网 时间:2024/05/18 01:13

关于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函数:

@Overridepublic 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