Android學習——LitePal源碼分析


原創技術博客,請認准Azzssss的原文http://www.cnblogs.com/Azzssss/p/4147704.html

 

這兩天項目終於上線了,松了一口氣,雖然還是很不穩定,見一步走一步吧。反正總算能抽出時間來寫博客了。在項目中用到了LitePal,LitePal是什么鬼東西呢?它的Github主頁是什么介紹的:“LitePal是一個開源的android庫,允許開發者極其方便地去使用sqlite數據庫。通過它,你甚至可以不寫一行SQL語句來完成大部分的數據庫操作,包括創建和更新表,crud操作,聚合函數等等。LitePal的配置同樣很簡單,5分鍾就可以整合進你的項目。”

 

我第一次知道LitePal是通過郭林的博客,郭先生一連寫了八篇博客介紹LitePal。這篇博客主要是想深入了解LitePal的源碼,如果還沒使用過LitePal,建議先移步郭先生的博客,這是他關於LitePal的第一篇博客《Android數據庫高手秘籍(零)——前言》。

 

現在,先看看LitePal的save操作,太簡單了,忍不住再次稱贊一下。

public class Album extends DataSupport {

    private String name;

    private float price;

    private List<Song> songs = new ArrayList<Song>();

    // generated getters and setters.
    ...
}

Album album = new Album();
album.setName("album");
album.setPrice(10.99f);
album.save();

 

是不是很簡單?想起自己寫過的sqlite語句是不是嗷的一聲昏過去了?這里我先講一下大概的思路吧,是這樣實現的,每一個實體類都要繼承DataSupport這個類,通過反射找到實體類中所有的屬性(只能是基本數據類型或者String),通過反射將對象屬性的值put到ContentValues里面,然后就調用原生SQLiteDatabase的方法insert進去。當然,這里面還涉及到關聯數據的東西,這個有點復雜,稍后分析。

 

我們先來看看LitePal的增刪改查操作的類圖

 可以看出,主要就是SaveHandler,DeleteHandler,UpdateHandler,QueryHandler,另外還有一個AssociationsAnalyzer,這是一個關聯的分析器,分析關聯關系。LitePal可以讓我們很輕松地處理關聯關系,就是通過這個東西。

 

下面我們看看,這個save操作究竟進行了什么,走起~

我們點進去DataSupport的save方法,發現是這樣的(我是不是應該畫個類圖啊)


public
synchronized boolean save() { //調用了saveThrows()方法 代碼上的注釋是這么說的,如果這個是一條新的記錄,就會在數據庫中create,否則就更新現有的數據。 try {       //它怎么區分這條數據是否在已經在數據庫中了?答案是根據JavaBean里地id或者_id屬性(必須為int類型或者long類型),這是一個約定,如果實體類 saveThrows();  //中有這個屬性,這個id的值將會由數據庫創建並在調用save方法后自動指派給它(原理我稍后看看),並且判斷這個id屬性是否有值區分insert和update操作 return true; } catch (Exception e) { e.printStackTrace(); return false; } }

接下來我們看saveThrows()方法

public synchronized void saveThrows() {
        SQLiteDatabase db = Connector.getDatabase();      //獲取數據庫
        db.beginTransaction();                    //這個估計是開啟事務?先放着
        try {
            SaveHandler saveHandler = new SaveHandler(db);    //這是關鍵咯
            saveHandler.onSave(this);
            clearAssociatedData();                  //不知道這個是什么鬼,清理外鍵數據啥的
            db.setTransactionSuccessful();
        } catch (Exception e) {
            throw new DataSupportException(e.getMessage());
        } finally {
            db.endTransaction();
        }
    }

這里LitePal把儲存的操作封裝在SaveHandler里面了,其實增刪改查的操作還分別封裝了DeleteHandler,UpdateHandler,QueryHandler,這四個類繼承自DataHandler

SaveHandler的onSave方法是這樣的

void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException {
String className
= baseObj.getClassName();                  //獲取類名,例子里就是Album類名,其實調用的就是getClass().getName(); List<Field> supportedFields = getSupportedFields(className);          //找出這個實體類中的所有屬性,但是只支持基本數據類型和String類型  Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);      //這里涉及到關聯數據,先不理 if (!baseObj.isSaved()) {       //判斷模型已經創建與否,也就是說這條數據是否在數據庫中了,就一行return baseObjId > 0,所以說實體類的id值不要自己手動去設 analyzeAssociatedModels(baseObj, associationInfos);            //關聯數據相關,等一下再分析 doSaveAction(baseObj, supportedFields); analyzeAssociatedModels(baseObj, associationInfos);           //關聯數據相關,等一下再分析 } else { analyzeAssociatedModels(baseObj, associationInfos);            //關聯數據相關,等一下再分析 doUpdateAction(baseObj, supportedFields);                  //如果baseObjId不為空,更新 } }

先說一下doSaveAction

private void doSaveAction(DataSupport baseObj, List<Field> supportedFields)
            throws SecurityException, IllegalArgumentException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
ContentValues values
= new ContentValues(); beforeSave(baseObj, supportedFields, values);          //beforeSave 主要功能是通過反射把實體類的數據存入ContentValues,動態地獲取數據類型,並調用相                                                  //應的put方法 long id = saving(baseObj, values);                //保存數據到表中,並且返回一個long類型的id afterSave(baseObj, supportedFields, id);             //afterSave }

 

現在看看這行代碼  long id = saving(baseObj, values);  這里點進去,就發現是調用SQLiteDataBase的insert操作了,之前我們不是把通過反射把ContentValues的數據都put好了嗎,接着調用SQLiteDataBase的insert方法,這個nullColumnHack參數是為了防止插入空行,底層數據庫不允許插入一個空行

public long insert(String table, String nullColumnHack, ContentValues values) 

好了,看看afterSave()方法,插入后的操作

private void afterSave(DataSupport baseObj, List<Field> supportedFields, long id) {
        throwIfSaveFailed(id);                    //如果id為-1,拋出異常,插入數據失敗
        assignIdValue(baseObj, getIdField(supportedFields), id);    //指派id給JavaBean,就是那個Album對象
        updateAssociatedTableWithFK(baseObj);               //更新關聯的表,先不要理,關聯那塊另外說
        insertIntermediateJoinTableValue(baseObj, false);        //這個也涉及到關聯數據,多對多關系下的中間表操作,先放着
}

好像差不多了,SaveHandler里面還有update的方法沒提到,還有一小部分關於關聯的操作。先吃個夜宵再寫。。。。

 

下面我們來分析LitePal的關聯關系是怎么實現的,在onSave()里面首先有這么一個getAssociationInfo

protected Collection<AssociationsInfo> getAssociationInfo(String className) {    //通過類名獲取關聯信息
        if (mAssociationInfos == null) {
            mAssociationInfos = new HashSet<AssociationsInfo>();
        }
        mAssociationInfos.clear();
        analyzeClassFields(className, GET_ASSOCIATION_INFO_ACTION);
        return mAssociationInfos;
}

什么是關聯信息呢?其實很簡單,就是修飾符是private,以及不是基本數據類型的,主要通過這個方法判斷

private boolean isPrivateAndNonPrimitive(Field field) {    
        return Modifier.isPrivate(field.getModifiers()) && !field.getType().isPrimitive();
}

然后分別有oneToAnyConditions,以及manyToAnyConditions兩個方法來分析,這個類中的一對多和多對多關系。

oneToAnyConditions方法其實很簡單,具體思路是這樣的。首先,通過反射,獲得這個類(from class)的所有申明的字段reverseFields

,然后遍歷這些字段,通過reverse字段得到它的類名(defined class),然后根據這個reverseFieldTypeClass,反射得到其所有申明的字段,查看

1、If there's the from class name in the defined class, they are one2one bidirectional associations.  中文真不到怎么表達,看不明白可以先閱讀郭霖的Android數據庫高手秘籍(四)——使用LitePal建立表關聯。通俗一點表達就是,如果reverseFields中也有你這個from class name,那么就是一對一關系,而且是雙邊的一對一關系

2、 If there's the from class Set or List in the defined class, they are many2one bidirectional associations.

如果reverseFields中有這個from class name,並且是以List或者Set的形式存在,那么就是多對一關系

3、If there's no from class in the defined class, they are one2one unidirectional associations.

如果在from class 不在defined class 中,他們是一對一關系,只是單邊的一對一關系

 

嗯,感覺沒說明白。。一步一步來吧,我們看看關聯數據的值是怎么賦的。

在剛才提到的beforeSave的方法中

private void beforeSave(DataSupport baseObj, List<Field> supportedFields, ContentValues values)
            throws SecurityException, IllegalArgumentException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        putFieldsValue(baseObj, supportedFields, values);
        putForeignKeyValue(values, baseObj);            //將foreignKey的值設置到ContentValue中
}

點進去putForeignKeyValue

private void putForeignKeyValue(ContentValues values, DataSupport baseObj) {
        Map<String, Long> associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK();   //得到存着關聯模型的一個Map,key是字段名,value是外鍵的id
        for (String associatedTableName : associatedModelMap.keySet()) {
            values.put(getForeignKeyColumnName(associatedTableName),                //put到ContentValue里面去
                    associatedModelMap.get(associatedTableName));
        }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM