Dagger2的应用——MVP+Retrofit+RxJava

Dagger2的应用——MVP+Retrofit+RxJava

传统Android开发里,Activity或Fragment这一层太重了,因为它承担了和界面交互、和数据交互等等一系列职责,所以一个Activity搞不好就有好几千行,各种职责的代码混在一块,不利用维护。

所以MVP、MVVM这类开发模式现在更吸引开发者,它们用不同方式减轻了Activity的职责,MVVM现在主要是google自家推的数据绑定,虽然刚出来没有多久,也吸引了不少开发者的兴趣,未来可能会有所发展。

MVP模式其实更容易理解,它把整个应用分成了三块,其中M代表着操纵数据的一组API,而V代表着操纵界面的一组API,P是Presenter的简称,它持有M和V这两组接口,自己提供一组方法供其它人调用,从而完成M和V的桥梁作用。

一般情况下,M这个接口,是纯粹的API,它主要是包含两大部分:远程网络API和本地缓存,也就是所谓的DataLayer,它和Presenter的通信是单向的,也就是说只能Presenter持有DataLayer的API,Presenter可以随意调用这些API来获取数据。

而V这个接口,通常是由Activity、Fragment、Adapter或者比较重的View来实现,它和Presenter互相持有,这样当发生界面交互的时候,它可以调用Presenter的方法,而Presenter同样也持有了V的api,当需要让界面发生改变的时候,就回调对应的方法,这样就发生了相互通信。

这三者概念说清楚了以后,来谈谈具体的实现。

首先是Presenter,我们实现个BasePresenter,它需要持有的一部分是DataLayer,而这个一般是网络API或者缓存API,我们用Retrofit来做网络API的实现,所以这里需要注入一个Retrofit Service。

// BasePresenter.java
public abstract class BasePresenter {
    @Inject public DemoService mApi;
    @Inject public CookieManager cookies;
    public BasePresenter(){
        App.apiComponent.inject(this);
    }
}

我上面用了一个Dagger2的Component,它提供注入两个api,一个是Retrofit service,还有一个是自己定义的CookieManager,这个CookieManager主要应用在登录、注册、换账号、退出登录这些Presenter里的。

apiComponent在App那边build,它需要提供的两个类都是单例。

Retrofit这部分,并不能直接build,这是因为默认没有接收和保存cookie,我们需要自己定义一个OkHttpClient,而这个OkHttpClient里得添加一个Interceptor,用于在请求开始的时候,给request添加一个Cookie头。

这个Interceptor是个接口,我刚才自定义的CookieManager就实现了这个接口,由于Cookies是需要持久化的,我们就把它放在SharedPreferences里,这样以后就能随时读取到了。

// CookieManager.java
public class CookieManager implements Interceptor {
protected Map<String,String> cookies = new HashMap<>(3);

    private SharedPreferences sp;
    public CookieManger(SharedPreferences sp){
        this.sp = sp;
        String cookieString = sp.getString("cookies","");
        initCookies(cookieString);

    }    
    public String toString(){
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : cookies.entrySet()) {
            sb.append(entry.getKey()+'='+entry.getValue()+";");
        }
        return sb.toString();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request req = chain.request();
        req=req.newBuilder()
               .addHeader("Cookie",this.toString())
               .build();
        return chain.proceed(req);
    }
}

上面这个类省略了initCookies、save、clear、reset、add等操作cookie map的方法,可以自行添补。

有了这个可以作为Intercetor的CookieManager,我们就可以用它来构造一个OkHttpClient,从而构造一个Retrofit实例,也就能构造一个Service API了。

// ApiModule.java
@Provides @Singleton
CookieManger provideCookieManger(SharedPreferences sp){
    return new CookieManger(sp);
}

上面通过参数依赖注入了SharedPreferences的单例,这样我们就能初始化cookies了,有了这个Intercetor,我们就能构造出一个OkHttpClient了

// ApiModule.java
@Provides @Singleton
OkHttpClient provideClient(CookieManger cm){
    OkHttpClient client = new OkHttpClient();
    client.interceptors().add(cm);
    return client;
}

上面这个同样是通过Dagger2的参数依赖注入完成的,这里的OkHttpClient是OkHttp2.0带的。

// ApiModule.java
@Provides @Singleton
Retrofit provideRetrofit(OkHttpClient client){
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .client(client)
            .build();
    return retrofit;
}

我们提供的这个Retrofit里添加了带Gson支持的Converter,并且由于我比较喜欢RxJava,所以也带了RxJavaCallAdapter的支持,需要注意如果要使用这两个,得在gradle那边加上

compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'

现在有了Retrofit的单例,也就能提供出一个Presenter需要的实例了

// ApiModule.java
@Provides @Singleton
DemoService provideService(Retrofit retrofit){
    return retrofit.create(DemoService.class);
}

这样我的ApiComponent就算完成了,它主要是提供了DemoService这个单例,把ApiComponent放在App的onCreate里build,随后就可以像开头一样,注入到BasePresenter类中了

这样也就完成了Presenter里的DataLayer部分,在随后的Presenter里,我们可以随时使用mApi.getThread()这样的api来完成网络请求并拿到数据了。

推荐在写Retrofit的Service Interface的时候,定义成Observable是最好的做法,非常方便的结合了RxJava,当你需要二次请求或者需要和缓存做结合的时候,RxJava这套链式写法太方便。

接下来就说说Presenter持有的这个V的接口,通常情况下,我会把要用到的V 接口写在Presenter的里面,比如这样

public class LoginPresenter extends BasePresenter {
    private LoginPresenter.View mView;
    public static interface View {
        public void setEmailError(String error);
        public void setPasswordError(String error);
        public void showError(String error);
    }
}

这里提供的LoginPresenter.View接口,有是在LoginActivity那边需要实现的,我们在这个Presenter提供了一个login()方法,它会回调mView的方法,这些方法都是和界面相关的,在LoginActivity那边实现。

注意到Activity剥离了与DataLayer交互的能力,在它的代码里,只能存在和界面相关的方法,它的职责完全限定在view范畴里了。

由于View和Presenter相互持有,所以可能会导致内存泄漏的问题,这时候需要在Activity的onDestory那边做处理

@Override
public void onDestroy(){
    super.onDestroy();
    mPresenter.clearView();
    mPresenter = null;
}

还有你可能需要在某些情况下,停止网络请求,这个时候只需要对Retrofit的Observable做unsubscribe处理即可,它会自动调用service的cancel功能,这样就可以停止请求,非常方便。

在基于Fragment构建的app里,也是把Fragment当成V API来处理,只要它实现了你自定义的接口就好。

MVP这种模式分拆了Activity或者Fragment的职责,自己做中间层挡在了View和DataLayer之间,它的好处不仅仅是职责清晰,还具有可替换的好处,因为它和View之间的联系是通过接口来完成了,当你需要替换的时候,只需要按照同样的接口来实现即可。

编辑于 2016-01-07 17:53