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,要去洗澡了,哈^_^
- Android开发者必知的Java知识(三) 结合注解分析ActiveAndroid的实现
- Android开发者必知的java知识(二)Annotation
- Android开发者必知的Java知识(一):Java反射机制
- Android开发者必知的Java知识(四):Java并发编程
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android 开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- Android开发者必知的开发资源
- navigator.userAgent.indexOf来判断浏览器类型
- 25匹马赛跑问题
- Android的px、dp和sp等单位的区别详解
- POJ 3159 Candies(差分约束)
- HDU 4994-Revenge of Nim(博弈论)
- Android开发者必知的Java知识(三) 结合注解分析ActiveAndroid的实现
- python3 导入模块
- JS快捷键
- PRML Charpter 2 Probability Distribution 2.3.5 Sequential estimation讲义摘要
- 解决:AMQ应用部署到weblogic服务器上出现“找不到匹配的方法setLifo"
- CUDA: 使用shared memory
- poj3190
- 【蓝桥杯训练】------2n皇后问题
- 有没有办法获取最近操作的联系人