Android网络交互:Dagger2、Retrofit2与RxJava巧妙结合

Dagger2是Google提供的依赖注入框架,依赖注入为Android中组件之间的解耦提供了很好的解决方案。
Retrofit2是一套RESTful架构的Android(Java)客户端实现,基于注解,提供多种数据交互类型(JSON、protobuf、XML等),网络请求(POST,GET,PUT,DELETE等)封装。
RxJava是一个在 Java VM上使用可观测的序列来组成异步的、基于事件的程序的库。

相关链接
Dagger2: https://github.com/google/dagger
Retrofit2:http://square.github.io/retrofit/
RxJava:https://github.com/ReactiveX/RxJava

本文旨在将目前在项目中实践出的这套网络框架整理出来,和广大读者一起交流共同提升。本文以Retrofit2的使用、RxJava的封装、和Dagger2的注入顺序讲述该网络交互框架。

Retrofit2的使用

讲述Retrofit2在项目中的应用之前,在此简要描述其基本使用知识。

Retrofit2的使用遵循两个基本步骤,先定义网络接口,并通过接口指定执行机制;使用时通过Retrofit Builder构建实例,并通过Retrofit.create() 方法创建接口实例,并使用之。

Retrofit 2 网络请求接口定义

Retrofit2支持通过网络接口来指定执行机制,默认是支持Call和Future类型的,RxJava的支持需要在Retrofit2的构造器中额外的指定适配器进行转换。网络接口定义如下:

interface GitHubService {
  @GET("/repos/{owner}/{repo}/contributors")
  Call<List<Contributor>> repoContributors(..);

  @GET("/repos/{owner}/{repo}/contributors")
  Observable<List<Contributor>> repoContributors2(..);

  @GET("/repos/{owner}/{repo}/contributors")
  Future<List<Contributor>> repoContributors3(..);
}

对于其同步调用和异步调用,以Call类型为例,如下:

Call<List<Contributor>> call =
    gitHubService.repoContributors("square", "retrofit");
// 同步调用
response = call.execute();

// 异步调用
call.enqueue(new Callback<List<Contributor>>() {
  @Override void onResponse(/* ... */) {
    // ...
  }

  @Override void onFailure(Throwable t) {
    // ...
  }
});

Retrofit 2 初始化

Retrofit的构造器可以指定OkHttp支持,并且指明特定的 converter 或者 execute 机制。converter即网络交互数据解析器,包括JSON、protobuf、XML等,executer可以理解成Call Adapter Factory ,RxJava的支持要求在构造器中指定RxJavaCallAdapterFactory。

还是以GitHubService的使用为例。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .client(client)
    .addConverterFactory(ProtoConverterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();
 
 GitHubService gitHubService = retrofit.create(GitHubService.class);   

添加 converter 的顺序很重要。按照这个顺序,SDK将依次询问每一个 converter 能否处理一个类型,并且SDK遵循first-come first-serve原则进行转换。Call Adapter Factory 是一个知道如何将 call 实例转换成其他类型的工厂类。目前,只有 RxJava 的类型,也就是将 Call 类型转换成 Observable 类型。Client是一个配置好的 OkHttp 实例的,比如:配置 interceptors, 或者一个 SSL socket 工厂类,或者 timeouts 的具体数值。

Retrofit 封装
基于以上的基本知识,本项目对Retrofit做了如下封装。

1、 网络接口封装

按照接口返回类型封装成不同的Api Service类

public interface SyncApiService {

    @POST("/logon/refresh/{id}")
    retrofit2.Call<Account> refreshToken(
            @Path("id") String id, @Body HashMap map);
}

public interface ApiService {

    @GET("/doctors/{id}")
    Observable<UserProfile> getDoctorinfo(@Path("id") String id);
    ...
}

2、 Retrofit构造器封装

Retrofit构造器可以按照不同需求创建Retrofit,提供了converter、executer、和 HttpClient设置,并封装了网络错误和数据的统一处理机制。

@Singleton
public final class RestApi {
    public static final long CONNECT_TIME_OUT = 30L;
    public static final long READ_TIME_OUT = 30L;
    public static final long WRITE_TIME_OUT = 30L;

    private Context context;
    private RxBus rxBus;

    private OkHttpClient mOkHttpClient;
    private Map<String, String> dynamicHosts;

    @Inject
    public RestApi(Context context, RxBus rxBus) {
        this.context = context;
        this.rxBus = rxBus;
    }

    /**
     * 指定 RxJavaCallAdapterFactory 为 Call Adapter
     */
    public Retrofit retrofitStudio(String url) {
        return new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(setupClient())
                .build();
    }

  /**
     * 自定义RxErrorHandlingCallAdapterFactory 为 Call Adapter
     */
    public Retrofit retrofitDajia(String url) {
        return new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context))
                .client(setupClient())
                .build();
    }
    
    public OkHttpClient setupClient() {
        if (mOkHttpClient == null) {
            HttpLoggingInterceptor loggingInterceptor = getHttpLoggingInterceptor();
            Interceptor layoutInterceptor = getLayoutInterceptor();
            Interceptor hmacInterceptor = getHMACInterceptor();
            Interceptor cookieInterceptor = getCookieInterceptor();

            Interceptor dynamicHostInterceptor = getDynamicHostInterceptor();
            Authenticator authenticator = new TokenAuthenticator(context);

            mOkHttpClient = new OkHttpClient.Builder()
                    .dispatcher(new Dispatcher(GlobalConfig.HTTP_EXECUTOR))
                    .cache(getHttpCache())
                    .authenticator(authenticator)
                    .addInterceptor(loggingInterceptor)
                    .addInterceptor(dynamicHostInterceptor)
                    .addInterceptor(getCacheInterceptor())
                    .addNetworkInterceptor(getCacheInterceptor())
                    .addNetworkInterceptor(layoutInterceptor)
                    .addNetworkInterceptor(hmacInterceptor)
                    .addNetworkInterceptor(cookieInterceptor)
                    .connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS)
                    .readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)
                    .writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)
                    .build();
        }

        return mOkHttpClient;
    }
    
    ....
}

以上代码中关于自定义RxErrorHandlingCallAdapterFactory,以及OkHttpClient中的各种设置(Cache机制、动态Host设置、Cookie、URL加密等)我会在另外的文章中细讲,此处不展开。

3、 网络接口使用方法

SyncApiService apiService = RestApi.retrofitStudio([BaseApiUrl]).create(SyncApiService.class);
ApiService apiService = RestApi.retrofitDajia([BaseApiUrl]).create(ApiService.class);

RxJava的封装

对于返回Observable 的网络接口,可以用RxJava 进行处理。我重写了一个HttpResponseObserver,该类有两个目的:1、所有网络请求错误支持统一处理;2、接口返回的Observable 支持对 onNext、onError、onComplete的统一处理。该类的封装如下:

public abstract class HttpResponseObserver<T> implements Observer<T> {

    private RxBus rxBus;

    public HttpResponseObserver() {
    }

    public HttpResponseObserver(RxBus rxBus) {
        this.rxBus = rxBus;
    }

    @Override
    public void onNext(T t) {

    }

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {
        Logger.e(e, "error message : %s", e.getMessage());
        ApiError error = new ApiError(e);
        if (!onError(error)) {
            handleError(error);
        }
    }

    /**
     * 错误处理回调
     *
     * @param error
     * @return true:已经handle, false: 统一handle
     */
    protected boolean onError(ApiError error) {
        return false;
    }

    private void handleError(ApiError error) {
        if (rxBus == null) {
            rxBus = RxBus.getInstance();
        }
        rxBus.post(ApiError.class, error);
    }
}

从代码中可以看到,Observable返回的错误会通过onError(Throwable e)方法统一转换成ApiError,onError(ApiError error)方法用于用户进行重载,根据其返回boolean决定是否进行统一 handleError(ApiError error)。handleError()方法会将错误通过RxBus 丢到统一的地方进行处理。

RxBus是我基于RxJava封装的,我会在另外篇幅中论述。

如此,网络接口的调用简化如下:

StudioApiService studioApiService; // 获取StudioApiService示例
studioApiService.getStudioAuth(doctorId).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new HttpResponseObserver<StudioAuth>() {

                    @Override
                    public void onNext(StudioAuth studioAuth) {
                        // 网络请求成功,处理返回数据
                    }

                    @Override
                    protected boolean onError(ApiError error) {
                     // 此处作特殊的Error处理
                      return false; // 再将错误传递到统一错误处理
                    }
                });

Dagger2 依赖注入

跟网络交互相关的Dagger组件依赖关系如下:

基本组件依赖.png

各组件代码如下:

@Scope
@Retention(RUNTIME)
public @interface PerView {
}

@Module
public class NetApiModule {

    @Provides
    @Singleton
    StudioApiService provideStudioApiService(RestApi restApi) {
        return restApi.retrofitStudio(GlobalConfig.STUDIO_API_BASE_URL).create(StudioApiService.class);
    }

    @Provides
    @Singleton
    RxBus provideRxBus() {
        return RxBus.getInstance();
    }
}

@Module(includes = NetApiModule.class)
public class AppModule {
    private final Application application;

    public AppModule(Application app) {
        application = app;
    }

    @Provides
    @Singleton
    Application provideApplication() {
        return application;
    }

    @Provides
    @Singleton
    Context provideContext() {
        return application;
    }
}

@Singleton
@Component(modules = {AppModule.class, AppManageModule.class})
public interface AppComponent {
    void inject(DajiaApplication app);
    StudioApiService studioApiService();
    ...
 }
 
 @Module
public class BaseViewModule {

    private final Activity activity;

    public BaseViewModule(Activity activity) {
        this.activity = activity;
    }

    @Provides
    @PerView
    Activity provideActivity() {
        return this.activity;
    }
}

@PerView
@Component(dependencies = AppComponent.class, modules = BaseViewModule.class)
public interface BaseViewComponent {

    Activity activity();

    void inject(AbstractActivity activity);
    void inject(MainActivity activity);
 }

网络接口使用方法:

在Activity中注入使用

public class AbstractActivity extends AppCompatActivity {

    @Inject
    protected Lazy<StudioApiService> studioApiServiceLazy;
    @Inject
    protected Lazy<FileUploadService> fileUploadServiceLazy;

    public BaseViewComponent component() {
        if (mBaseViewComponent == null) {
            mBaseViewComponent = DaggerBaseViewComponent.builder()
                    .appComponent(DajiaApplication.getInstance().component())
                    .baseViewModule(new BaseViewModule(this))
                    .build();
        }
        return mBaseViewComponent;
    }
}

public class MainActivity extends BasePresenterActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        component().inject(this);
        ...
     }
 
 private void loadData() {
  studioApiServiceLazy.get().getProfile(docId, params).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new HttpResponseObserver<Profile>(rxBus) {
                    @Override
                    public void onNext(Profile profile) {
                         // 处理返回数据
                        }
                    }
                });
 }
}

通过Application注入使用

public class MyApplication extends MultiDexApplication {

    public AppComponent component() {
        if (appComponent == null) {
            appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).appManageModule(new AppManageModule(this)).build();
        }
        return appComponent;
    }
}

在任何地方可以以下方法调用
StudioApiService apiService = DajiaApplication.getInstance().component().studioApiService();

思路梳理

  1. Retrofit构造类构造不同需求下的 Retrofit,配置好HttpClient各种属性;
  2. 以RxJava 来处理返回数据,并进行必要的封装,统一处理网络请求错误;
  3. Dagger2 实现依赖注入,实现在Activity、Fragment等组件中注入网络接口。

这样,结合Dagger2、Retrofit2与RxJava的网路框架就介绍结束了,有些论述可能不够详细,欢迎大家讨论。

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

推荐阅读更多精彩内容