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,不過能夠接受,儘管如此 仍是請儘可能在子線程裏去操做數據庫!重要的話 只說一遍!