Retrofit + EventBus的一点总结

说起Android应用开发的网络请求框架,最流行也最优秀的两个,一个是Volley,另一个是Retrofit. 今天来聊聊我在项目中对Retrofit的应用实践.

Retrofit介绍

Retrofit是明星程序员、男神Jake Wharton所在的Square公司开源的一个RESTful网络请求框架, 目前最新版是2.1.0,但是本文基于Retrofit 1.9.x,2.x 版本相对于1.x 有非常大的更新,以后有机会再研究一下2.x版本. 本文不涉及Retrofit的入门介绍,不了解的亲们可以查看文档Github项目主页, 网络上也有大量的入门介绍文章. 本文主要讲讲我在实际项目中对Retrofit和EventBus结合使用的总结。

EventBus介绍

EventBus是一个Android平台的事件总线框架,使用简单、轻量、低开销,可以用于代码的解耦. 基本介绍和用法见项目主页.

为什么使用EventBus

大家知道Retrofit中声明一个API接口的方式如下:
我们定义一个interface 叫做 MyRetrofitService(我知道MyXxx这样的命名有点土, 但是作为示例, 观众朋友们忍耐一下吧-_- ), 里面声明一个登录方法:

@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层代码中调用接口的时候是类似下面的写法:

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层的业务逻辑代码和Retrofit的代码紧紧耦合在了一起,剪不断,理还乱. 如果哪天项目不再爱Retrofit了,想要更换网络请求框架,不再使用Retrofit,那么所有的调用网络接口的UI层代码都要一番改动,这样代码维护成本高,而且极易引入bug.

我们要做的是将业务逻辑和网络请求两层做分离,解耦.

  • 最初的尝试
    定义一个ResponseHandler抽象类,实现Callback<T>接口, 在UI层调用时传入ResponseHandler类的实例,这样UI层代码不再直接依赖Retrofit的代码,改为依赖ResponseHandler类. ResponseHandler类的实现大致如下:
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);
}

这样已经达到了解耦的目的,好比你跟一个人网聊,每天聊得开心开心极了,但是你不知道那边手机或电脑后边是不是已经换了人了,反正对你来说体验一样. 相对于直接面聊,隔了一层网络,你和TA被网络解耦了。

  • 更进一步
    考虑这样一个需求,如果一个Activity里有两个Fragment,左侧列表FragmentA和右侧详情FragmentB,在FragmentA中点击一个按钮时,调用一个接口,然后根据接口返回结果FragmentB需要相应的刷新界面.

    实现这个需求有很多方式,使用事件总线是比较好的一种。事件总线可以自己实现,也可以使用一些优秀的开源框架,比如EventBus.
    一种方式是在FragmentA中接收接口回调,然后再用EventBus通知FragmentB. 那么既然已经引入了事件总线,何不把网络接口请求的响应用EventBus的事件形式发出来,这样代码看起来更清楚美观些,接下来是这种方式的实践总结.

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;
        }
}


所有需要处理网络请求的Activity都继承自一个BaseActivity,在BaseActivity里加一个onEvent()方法,因为onEvent()方法是EventBus的监听者类必须有的一个方法,这样避免所有的activity都去写onEvent()方法.

BaseActivityonCreate()方法里注册监听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: 需要处理网络请求响应的子类,要覆盖这个方法来处理响应
     */
    protected void handleNwEvent(NwEvent event) {
    }
}

**NOTE: ** BaseFragmentBaseActivity类似.

NwEvent是网络事件类:

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表示是哪个类发出的请求的响应事件,subType用于区分一个类发出的多个请求:

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类,处理网络响应回调,并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: 根据你的逻辑.
        boolean result = resp != null;//或更具体的判断
        
        //然后发出Event, 在具体调用接口的Activity或Fragment就能收到onEvent()回调了
        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已经能够确定是哪个类发出的哪个请求:

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


UI层的调用. 比如在一个Activity里调用接口:

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 处理登录响应
        
        //如果登录成功,而且有需要,调用获取用户信息接口
        MyRestService.getInstance().getUserInfo(new NwEventType(getNwEventMainType(), NW_EVENT_SUB_TYPE_GET_USER_INFO));
    }
    
    ...
}

总结

以上方式基本实现了网络层和UI、业务逻辑层的解耦. 但是对于EventBus的使用并不限于如此,比如NwEventType的实现就可以改为用API接口的编号来实现,这样一个类发出的请求就不被拘泥于一定要自己这个类来监听处理,也就可以直接实现我们在"更进一步"小节中提到的需求. 等等.

献花拍砖请随意-_-.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容