Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

时间:2021-10-18 01:54:38

ActiveAndroid 项目地址在https://github.com/pardom/ActiveAndroid

关于他的详细介绍和使用步骤 可以看下面两篇文章:

https://github.com/pardom/ActiveAndroid/wiki

http://www.future-processing.pl/blog/persist-your-data-activeandroid-and-parse/

请确保你在阅读本文下面的内容之前 熟读上面的2篇文章。我不会再讲一遍如何使用这个框架。

另外由于这个项目的作者已经多时不更新,所以在android studio 里直接引用的话会有些麻烦

请确保使用我的代码 保证你可以正常引用并使用这个框架。

 repositories {
mavenCentral()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
//compile ':ActiveAndroid'
}

然后我们来剖析他的源码,看看他的工作机理是什么。

首先看数据库是如何创建的?

 public static void initialize(Context context) {
initialize(new Configuration.Builder(context).create());
} public static void initialize(Configuration configuration) {
initialize(configuration, true);
} public static void initialize(Context context, boolean loggingEnabled) {
initialize(new Configuration.Builder(context).create(), loggingEnabled);
} public static void initialize(Configuration configuration, boolean loggingEnabled) {
// Set logging enabled first
setLoggingEnabled(loggingEnabled);
Cache.initialize(configuration);
}

看第10行,这个地方有一个Configuration的类 还有个create方法。

我们贴以下create方法

   public Configuration create() {
Configuration configuration = new Configuration(mContext);
configuration.mCacheSize = mCacheSize; // Get database name from meta-data
if (mDatabaseName != null) {
configuration.mDatabaseName = mDatabaseName;
} else {
configuration.mDatabaseName = getMetaDataDatabaseNameOrDefault();
} // Get database version from meta-data
if (mDatabaseVersion != null) {
configuration.mDatabaseVersion = mDatabaseVersion;
} else {
configuration.mDatabaseVersion = getMetaDataDatabaseVersionOrDefault();
} // Get SQL parser from meta-data
if (mSqlParser != null) {
configuration.mSqlParser = mSqlParser;
} else {
configuration.mSqlParser = getMetaDataSqlParserOrDefault();
} // Get model classes from meta-data
if (mModelClasses != null) {
configuration.mModelClasses = mModelClasses;
} else {
final String modelList = ReflectionUtils.getMetaData(mContext, AA_MODELS);
if (modelList != null) {
configuration.mModelClasses = loadModelList(modelList.split(","));
}
} // Get type serializer classes from meta-data
if (mTypeSerializers != null) {
configuration.mTypeSerializers = mTypeSerializers;
} else {
final String serializerList = ReflectionUtils.getMetaData(mContext, AA_SERIALIZERS);
if (serializerList != null) {
configuration.mTypeSerializers = loadSerializerList(serializerList.split(","));
}
} return configuration;
}

所以就能看出来 这个create方法 就是去读取我们的配置文件里的内容的。比如

Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

但实际上个人认为这样做并不是特别好的方案,比如AA_MODELS,如果你的app 需要的表比较多,那这里android:value那边里面因为要写model的全名就是包名+类名

这么做会导致这个value里的字符串非常的长,而且难以维护,比较好的写法 我认为是要把这个配置文件单独放在assets目录下面 新建一个xml文件,去读取比较好。

有兴趣的读者可以尝试修改一下这边的逻辑。

当我们读取结束配置文件以后 我们会继续调用cache.init这个方法

  public static synchronized void initialize(Configuration configuration) {
if (sIsInitialized) {
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.");
}

在这个里面 我们找到了databaseHelper。看来建立数据库和表的 关键部分就在这里面了。而且要注意databasehelper是用config 我们读取出来的那些配置数据来初始化的。

  @Override
public void onCreate(SQLiteDatabase db) {
executePragmas(db);
executeCreate(db);
executeMigrations(db, -1, db.getVersion());
executeCreateIndex(db);
}
private void executeCreate(SQLiteDatabase db) {
db.beginTransaction();
try {
for (TableInfo tableInfo : Cache.getTableInfos()) {
String sql = SQLiteUtils.createTableDefinition(tableInfo);
db.execSQL(sql);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}

实际上 在这个地方就已经把我们取得的config数据 遍历以后 组成我们的sql语句 去初始化我们的数据库以及表了。

除此之外,我们当然也可以用sql脚本的方式来建表,尤其是在数据库升级的时候,直接导入脚本来升级也是非常简便的。

此框架,也要求我们将sql脚本放在app assets migrations 目录下.此脚本必须.sql为结尾。前缀可以用阿拉伯数字

还表示脚本的版本号。

我们来走一遍数据库升级时的脚本流程。

 private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean migrationExecuted = false;
try {
final List<String> files = Arrays.asList(Cache.getContext().getAssets().list(MIGRATION_PATH));
Collections.sort(files, new NaturalOrderComparator()); db.beginTransaction();
try {
for (String file : files) {
try {
final int version = Integer.valueOf(file.replace(".sql", ""));
if (version > oldVersion && version <= newVersion) {
executeSqlScript(db, file);
migrationExecuted = true; Log.i(file + " executed succesfully.");
}
} catch (NumberFormatException e) {
Log.w("Skipping invalidly named file: " + file, e);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (IOException e) {
Log.e("Failed to execute migrations.", e);
} return migrationExecuted;
}

4行 拿到assets下mig路径下的所有文件 并且排序以后进行遍历 符合条件的会在13行执行脚本。

12行就是判断 是否需要进行数据库升级脚本操作的判断条件。可以自己体会下为什么要这么写,

当然我个人意见是你在使用此框架的时候 关于数据库升级的部分 可以自定义。尤其是在做一些

复杂的电商,聊天之类的app时候,表关系复杂,并且时有升级。

到目前为止,我们已经过了一遍此框架 建立数据库 建立 升级表的过程。

然后我们来看一下 这个框架是如何执行crud 操作的。我们只分析add和selet操作。其他操作留给读者自己分析。

一般我们建立的model都如下

Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

     public Model() {
mTableInfo = Cache.getTableInfo(getClass());
idName = mTableInfo.getIdName();
}

实际上我们的model 在初始化的时候 会把自己的class 在构造函数里面 传到cache里,这样我们在model里面就能拿到我们对应的表的信息了。

这个model的构造函数就是做这件事的。 那拿到这个表信息以后 进行save操作 就是非常简单的了。

  public final Long save() {
final SQLiteDatabase db = Cache.openDatabase();
final ContentValues values = new ContentValues(); for (Field field : mTableInfo.getFields()) {
final String fieldName = mTableInfo.getColumnName(field);
Class<?> fieldType = field.getType();
field.setAccessible(true); try {
Object value = field.get(this); if (value != null) {
final TypeSerializer typeSerializer = Cache.getParserForType(fieldType);
if (typeSerializer != null) {
// serialize data
value = typeSerializer.serialize(value);
// set new object type
if (value != null) {
fieldType = value.getClass();
// check that the serializer returned what it promised
if (!fieldType.equals(typeSerializer.getSerializedType())) {
Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s",
typeSerializer.getSerializedType(), fieldType));
}
}
}
} // TODO: Find a smarter way to do this? This if block is necessary because we
// can't know the type until runtime.
if (value == null) {
values.putNull(fieldName);
} else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) {
values.put(fieldName, (Byte) value);
} else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) {
values.put(fieldName, (Short) value);
} else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
values.put(fieldName, (Integer) value);
} else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
values.put(fieldName, (Long) value);
} else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) {
values.put(fieldName, (Float) value);
} else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
values.put(fieldName, (Double) value);
} else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
values.put(fieldName, (Boolean) value);
} else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
values.put(fieldName, value.toString());
} else if (fieldType.equals(String.class)) {
values.put(fieldName, value.toString());
} else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) {
values.put(fieldName, (byte[]) value);
} else if (ReflectionUtils.isModel(fieldType)) {
values.put(fieldName, ((Model) value).getId());
} else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
values.put(fieldName, ((Enum<?>) value).name());
}
} catch (IllegalArgumentException e) {
Log.e(e.getClass().getName(), e);
} catch (IllegalAccessException e) {
Log.e(e.getClass().getName(), e);
}
} if (mId == null) {
mId = db.insert(mTableInfo.getTableName(), null, values);
} else {
db.update(mTableInfo.getTableName(), values, idName + "=" + mId, null);
} Cache.getContext().getContentResolver()
.notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null);
return mId;
}

思路就是利用反射 来拿到值以后 进行数据库操作,10-29行有一段关于序列化的代码 我放在后面再讲。先继续看select操作吧。

比如说 我们一般的select操作 都是这样的

Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

来看一下select的from方法。

public From from(Class<? extends Model> table) {
return new From(table, this);
}

原来是利用我们传进去的class名字来构造真正的查询对象。

 public final class From implements Sqlable {
private Sqlable mQueryBase; private Class<? extends Model> mType;
private String mAlias;
private List<Join> mJoins;
private final StringBuilder mWhere = new StringBuilder();
private String mGroupBy;
private String mHaving;
private String mOrderBy;
private String mLimit;
private String mOffset; private List<Object> mArguments; public From(Class<? extends Model> table, Sqlable queryBase) {
mType = table;
mJoins = new ArrayList<Join>();
mQueryBase = queryBase; mJoins = new ArrayList<Join>();
mArguments = new ArrayList<Object>();
}

最终的excute方法

 public <T extends Model> List<T> execute() {
if (mQueryBase instanceof Select) {
return SQLiteUtils.rawQuery(mType, toSql(), getArguments()); } else {
SQLiteUtils.execSql(toSql(), getArguments());
Cache.getContext().getContentResolver().notifyChange(ContentProvider.createUri(mType, null), null);
return null; }
}

query这边的代码 我认为写的非常巧妙清晰,篇幅所限 我无法讲的太细,你们可以观察下代码结构 自己回去多翻翻

Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

回到 我们刚才讲save操作时候 看到有几行序列化的代码,实际上 那边的代码 是用来建立 对象和 数据库原始数据关系的。

比如我们有个学生model,我们希望有个字段来存放这个学生的手机 pad以及电脑的品牌,当然为了表示简洁,我们这个字段

可以用一段json String来表示。但是我们在model里又不希望直接用string,我们希望用一个对象来表示。就可以用

“序列化” 和“反序列化” 来表示他们之间的关系。

我们首先来建立一个 学生物品类

 package com.example.administrator.myapplication5;

 import org.json.JSONException;
import org.json.JSONObject; /**
* Created by Administrator on 2015/8/28.
*/
public class PersonalItems {
private String computerBand;
private String phoneBand;
private String padBand; public String getComputerBand() {
return computerBand;
} public void setComputerBand(String computerBand) {
this.computerBand = computerBand;
} public String getPhoneBand() {
return phoneBand;
} public void setPhoneBand(String phoneBand) {
this.phoneBand = phoneBand;
} public String getPadBand() {
return padBand;
} public void setPadBand(String padBand) {
this.padBand = padBand;
} @Override
public String toString() { JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("computerBand", computerBand);
jsonObject.put("padBand", padBand);
jsonObject.put("phoneBand", phoneBand); } catch (JSONException e) {
e.printStackTrace();
} return jsonObject.toString();
}
}

然后我们在model里 也建立这个对象。

 package com.example.administrator.myapplication5;

 import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table; import java.util.Date; /**
* Created by Administrator on 2015/8/27.
*/
@Table(name = "Student")
public class Student extends Model {
@Column(name = "Name")
public String name;
@Column(name = "No")
public String no;
@Column(name = "sex")
public int sex;
@Column(name = "date")
public Date date; @Column(name = "personalItems")
public PersonalItems personalItems; @Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", no='" + no + '\'' +
", sex=" + sex +
", date=" + date +
", personalItems=" + personalItems +
'}';
}
}

然后我们来建立两者之间的关系类!这个是核心。

 package com.activeandroid.serializer;

 import com.example.administrator.myapplication5.PersonalItems;

 import org.json.JSONException;
import org.json.JSONObject; /**
* Created by Administrator on 2015/8/28.
*/
public class PersonalItemsSerializer extends TypeSerializer { @Override
public Class<?> getDeserializedType() {
return PersonalItems.class;
} @Override
public Class<?> getSerializedType() {
return String.class;
} @Override
public String serialize(Object data) {
if (data == null) {
return null;
}
String str = data.toString(); return str;
} @Override
public PersonalItems deserialize(Object data) {
if (data == null) {
return null;
}
PersonalItems personalItems = new PersonalItems();
try {
JSONObject dataJson = new JSONObject(data.toString());
personalItems.setComputerBand(dataJson.getString("computerBand"));
personalItems.setPadBand(dataJson.getString("padBand"));
personalItems.setPhoneBand(dataJson.getString("phoneBand"));
} catch (JSONException e) {
e.printStackTrace();
}
return personalItems;
}
}

代码还是很清楚的,复写的几个方法就是要求你 写一下序列和反序列化的过程罢了。(对象----数据库字段 的关系 就是在这建立的)

当然你写完了 还要在配置文件里配置一下。不然系统会找不到这个序列化类的。这个过程我就不分析了,留着大家有兴趣可以自己写一个。

我们主要看一下 那个save操作里的过程

 for (Field field : mTableInfo.getFields()) {
final String fieldName = mTableInfo.getColumnName(field);
Class<?> fieldType = field.getType();
field.setAccessible(true); try {
Object value = field.get(this); if (value != null) {
final TypeSerializer typeSerializer = Cache.getParserForType(fieldType);
if (typeSerializer != null) {
// serialize data
value = typeSerializer.serialize(value);
// set new object type
if (value != null) {
fieldType = value.getClass();
// check that the serializer returned what it promised
if (!fieldType.equals(typeSerializer.getSerializedType())) {
Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s",
typeSerializer.getSerializedType(), fieldType));
}
}
}
}

第三行 我们拿到了 fieldType 注意此时他的值 实际上 就是我们对象所属的类的名字!

第10行  去cache里 找对应关系类,看看有没有对应的这个类的 序列化工作器。

第13行 表明如果找到这个序列化工作器的话  就用他把这个value的值转换成 数据库能接受的值!

第15行  表明如果序列化成功的话,fieldType的值也要改成 数据库也就是sqlite能接受的类型!

然后 才能用下面的代码 进行插入操作!

 if (value == null) {
values.putNull(fieldName);
} else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) {
values.put(fieldName, (Byte) value);
} else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) {
values.put(fieldName, (Short) value);
} else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
values.put(fieldName, (Integer) value);
} else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
values.put(fieldName, (Long) value);
} else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) {
values.put(fieldName, (Float) value);
} else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
values.put(fieldName, (Double) value);
} else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
values.put(fieldName, (Boolean) value);
} else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
values.put(fieldName, value.toString());
} else if (fieldType.equals(String.class)) {
values.put(fieldName, value.toString());
} else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) {
values.put(fieldName, (byte[]) value);
} else if (ReflectionUtils.isModel(fieldType)) {
values.put(fieldName, ((Model) value).getId());
} else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
values.put(fieldName, ((Enum<?>) value).name());
}

至于selcet操作时 反序列化的操作 就留给读者自己分析了。

其实这个框架本身已经提供了不少好用的工作器给我们使用了(第四个是我刚才定义的 请大家忽略掉 哈哈)。

Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

最后由于这个框架大量使用的注解 反射 导致效率肯定比不上自己直接写sql,不过可以接受,尽管如此 还是请尽量在子线程里去操作数据库!重要的话 只说一遍!