1. 程式人生 > >從ActiveAndroid到Realm的爬坑之路(二)

從ActiveAndroid到Realm的爬坑之路(二)

Realm是作為一個Gradle外掛整合到專案中的,怎麼配置 文件 裡說的也很清楚了,需要注意的是在官方的GitHub上是這麼新增依賴的:
dependencies {
classpath "io.realm:realm-gradle-plugin:<version>-SNAPSHOT"
}

但copy的時候中間的version 應該替換成最新的版本號。然後,就可以開始使(pa)用(keng) 了。

一、 資料庫實體可以不繼承RealmObject,實現RealmModel介面也可,但不能繼承其它父類。

文件很明確的指出,不一定要繼承RealmObject,實現RealmModel介面加@RealmClass註解一樣可以工作。 不過你要是繼承了別的類,就像這樣:

@RealmClass
public class DepartureRealm extends Model implements RealmModel{
    @PrimaryKey
    public long key;
    ...
}

那問題就來了:
Error:(12, 8) 錯誤: Realm model classes must either extend RealmObject or implement RealmModel to be considered a valid model class

明明實現了RealmModel介面了好嗎?

不可理解,去掉extends又能正常運行了。這樣就直接否定了我想直接使用ActiveAndroid的實體的想法,最關鍵的是,既然不能繼承其他類,提供這麼個功能的意義何在啊?

二、沒有自增主鍵,不能進行對欄位進行唯一性約束,不支援連線查詢

傳統資料庫很常見的概念在這裡是找不到的,主鍵不自增雖然有點不方便,但使用UUID也不失為一個好辦法。只是只有主鍵相同,它才幫你更新這一點,就讓人滿腦子問號了。而且它不支援左連右連,它儘量的將連線查詢給透明化,你只需要建立好實體之間的關係,就能通過欄位名直接查到關聯表的資料。不得不說,這樣設計很酷炫也很方便,然而,對於一個已存在的專案,就必須去修改它的表結構了,這必然會影響到整個專案。
為了解決這些問題,通過只修改資料庫Service層的相關類達到使用Realm而又能隨時切換回來的目的,就必須提供一個轉換機制,如下圖:

而且因為Realm只支援主鍵唯一的特點,每次Save時,還得判斷欄位是不是有唯一性約束,有就查詢資料庫是否存在此欄位值,存在就更新,不存在才生成新的ID並插入。總的changeActiveToRealm方法如下:

//mID, 轉換到RealmObject後,為其生成UUID,並將其通過這個引數傳遞出去。
 public static RealmModel changeActiveToRealm(Model object, Bundle mID) {
        Model temp;
        if (object == null) {
            return null;
        } else {
            temp = object;
        }

        Field[] fields = temp.getClass().getFields();
        String className = temp.getClass().getName();
        ...
        try {
            //載入Active實體對應的Realm實體。
            Class obtainClass = Class.forName(className.replace("Active", "Realm").replace("active", "realmobj"));
            RealmModel obtainObj = (RealmObject) obtainClass.newInstance();
            Field tempObtainField;
            String fieldName;
            boolean findUnique = false;
            Realm realm = Realm.getDefaultInstance();

            //記錄下所有需要提供唯一性約束的欄位。(根據Column註解)
            List<Field> group = new ArrayList<>();
            for (Field field : fields) {
                Column c = field.getAnnotation(Column.class);
                if (c != null) {
                    Column.ConflictAction[] a = c.onUniqueConflicts();
                    if (a.length != 0 && a[0] == Column.ConflictAction.REPLACE) {
                        group.add(field);
                    }
                }
            }

            if(group.size() != 0) {
                RealmQuery query = realm.where(obtainClass);
                for (Field f :
                        group) {
                    Object uniqueValue = f.get(temp);
                    if (uniqueValue instanceof Integer) {
                        query.equalTo(f.getName(), (Integer) uniqueValue);
                    } else {
                        query.equalTo(f.getName(), String.valueOf(uniqueValue));
                    }
                }
                RealmModel clazz = query.findFirst();
                if (clazz != null) {
                    Log.i("feng", "找到唯一性約束衝突 class Name :" + clazz.getClass().getSimpleName() +" 更新資料庫資料");
                    obtainObj = realm.copyFromRealm(clazz);
                    findUnique = true;
                }
                realm.close();
            }

            //將欄位值一一對映
            for (Field field : fields) {
                fieldName = field.getName();
                tempObtainField = obtainObj.getClass().getField(fieldName);
                tempObtainField.setAccessible(true);
                tempObtainField.set(obtainObj, field.get(temp));
            }
            //是否生成ID
            if (!findUnique) {
                Long tempmID = UUID.randomUUID().getLeastSignificantBits();

                Field o = obtainClass.getField("key");
                if (o.getName().equals("key")) {
                    int t = tempmID.intValue();
                    o.set(obtainObj, (long) t);
                    mID.putLong("id", (long) t);
                }
            } else {
                    mID.putLong("id", (long)obtainClass.getField("key").get(obtainObj));
            }

            return obtainObj;
        } catch (ClassNotFoundException e) {
          ...
        }
    }

而changeRealmToActive 的基本思路也差不多,都是通過反射獲得實體的值,並將它們對映到新的實體中去。
至此,應用並不知道底層的資料庫已經更換了,它一直用的都是Active的實體物件,而只需要在Service層中增加一個flag做判斷,來回切換兩個資料庫都不是問題了。
還有一種實現思路是不通過反射,而是用JSON字串做中轉,然而效率上肯定是不如直接反射拿到物件並且直接操作了。

三、不支援分頁查詢。

上篇文章已經介紹了懶載入機制,官方自豪的宣稱因為查詢出來並不佔用記憶體,所以你們放心地查出來所有的資料,想要哪段就擷取哪段。(提供介面不是更好嗎?)

//////從查詢的所有資料中取出需要的。(脫離Realm的控制)
   public static <E extends RealmModel> List<E> getLimitList(
            RealmResults<E> data, int offset, int limit) {
        List<E> obtainList = new ArrayList();
        Realm realm = Realm.getDefaultInstance();
        if (data.size() == 0 ){
            return obtainList;
        }
        for (int i = offset; i < offset + limit; i++) {
            if (i >= data.size()) {
                break;
            }
            E temp = realm.copyFromRealm(data.get(i));
            obtainList.add(temp);
        }
        realm.close();
        return obtainList;
    }

對於一個已經成型的專案,在新增任何第三方框架前都應該慎之又慎,雖然新技術聽起來總是很誘人,但能不能和現有的程式碼相結合是需要考慮的一方面,另一方面,還有可能遇到各種大大小小的坑。這也許是這次更換資料庫框架帶來的最大感受。