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的網路框架就介紹結束了,有些論述可能不夠詳細,歡迎大家討論。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容