关于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,要去洗澡了,哈^_^