從零開始的Android新項目6 - Repository層(下) Realm檐春、緩存、異常處理

承接上篇從零開始的Android新項目5 - Repository層(上) Retrofit么伯、Repository組裝疟暖,本文繼續(xù)介紹Realm、緩存,以及統(tǒng)一的異常處理設(shè)計俐巴。

Realm

Realm在移動端數(shù)據(jù)庫中也算是比較有名的一款了骨望,以其跨平臺和驚人的速度而聞名。啊欣舵,對了擎鸠,還有文檔多。

這里要黑的就是文檔問題邻遏,Realm雖然乍一看文檔很多糠亩,但是老實說,寫的挺亂的准验。不過總體來說赎线,實踐和應(yīng)用中感覺還不錯,性能好糊饱,也比較方便垂寥,比起不穩(wěn)定的DBFlow和麻煩至極的GreenDao來好了太多了,唯一的美中不足就是so比較大另锋,會增大包的體積1MB滞项。

引入

從Realm 0.90開始,用法與之前有了改變:

在root的build.gralde中:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:0.90.1"
    }
}

然后在對應(yīng)需要應(yīng)用到Realm的夭坪,比如data module的build.gradle:

apply plugin: 'realm-android'

即可使用Realm文判。

使用

使用起來也很方便,比如我們想要緩存用戶的信息

public class UserPo extends RealmObject {
    @PrimaryKey
    public String id;
    public String name;
    public String headerUrl;
    public long updateTime;
}

這樣就對應(yīng)了一個表室梅,其主鍵為id戏仓,另外有3列name, headerUrl, 以及updateTime。

如果想要查詢亡鼠,只需要:

UserPo user = getRealm().where(UserPo.class)
        .equalTo("id", userId)
        .findFirst();

如果要寫入一條記錄:

User user = new UserPo();
user.setName(userInfoEntity.getNickName());
user.setId(userInfoEntity.getUserId());
user.setHeaderUrl(userInfoEntity.getHeaderImageUrl());
user.setUpdateTime(System.currentTimeMillis());

getRealm().beginTransaction();
getRealm().copyToRealmOrUpdate(user);
getRealm().commitTransaction();

就是這么簡單赏殃。

如果想要直接和Retrofit一起應(yīng)用,去進(jìn)行串行化间涵,可以參考該Gist仁热。

// 結(jié)合 Realm, Retrofit 和 RxJava (使用了Retrolambda以簡化符號)的例子。
// 讀取所有Person勾哩,然后與從GitHub獲取的最新狀態(tài)merge到一起
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
realm.where(Person.class).isNotNull("username").findAllAsync().asObservable()
    .filter(persons.isLoaded)
    .flatMap(persons -> Observable.from(persons))
    .flatMap(person -> api.user(person.getGithubUserName())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(user -> showUser(user));

更多詳情可以去官網(wǎng)看抗蠢,migration/relationship等等支持應(yīng)有盡有,我只能說思劳,文檔實在太長太長了迅矛。

內(nèi)存

內(nèi)存,也就是直接使用變量存儲在對應(yīng)repository中敢艰,如果非空則優(yōu)先直接返回內(nèi)存中的變量诬乞。

LruCache

LruCache限定了最大的entry數(shù)量册赛,近期最少使用算法保證了淘汰機(jī)制的合理性钠导。使用場景如用戶信息緩存震嫉,會淘汰那些最近沒有訪問過的用戶的信息緩存。使用可參考Google官網(wǎng):LruCache牡属。

變量存儲

變量存儲很簡單票堵,直接在Repository實現(xiàn)類中直接變量存儲上一次的返回結(jié)果,在下一次請求的時候優(yōu)先使用內(nèi)存緩存逮栅。使用場景如請求后直接刷新本地的變量悴势,下次調(diào)用repository方法使用啊concat先返回內(nèi)存里的變量,然后再使用網(wǎng)絡(luò)數(shù)據(jù)進(jìn)行刷新措伐。

統(tǒng)一異常處理

作為Repository層特纤,本身不會,也不應(yīng)該去處理任何異常和錯誤(比如請求的錯誤碼)侥加,一切都將作為Exception異常拋給上層去做統(tǒng)一處理捧存,而RxJava的onError機(jī)制也幫助我們能優(yōu)雅地去做這件事。

Observable.error

類似在上一篇中提到的方法担败,我們可以使用Observable.error返回異常昔穴,供上層根據(jù)該異常做對應(yīng)處理。無論是網(wǎng)絡(luò)異常提前,數(shù)據(jù)庫異常吗货,亦或是服務(wù)器response異常等等,都可以進(jìn)行分類創(chuàng)建對應(yīng)的Exception類狈网,拋給上層宙搬。

public static <T> Observable<T> extractData(Observable<MrResponse> observable, Class<T> clazz) {
    return observable.flatMap(response -> {
        if (response == null) {
            return Observable.error(new NetworkConnectionException());
        } else if (response.getStatusCode() == ResponseException.STATUS_CODE_SUCCESS) {
            return Observable.just(mGson.fromJson(mGson.toJson(response.data), clazz));
        } else {
            Logger.e(TAG, response.data);
            return Observable.error(new ResponseException(response));
        }
    });
}

Subscriber.onError

我們使用Subscriber的基類來處理通用錯誤,其他所有Subscriber繼承它:

public class MrSubscriber<T> extends DefaultSubscriber<T> {
   @Override
   public void onError(Throwable e) {
       super.onError(e);
       if (!handleCommonResponseError((Exception) e)) {
           if (e != null && e.getMessage() != null) {
               Logger.w(TAG, e.getMessage());
           }
           showErrorMessage(new DefaultErrorBundle((Exception) e));
       }
   }
}

protected void showErrorMessage(ErrorBundle errorBundle) {
    String errorMessage = ErrorMessageFactory.create(this, errorBundle.getException());
    showErrorMessage(errorMessage);
}

protected void showErrorMessage(String errorMessage) {
    ToastUtils.show(this, errorMessage);
}

DefaultErrorBundle是exception的wrapper孙援,管理了其錯誤害淤。

public class DefaultErrorBundle implements ErrorBundle {

    private static final String DEFAULT_ERROR_MSG = "Unknown error";

    private final Exception exception;

    public DefaultErrorBundle(Exception exception) {
        this.exception = exception;
    }

    @Override
    public Exception getException() {
        return exception;
    }

    @Override
    public String getErrorMessage() {
        return (exception != null) ? this.exception.getMessage() : DEFAULT_ERROR_MSG;
    }
}

ErrorMessageFactory是錯誤消息工廠,根據(jù)exception創(chuàng)建對應(yīng)的錯誤消息提示拓售,讓用戶不至于碰到錯誤莫名其妙窥摄。

/**
 * Factory used to create error messages from an Exception as a condition.
 */
public class ErrorMessageFactory {

    private static final String TAG = "ErrorMessageFactory";

    private ErrorMessageFactory() {
        //empty
    }

    /**
     * Creates a String representing an error message.
     *
     * @param context   Context needed to retrieve string resources.
     * @param exception An exception used as a condition to retrieve the correct error message.
     * @return {@link String} an error message.
     */
    public static String create(Context context, Exception exception) {
        if (StringUtils.isNotEmpty(exception.getMessage())) {
            Logger.e(TAG, exception.getMessage());
        }

        String message = context.getString(R.string.exception_message_generic);

        if (exception instanceof NetworkConnectionException) {
            message = context.getString(R.string.exception_message_no_connection);
        } else if (exception instanceof NotFoundException) {
            message = context.getString(R.string.exception_message_not_found);
        } else if (exception instanceof ResponseException) {
            message = exception.getMessage();
        } else if (exception instanceof HttpException) {
            message = exception.getMessage();
        }
        return message;
    }
}

handleCommonResponseError

通常,服務(wù)器會返回錯誤信息础淤,我們需要根據(jù)一些code進(jìn)行對應(yīng)處理崭放,MrSubscriber的onError就調(diào)用了handleCommonResponseError來處理這些通用錯誤:

protected boolean handleCommonResponseError(Exception exception) {
    boolean handled = false;
    if (exception instanceof ResponseException) {
        ResponseException responseException = (ResponseException) exception;
        switch (responseException.getStatusCode()) {
            case ResponseException.ERROR_CODE_NEED_LOGIN:
                handled = true;
                getUserSystem().setVuser("");
                getNavigator().navigateToLoginPage(this);
                break;
            case ResponseException.ERROR_CODE_NEED_PERFECT_PROFILE:
                handled = true;
                if (responseException.getVuser() != null) {
                    getUserSystem().setVuser(responseException.getVuser().getVuser());
                }
                getNavigator().navigateToPerfectProfile(this);
                break;
            case ResponseException.ERROR_CODE_NEED_THIRD_PARTY_BIND:
                handled = true;
                getNavigator().navigateToThirdPartyBind(this);
                break;
        }
    }
    return handled;
}

Log & 上報

出錯了當(dāng)然要上報啦,bugly鸽凶、友盟币砂,本地寫文件打zip包上傳,Logger做的就是寫文件log了玻侥,這些常見的app都會去做决摧,這里就不贅述了。

總結(jié)和下集預(yù)告

本系列兩篇文章描述了Android項目中,Repository層的設(shè)計與實現(xiàn)掌桩,也可以理解它為data或者model層边锁。一個好的Repository層和上層相對獨(dú)立,內(nèi)聚完成業(yè)務(wù)邏輯的數(shù)據(jù)部分波岛,即便內(nèi)部有修改茅坛,比如添加了緩存,對外仍然保持一致则拷。而好的異常處理設(shè)計一方面讓代碼中不會充斥著雜七雜八的 try & catch贡蓖,另一方,恰當(dāng)?shù)腻e誤展示也讓用戶知道究竟出了什么錯煌茬,不至于莫名其妙斥铺。

下一次不知是何時相見,希望能為大家?guī)砦覀冺椖恐惺褂肦eact Native進(jìn)行混合開發(fā)的苦與甜坛善。

另外仅父,打個小廣告,本司的新產(chǎn)品Crew已經(jīng)在各大Android應(yīng)用市場上線浑吟,專注于職場垂直社交笙纤。一搜和興趣相投的人聊天。iOS版本正在審核中组力。

2個字找到志趣相投的職場伙伴省容,秒搜陌生人同類,智能自動破冰燎字。多關(guān)鍵字疊加腥椒,高效率鎖定職場同僚。精準(zhǔn)匹配興趣對象候衍,超輕聊天笼蛛,更能一鍵組建群聊,加入一群人的狂歡蛉鹿。

demo沒空寫了滨砍,反正我也沒混淆,直接反編譯來黑我吧妖异。哈哈惋戏。有bug或者功能上的意見建議歡迎直接反饋給我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末他膳,一起剝皮案震驚了整個濱河市响逢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棕孙,老刑警劉巖舔亭,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件些膨,死亡現(xiàn)場離奇詭異,居然都是意外死亡钦铺,警方通過查閱死者的電腦和手機(jī)傀蓉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來职抡,“玉大人,你說我怎么就攤上這事误甚「克Γ” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵窑邦,是天一觀的道長擅威。 經(jīng)常有香客問我,道長冈钦,這世上最難降的妖魔是什么郊丛? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮瞧筛,結(jié)果婚禮上厉熟,老公的妹妹穿的比我還像新娘。我一直安慰自己较幌,他們只是感情好揍瑟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乍炉,像睡著了一般绢片。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岛琼,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天底循,我揣著相機(jī)與錄音,去河邊找鬼槐瑞。 笑死熙涤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的困檩。 我是一名探鬼主播灭袁,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窗看!你這毒婦竟也來了茸歧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤显沈,失蹤者是張志新(化名)和其女友劉穎软瞎,沒想到半個月后逢唤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涤浇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年鳖藕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片只锭。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡著恩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜻展,到底是詐尸還是另有隱情喉誊,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布纵顾,位于F島的核電站伍茄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏施逾。R本人自食惡果不足惜敷矫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汉额。 院中可真熱鬧曹仗,春花似錦、人聲如沸蠕搜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讥脐。三九已至遭居,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旬渠,已是汗流浹背俱萍。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留告丢,地道東北人枪蘑。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像岖免,于是被迫代替她去往敵國和親岳颇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理颅湘,服務(wù)發(fā)現(xiàn)话侧,斷路器,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,520評論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法闯参,類相關(guān)的語法瞻鹏,內(nèi)部類的語法悲立,繼承相關(guān)的語法,異常的語法新博,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • 我已古佛枯燈相與共 竹影搖曳半夜涼初透 你眼中仍柔情難收 如那一場夢 喜悅又惶恐 那日葉落歸根依舊 秋夜微涼 神形...
    若風(fēng)月夜閱讀 517評論 13 8
  • 1 從小姐姐就屬于別人家的孩子的那種赫悄,成績一級棒原献,是父母的驕傲,老師的得意門生埂淮。而我至今還記得小學(xué)一次數(shù)學(xué)測試我的...
    不傾城閱讀 465評論 1 4