Retrofit + EventBus的一點總結(jié)

說起Android應用開發(fā)的網(wǎng)絡請求框架,最流行也最優(yōu)秀的兩個状蜗,一個是Volley,另一個是Retrofit. 今天來聊聊我在項目中對Retrofit的應用實踐.

Retrofit介紹

Retrofit是明星程序員、男神Jake Wharton所在的Square公司開源的一個RESTful網(wǎng)絡請求框架幢炸, 目前最新版是2.1.0充岛,但是本文基于Retrofit 1.9.x保檐,2.x 版本相對于1.x 有非常大的更新,以后有機會再研究一下2.x版本. 本文不涉及Retrofit的入門介紹崔梗,不了解的親們可以查看文檔Github項目主頁夜只, 網(wǎng)絡上也有大量的入門介紹文章. 本文主要講講我在實際項目中對Retrofit和EventBus結(jié)合使用的總結(jié)。

EventBus介紹

EventBus是一個Android平臺的事件總線框架炒俱,使用簡單盐肃、輕量、低開銷权悟,可以用于代碼的解耦. 基本介紹和用法見項目主頁.

為什么使用EventBus

大家知道Retrofit中聲明一個API接口的方式如下:
我們定義一個interface 叫做 MyRetrofitService(我知道MyXxx這樣的命名有點土, 但是作為示例, 觀眾朋友們?nèi)棠鸵幌掳?_- ), 里面聲明一個登錄方法:

@Headers("Content-Type: application/json;charset=UTF-8")
@POST("/api/appLogin")
void login(@Body LoginReq loginReqBody, Callback<LoginResp> cb);

其中Callback<LoginResp>是使用Retrofit提供的接口retrofit.Callback<T>砸王,用于接收請求響應. LoginRespBaseResp的子類.

如果我們在UI層代碼中調(diào)用接口的時候是類似下面的寫法:

MyRestService.getInstance().login(new loginReqBody("username","password"), new Callback<LoginResp> {
    @Override
    public void failure(RetrofitError err) {
        handleRetrofitError(err);
    }

    @Override
    public void success(final LoginResp arg0, Response arg1) {
        //TODO: 處理響應
    }
});

那么很顯然UI層的業(yè)務邏輯代碼和Retrofit的代碼緊緊耦合在了一起,剪不斷峦阁,理還亂. 如果哪天項目不再愛Retrofit了谦铃,想要更換網(wǎng)絡請求框架,不再使用Retrofit榔昔,那么所有的調(diào)用網(wǎng)絡接口的UI層代碼都要一番改動驹闰,這樣代碼維護成本高,而且極易引入bug.

我們要做的是將業(yè)務邏輯和網(wǎng)絡請求兩層做分離撒会,解耦.

  • 最初的嘗試
    定義一個ResponseHandler抽象類嘹朗,實現(xiàn)Callback<T>接口, 在UI層調(diào)用時傳入ResponseHandler類的實例,這樣UI層代碼不再直接依賴Retrofit的代碼诵肛,改為依賴ResponseHandler類. ResponseHandler類的實現(xiàn)大致如下:
public abstract class ResponseHandler implements Callback<BaseResp > {

    @Override
    public void success(BaseResp resp, Response response) {
        //TODO: 如果需要的話屹培,做一些針對resp的處理
        onSuccess(resp);
    }

    @Override
    public void failure(RetrofitError retrofitError) {
        int errCode = getErrCode(retrofitError);
        String errMsg = getErrMsg(retrofitError);
        onFailure0(errCode, errMsg);
    }

    public abstract void onSuccess(BaseResp resp);
    public abstract void onFailure0(int errorCode, String errorMsg);
}

這樣已經(jīng)達到了解耦的目的,好比你跟一個人網(wǎng)聊,每天聊得開心開心極了褪秀,但是你不知道那邊手機或電腦后邊是不是已經(jīng)換了人了蓄诽,反正對你來說體驗一樣. 相對于直接面聊,隔了一層網(wǎng)絡媒吗,你和TA被網(wǎng)絡解耦了仑氛。

  • 更進一步
    考慮這樣一個需求,如果一個Activity里有兩個Fragment闸英,左側(cè)列表FragmentA和右側(cè)詳情FragmentB锯岖,在FragmentA中點擊一個按鈕時,調(diào)用一個接口甫何,然后根據(jù)接口返回結(jié)果FragmentB需要相應的刷新界面.

    實現(xiàn)這個需求有很多方式嚎莉,使用事件總線是比較好的一種。事件總線可以自己實現(xiàn)沛豌,也可以使用一些優(yōu)秀的開源框架趋箩,比如EventBus.
    一種方式是在FragmentA中接收接口回調(diào),然后再用EventBus通知FragmentB. 那么既然已經(jīng)引入了事件總線加派,何不把網(wǎng)絡接口請求的響應用EventBus的事件形式發(fā)出來叫确,這樣代碼看起來更清楚美觀些,接下來是這種方式的實踐總結(jié).

Retrofit + EventBus

芍锦,
首先要有一個全局的EventBus單例實例竹勉,可以放在Application里,也可以如下:

    public class EventBusProvider {
        private static final EventBus mEventBus;
        static {
            mEventBus = EventBus.builder().logNoSubscriberMessages(false).sendNoSubscriberEvent(false).build();
        }

        public static final EventBus getEventBus() {
            return mEventBus;
        }
}

娄琉,
所有需要處理網(wǎng)絡請求的Activity都繼承自一個BaseActivity次乓,在BaseActivity里加一個onEvent()方法,因為onEvent()方法是EventBus的監(jiān)聽者類必須有的一個方法孽水,這樣避免所有的activity都去寫onEvent()方法.

BaseActivityonCreate()方法里注冊監(jiān)聽EventBusProvider.getEventBus().register(this);
onDestroy()里解注冊EventBusProvider.getEventBus().unregister(this);

BaseActivity類:

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        EventBusProvider.getEventBus().register(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        EventBusProvider.getEventBus().unregister(this);
        super.onDestroy();
    }

    public final void onEvent(NwEvent event) {
        if (event != null && event.type.mainType == getNwEventMainType()) {
            handleNwEvent(event);
        }
    }

    //這個方法是final的票腰,因為子類不需要覆蓋這個方法,這個方法會返回當前子類的類型
    protected final Type getNwEventMainType() {
        return this.getClass();
    }

    /**
     * NOTE: 需要處理網(wǎng)絡請求響應的子類女气,要覆蓋這個方法來處理響應
     */
    protected void handleNwEvent(NwEvent event) {
    }
}

**NOTE: ** BaseFragmentBaseActivity類似.

NwEvent是網(wǎng)絡事件類:

public class NwEvent {

    public NwEventType type = null;
    public BaseResp content = null;
    public boolean success = false;
    public MyRestService.ErrorType errorType = MyRestService.ErrorType.NOT_SPECIFIED;

    public NwEvent() {
        this.type = new NwEventType();
    }

    public NwEvent(NwEventType type, boolean success, BaseResp content, MyRestService.ErrorType errorType) {
        this.type = type != null ? type : new NwEventType();
        this.content = content;
        this.success = success;
        this.errorType = errorType;
    }
}

NwEventType是事件類型類杏慰,mainType表示是哪個類發(fā)出的請求的響應事件,subType用于區(qū)分一個類發(fā)出的多個請求:

public class NwEventType {
    public Type mainType = null;
    public int subType = -1;

    public NwEventType() {
    }

    public NwEventType(Type type) {
        this.mainType = type;
    }

    public NwEventType(Type mainType, int subType) {
        this.mainType = mainType;
        this.subType = subType;
    }

    @Override
    public String toString() {
        return "{ mainType: " + mainType + ", subType: " + subType + " }";
    }
}

炼鞠,
另寫一個ResponseHandler類缘滥,處理網(wǎng)絡響應回調(diào),并post事件谒主,簡要如下:

class ResponseHandler implements Callback<BaseResp> {
    private NwEventType eventType = null;

    public ResponseHandler(NwEventType eventType) {
        this.eventType = eventType;
    }
    
    @Override
    public void success(BaseResp resp, Response response) {
        //TODO: 一些針對resp的判斷朝扼、處理
        ErrorType errorType = ErrorType.OK;//TOOD: 根據(jù)你的邏輯.
        boolean result = resp != null;//或更具體的判斷
        
        //然后發(fā)出Event, 在具體調(diào)用接口的Activity或Fragment就能收到onEvent()回調(diào)了
        EventBusProvider.getEventBus().post(new NwEvent(eventType, result, resp, errorType));
    }
    
    @Override
    public void failure(RetrofitError error) {
        //TODO: 一些針對error的判斷、處理
        EventBusProvider.getEventBus().post(new NwEvent(eventType, false, null, errorType));
    }
}

霎肯,
接口聲明還是一樣:

@Headers("Content-Type: application/json;charset=UTF-8")
@POST("/api/appLogin")
void login(@Body LoginReq loginReqBody, Callback<LoginResp> cb);

然后在MyRestService里對外的接口改為傳入一個事件類型NwEventType即可擎颖,以為NwEventType中的mainTypesubType已經(jīng)能夠確定是哪個類發(fā)出的哪個請求:

public void login(String username, String password, NwEventType eventType) {
    mApiService.login(new LoginReq(username, password), new ResponseHandler(eventType));
}

凹耙,
UI層的調(diào)用. 比如在一個Activity里調(diào)用接口:

class MyExampleActivity extends BaseActivity {

    private static final int NW_EVENT_SUB_TYPE_LOGIN = 1;
    private static final int NW_EVENT_SUB_TYPE_GET_USER_INFO = 2;

    ...
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ...
        MyRestService.getInstance().login(un, pw, new NwEventType(getNwEventMainType(), NW_EVENT_SUB_TYPE_LOGIN));
        ...
        
        
    }

    ...
    
    @Override
    protected void handleNwEvent(NwEvent event) {
        switch (event.type.subType) {
            case NW_EVENT_SUB_TYPE_LOGIN:
                handleLoginResp(event);
                break;
            case NW_EVENT_SUB_TYPE_GET_USER_INFO:
                handleUserInfoResp(event);
                break;
            
        }
    }
    
    ...
    
    private void handleLoginResp(NwEvent event) {
        //TODO 處理登錄響應
        
        //如果登錄成功,而且有需要肠仪,調(diào)用獲取用戶信息接口
        MyRestService.getInstance().getUserInfo(new NwEventType(getNwEventMainType(), NW_EVENT_SUB_TYPE_GET_USER_INFO));
    }
    
    ...
}

總結(jié)

以上方式基本實現(xiàn)了網(wǎng)絡層和UI、業(yè)務邏輯層的解耦. 但是對于EventBus的使用并不限于如此备典,比如NwEventType的實現(xiàn)就可以改為用API接口的編號來實現(xiàn)异旧,這樣一個類發(fā)出的請求就不被拘泥于一定要自己這個類來監(jiān)聽處理,也就可以直接實現(xiàn)我們在"更進一步"小節(jié)中提到的需求. 等等.

獻花拍磚請隨意-_-.

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末提佣,一起剝皮案震驚了整個濱河市吮蛹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拌屏,老刑警劉巖潮针,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異倚喂,居然都是意外死亡每篷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門端圈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焦读,“玉大人,你說我怎么就攤上這事舱权〈;危” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵宴倍,是天一觀的道長张症。 經(jīng)常有香客問我,道長鸵贬,這世上最難降的妖魔是什么俗他? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮阔逼,結(jié)果婚禮上拯辙,老公的妹妹穿的比我還像新娘。我一直安慰自己颜价,他們只是感情好涯保,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著周伦,像睡著了一般夕春。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上专挪,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天及志,我揣著相機與錄音片排,去河邊找鬼。 笑死速侈,一個胖子當著我的面吹牛率寡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倚搬,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼冶共,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了每界?” 一聲冷哼從身側(cè)響起捅僵,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眨层,沒想到半個月后庙楚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡趴樱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年馒闷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叁征。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡窜司,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出航揉,到底是詐尸還是另有隱情塞祈,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布帅涂,位于F島的核電站议薪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏媳友。R本人自食惡果不足惜斯议,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醇锚。 院中可真熱鬧哼御,春花似錦、人聲如沸焊唬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赶促。三九已至液肌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸥滨,已是汗流浹背嗦哆。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工谤祖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人老速。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓粥喜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親橘券。 傳聞我的和親對象是個殘疾皇子额湘,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

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