Android 輕量級ORM數據庫開源框架ActiveAndroid 源碼分析

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

關於他的詳細介紹和使用步驟 能夠看下面兩篇文章:android

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

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

請確保你在閱讀本文下面的內容以前 熟讀上面的2篇文章。我不會再講一遍如何使用這個框架。sql

另外因爲這個項目的做者已經多時不更新,因此在android studio 裏直接引用的話會有些麻煩數據庫

請確保使用個人代碼 保證你能夠正常引用並使用這個框架。json

 1 repositories {
 2     mavenCentral()
 3     maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
 4 }
 5 
 6 
 7 dependencies {
 8     compile fileTree(dir: 'libs', include: ['*.jar'])
 9     compile 'com.android.support:appcompat-v7:22.2.1'
10     compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
11     //compile ':ActiveAndroid'
12 }

而後咱們來剖析他的源碼,看看他的工做機理是什麼。promise

首先看數據庫是如何建立的?app

 1 public static void initialize(Context context) {
 2         initialize(new Configuration.Builder(context).create());
 3     }
 4 
 5     public static void initialize(Configuration configuration) {
 6         initialize(configuration, true);
 7     }
 8 
 9     public static void initialize(Context context, boolean loggingEnabled) {
10         initialize(new Configuration.Builder(context).create(), loggingEnabled);
11     }
12 
13     public static void initialize(Configuration configuration, boolean loggingEnabled) {
14         // Set logging enabled first
15         setLoggingEnabled(loggingEnabled);
16         Cache.initialize(configuration);
17     }

 

 

看第10行,這個地方有一個Configuration的類 還有個create方法。框架

咱們貼如下create方法

 1   public Configuration create() {
 2             Configuration configuration = new Configuration(mContext);
 3             configuration.mCacheSize = mCacheSize;
 4 
 5             // Get database name from meta-data
 6             if (mDatabaseName != null) {
 7                 configuration.mDatabaseName = mDatabaseName;
 8             } else {
 9                 configuration.mDatabaseName = getMetaDataDatabaseNameOrDefault();
10             }
12 
13             // Get database version from meta-data
14             if (mDatabaseVersion != null) {
15                 configuration.mDatabaseVersion = mDatabaseVersion;
16             } else {
17                 configuration.mDatabaseVersion = getMetaDataDatabaseVersionOrDefault();
18             }
20 
21             // Get SQL parser from meta-data
22             if (mSqlParser != null) {
23                 configuration.mSqlParser = mSqlParser;
24             } else {
25                 configuration.mSqlParser = getMetaDataSqlParserOrDefault();
26             }
27 
28             // Get model classes from meta-data
29             if (mModelClasses != null) {
30                 configuration.mModelClasses = mModelClasses;
31             } else {
32                 final String modelList = ReflectionUtils.getMetaData(mContext, AA_MODELS);
34                 if (modelList != null) {
35                     configuration.mModelClasses = loadModelList(modelList.split(","));
36                 }
37             }
38 
39             // Get type serializer classes from meta-data
40             if (mTypeSerializers != null) {
41                 configuration.mTypeSerializers = mTypeSerializers;
42             } else {
43                 final String serializerList = ReflectionUtils.getMetaData(mContext, AA_SERIALIZERS);
44                 if (serializerList != null) {
45                     configuration.mTypeSerializers = loadSerializerList(serializerList.split(","));
46                 }
47             }
48 
49             return configuration;
50         }

因此就能看出來 這個create方法 就是去讀取咱們的配置文件裏的內容的。好比

但實際上我的認爲這樣作並非特別好的方案,好比AA_MODELS,若是你的app 須要的表比較多,那這裏android:value那邊裏面由於要寫model的全名就是包名+類名

這麼作會致使這個value裏的字符串很是的長,並且難以維護,比較好的寫法 我認爲是要把這個配置文件單獨放在assets目錄下面 新建一個xml文件,去讀取比較好。

有興趣的讀者能夠嘗試修改一下這邊的邏輯。

當咱們讀取結束配置文件之後 咱們會繼續調用cache.init這個方法

 1  public static synchronized void initialize(Configuration configuration) {
 2         if (sIsInitialized) {
 3             return;
 4         }
 5         sContext = configuration.getContext();
 6         sModelInfo = new ModelInfo(configuration);
 7         sDatabaseHelper = new DatabaseHelper(configuration);
 8 
 9         // TODO: It would be nice to override sizeOf here and calculate the memory
10         // actually used, however at this point it seems like the reflection
11         // required would be too costly to be of any benefit. We'll just set a max
12         // object size instead.
13         sEntities = new LruCache<String, Model>(configuration.getCacheSize());
14 
15         openDatabase();
16 
17         sIsInitialized = true;
18 
19         Log.v("ActiveAndroid initialized successfully.");
20     }

在這個裏面 咱們找到了databaseHelper。看來創建數據庫和表的 關鍵部分就在這裏面了。並且要注意databasehelper是用config 咱們讀取出來的那些配置數據來初始化的。

1  @Override
2     public void onCreate(SQLiteDatabase db) {
3         executePragmas(db);
4         executeCreate(db);
5         executeMigrations(db, -1, db.getVersion());
6         executeCreateIndex(db);
7     }
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爲結尾。前綴能夠用阿拉伯數字

還表示腳本的版本號。

咱們來走一遍數據庫升級時的腳本流程。

 1 private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) {
 2         boolean migrationExecuted = false;
 3         try {
 4             final List<String> files = Arrays.asList(Cache.getContext().getAssets().list(MIGRATION_PATH));
 5             Collections.sort(files, new NaturalOrderComparator());
 6 
 7             db.beginTransaction();
 8             try {
 9                 for (String file : files) {
10                     try {
11                         final int version = Integer.valueOf(file.replace(".sql", ""));
12                         if (version > oldVersion && version <= newVersion) {
13                             executeSqlScript(db, file);
14                             migrationExecuted = true;
15 
16                             Log.i(file + " executed succesfully.");
17                         }
18                     } catch (NumberFormatException e) {
19                         Log.w("Skipping invalidly named file: " + file, e);
20                     }
21                 }
22                 db.setTransactionSuccessful();
23             } finally {
24                 db.endTransaction();
25             }
26         } catch (IOException e) {
27             Log.e("Failed to execute migrations.", e);
28         }
29 
30         return migrationExecuted;
31     }

4行 拿到assets下mig路徑下的全部文件 而且排序之後進行遍歷 符合條件的會在13行執行腳本。

12行就是判斷 是否須要進行數據庫升級腳本操做的判斷條件。能夠本身體會下爲何要這麼寫,

固然我我的意見是你在使用此框架的時候 關於數據庫升級的部分 能夠自定義。尤爲是在作一些

複雜的電商,聊天之類的app時候,表關係複雜,而且時有升級。

到目前爲止,咱們已通過了一遍此框架 創建數據庫 創建 升級表的過程。

而後咱們來看一下 這個框架是如何執行crud 操做的。咱們只分析add和selet操做。其餘操做留給讀者本身分析。

通常咱們創建的model都以下

1     public Model() {
2         mTableInfo = Cache.getTableInfo(getClass());
3         idName = mTableInfo.getIdName();
4     }

 

實際上咱們的model 在初始化的時候 會把本身的class 在構造函數裏面 傳到cache裏,這樣咱們在model裏面就能拿到咱們對應的表的信息了。

這個model的構造函數就是作這件事的。 那拿到這個表信息之後 進行save操做 就是很是簡單的了。

 1  public final Long save() {
 2         final SQLiteDatabase db = Cache.openDatabase();
 3         final ContentValues values = new ContentValues();
 4 
 5         for (Field field : mTableInfo.getFields()) {
 6             final String fieldName = mTableInfo.getColumnName(field);
 7             Class<?> fieldType = field.getType();
 8             field.setAccessible(true);
 9 
10             try {
11                 Object value = field.get(this);
12 
13                 if (value != null) {
14                     final TypeSerializer typeSerializer = Cache.getParserForType(fieldType);
15                     if (typeSerializer != null) {
16                         // serialize data
17                         value = typeSerializer.serialize(value);
18                         // set new object type
19                         if (value != null) {
20                             fieldType = value.getClass();
21                             // check that the serializer returned what it promised
22                             if (!fieldType.equals(typeSerializer.getSerializedType())) {
23                                 Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s",
24                                         typeSerializer.getSerializedType(), fieldType));
25                             }
26                         }
27                     }
28                 }
29 
30                 // TODO: Find a smarter way to do this? This if block is necessary because we
31                 // can't know the type until runtime.
32                 if (value == null) {
33                     values.putNull(fieldName);
34                 } else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) {
35                     values.put(fieldName, (Byte) value);
36                 } else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) {
37                     values.put(fieldName, (Short) value);
38                 } else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
39                     values.put(fieldName, (Integer) value);
40                 } else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
41                     values.put(fieldName, (Long) value);
42                 } else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) {
43                     values.put(fieldName, (Float) value);
44                 } else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
45                     values.put(fieldName, (Double) value);
46                 } else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
47                     values.put(fieldName, (Boolean) value);
48                 } else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
49                     values.put(fieldName, value.toString());
50                 } else if (fieldType.equals(String.class)) {
51                     values.put(fieldName, value.toString());
52                 } else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) {
53                     values.put(fieldName, (byte[]) value);
54                 } else if (ReflectionUtils.isModel(fieldType)) {
55                     values.put(fieldName, ((Model) value).getId());
56                 } else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
57                     values.put(fieldName, ((Enum<?>) value).name());
58                 }
59             } catch (IllegalArgumentException e) {
60                 Log.e(e.getClass().getName(), e);
61             } catch (IllegalAccessException e) {
62                 Log.e(e.getClass().getName(), e);
63             }
64         }
65 
66         if (mId == null) {
67             mId = db.insert(mTableInfo.getTableName(), null, values);
68         } else {
69             db.update(mTableInfo.getTableName(), values, idName + "=" + mId, null);
70         }
71 
72         Cache.getContext().getContentResolver()
73                 .notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null);
74         return mId;
75     }

思路就是利用反射 來拿到值之後 進行數據庫操做,10-29行有一段關於序列化的代碼 我放在後面再講。先繼續看select操做吧。

好比說 咱們通常的select操做 都是這樣的

來看一下select的from方法。

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

原來是利用咱們傳進去的class名字來構造真正的查詢對象。

 1 public final class From implements Sqlable {
 2     private Sqlable mQueryBase;
 3 
 4     private Class<? extends Model> mType;
 5     private String mAlias;
 6     private List<Join> mJoins;
 7     private final StringBuilder mWhere = new StringBuilder();
 8     private String mGroupBy;
 9     private String mHaving;
10     private String mOrderBy;
11     private String mLimit;
12     private String mOffset;
13 
14     private List<Object> mArguments;
15 
16     public From(Class<? extends Model> table, Sqlable queryBase) {
17         mType = table;
18         mJoins = new ArrayList<Join>();
19         mQueryBase = queryBase;
20 
21         mJoins = new ArrayList<Join>();
22         mArguments = new ArrayList<Object>();
23     }

最終的excute方法

 1 public <T extends Model> List<T> execute() {
 2         if (mQueryBase instanceof Select) {
 3             return SQLiteUtils.rawQuery(mType, toSql(), getArguments());
 4             
 5         } else {
 6             SQLiteUtils.execSql(toSql(), getArguments());
 7             Cache.getContext().getContentResolver().notifyChange(ContentProvider.createUri(mType, null), null);
 8             return null;
 9             
10         }
11     }

query這邊的代碼 我認爲寫的很是巧妙清晰,篇幅所限 我沒法講的太細,大家能夠觀察下代碼結構 本身回去多翻翻

回到 咱們剛纔講save操做時候 看到有幾行序列化的代碼,實際上 那邊的代碼 是用來創建 對象和 數據庫原始數據關係的。

好比咱們有個學生model,咱們但願有個字段來存放這個學生的手機 pad以及電腦的品牌,固然爲了表示簡潔,咱們這個字段

能夠用一段json String來表示。可是咱們在model裏又不但願直接用string,咱們但願用一個對象來表示。就能夠用

「序列化」 和「反序列化」 來表示他們之間的關係。

咱們首先來創建一個 學生物品類

 1 package com.example.administrator.myapplication5;
 2 
 3 import org.json.JSONException;
 4 import org.json.JSONObject;
 5 
 6 /**
 7  * Created by Administrator on 2015/8/28.
 8  */
 9 public class PersonalItems {
10     private String computerBand;
11     private String phoneBand;
12     private String padBand;
13 
14     public String getComputerBand() {
15         return computerBand;
16     }
17 
18     public void setComputerBand(String computerBand) {
19         this.computerBand = computerBand;
20     }
21 
22     public String getPhoneBand() {
23         return phoneBand;
24     }
25 
26     public void setPhoneBand(String phoneBand) {
27         this.phoneBand = phoneBand;
28     }
29 
30     public String getPadBand() {
31         return padBand;
32     }
33 
34     public void setPadBand(String padBand) {
35         this.padBand = padBand;
36     }
37 
38     @Override
39     public String toString() {
40 
41         JSONObject jsonObject = new JSONObject();
42         try {
43             jsonObject.put("computerBand", computerBand);
44             jsonObject.put("padBand", padBand);
45             jsonObject.put("phoneBand", phoneBand);
46 
47         } catch (JSONException e) {
48             e.printStackTrace();
49         }
50 
51 
52         return jsonObject.toString();
53     }
54 }

而後咱們在model裏 也創建這個對象。

 1 package com.example.administrator.myapplication5;
 2 
 3 import com.activeandroid.Model;
 4 import com.activeandroid.annotation.Column;
 5 import com.activeandroid.annotation.Table;
 6 
 7 import java.util.Date;
 8 
 9 /**
10  * Created by Administrator on 2015/8/27.
11  */
12 @Table(name = "Student")
13 public class Student extends Model {
14     @Column(name = "Name")
15     public String name;
16     @Column(name = "No")
17     public String no;
18     @Column(name = "sex")
19     public int sex;
20     @Column(name = "date")
21     public Date date;
22 
23     @Column(name = "personalItems")
24     public PersonalItems personalItems;
25 
26     @Override
27     public String toString() {
28         return "Student{" +
29                 "name='" + name + '\'' +
30                 ", no='" + no + '\'' +
31                 ", sex=" + sex +
32                 ", date=" + date +
33                 ", personalItems=" + personalItems +
34                 '}';
35     }
36 }

而後咱們來創建二者之間的關係類!這個是核心。

 1 package com.activeandroid.serializer;
 2 
 3 import com.example.administrator.myapplication5.PersonalItems;
 4 
 5 import org.json.JSONException;
 6 import org.json.JSONObject;
 7 
 8 /**
 9  * Created by Administrator on 2015/8/28.
10  */
11 public class PersonalItemsSerializer extends TypeSerializer {
12 
13     @Override
14     public Class<?> getDeserializedType() {
15         return PersonalItems.class;
16     }
17 
18     @Override
19     public Class<?> getSerializedType() {
20         return String.class;
21     }
22 
23     @Override
24     public String serialize(Object data) {
25         if (data == null) {
26             return null;
27         }
28         String str = data.toString();
29 
30         return str;
31     }
32 
33     @Override
34     public PersonalItems deserialize(Object data) {
35         if (data == null) {
36             return null;
37         }
38         PersonalItems personalItems = new PersonalItems();
39         try {
40             JSONObject dataJson = new JSONObject(data.toString());
41             personalItems.setComputerBand(dataJson.getString("computerBand"));
42             personalItems.setPadBand(dataJson.getString("padBand"));
43             personalItems.setPhoneBand(dataJson.getString("phoneBand"));
44         } catch (JSONException e) {
45             e.printStackTrace();
46         }
47         return personalItems;
48     }
49 }

代碼仍是很清楚的,複寫的幾個方法就是要求你 寫一下序列和反序列化的過程罷了。(對象----數據庫字段 的關係 就是在這創建的)

固然你寫完了 還要在配置文件裏配置一下。否則系統會找不到這個序列化類的。這個過程我就不分析了,留着你們有興趣能夠本身寫一個。

咱們主要看一下 那個save操做裏的過程

 1 for (Field field : mTableInfo.getFields()) {
 2             final String fieldName = mTableInfo.getColumnName(field);
 3             Class<?> fieldType = field.getType();
 4             field.setAccessible(true);
 5 
 6             try {
 7                 Object value = field.get(this);
 8 
 9                 if (value != null) {
10                     final TypeSerializer typeSerializer = Cache.getParserForType(fieldType);
11                     if (typeSerializer != null) {
12                         // serialize data
13                         value = typeSerializer.serialize(value);
14                         // set new object type
15                         if (value != null) {
16                             fieldType = value.getClass();
17                             // check that the serializer returned what it promised
18                             if (!fieldType.equals(typeSerializer.getSerializedType())) {
19                                 Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s",
20                                         typeSerializer.getSerializedType(), fieldType));
21                             }
22                         }
23                     }
24                 }

 

第三行 咱們拿到了 fieldType 注意此時他的值 實際上 就是咱們對象所屬的類的名字!

第10行  去cache裏 找對應關係類,看看有沒有對應的這個類的 序列化工做器。

第13行 代表若是找到這個序列化工做器的話  就用他把這個value的值轉換成 數據庫能接受的值!

第15行  代表若是序列化成功的話,fieldType的值也要改爲 數據庫也就是sqlite能接受的類型!

而後 才能用下面的代碼 進行插入操做!

 1 if (value == null) {
 2                     values.putNull(fieldName);
 3                 } else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) {
 4                     values.put(fieldName, (Byte) value);
 5                 } else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) {
 6                     values.put(fieldName, (Short) value);
 7                 } else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
 8                     values.put(fieldName, (Integer) value);
 9                 } else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
10                     values.put(fieldName, (Long) value);
11                 } else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) {
12                     values.put(fieldName, (Float) value);
13                 } else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
14                     values.put(fieldName, (Double) value);
15                 } else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
16                     values.put(fieldName, (Boolean) value);
17                 } else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
18                     values.put(fieldName, value.toString());
19                 } else if (fieldType.equals(String.class)) {
20                     values.put(fieldName, value.toString());
21                 } else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) {
22                     values.put(fieldName, (byte[]) value);
23                 } else if (ReflectionUtils.isModel(fieldType)) {
24                     values.put(fieldName, ((Model) value).getId());
25                 } else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
26                     values.put(fieldName, ((Enum<?>) value).name());
27                 }

 

至於selcet操做時 反序列化的操做 就留給讀者本身分析了。

其實這個框架自己已經提供了很多好用的工做器給咱們使用了(第四個是我剛纔定義的 請你們忽略掉 哈哈)。

 

最後因爲這個框架大量使用的註解 反射 致使效率確定比不上本身直接寫sql,不過能夠接受,儘管如此 仍是請儘可能在子線程裏去操做數據庫!重要的話 只說一遍!

相關文章
相關標籤/搜索