Android Dagger2 + OkHttp + Retrofit依赖循环错误

时间:2017-05-11 11:49:51

标签: android dependency-injection retrofit2 dagger-2 rx-java2

嘿,我正在使用Dagger2RetrofitOkHttp,我正面临依赖循环问题。

提供OkHttp时:

@Provides
@ApplicationScope
OkHttpClient provideOkHttpClient(TokenAuthenticator auth,Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .authenticator(auth)
            .dispatcher(dispatcher)
            .build();
}

提供Retrofit时:

@Provides
@ApplicationScope
Retrofit provideRetrofit(Resources resources,Gson gson, OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

提供APIService时:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit) {
    return retrofit.create(APIService.class);
}

我的APIService界面:

public interface  APIService {
@FormUrlEncoded
@POST("token")
Observable<Response<UserTokenResponse>> refreshUserToken();

--- other methods like login, register ---

}

我的TokenAuthenticator课程:

@Inject
public TokenAuthenticator(APIService mApi,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
    this.mApi= mApi;
    this.mSchedulerProvider=mSchedulerProvider;
    mDisposables=new CompositeDisposable();
}

@Override
public  Request authenticate(Route route, Response response) throws IOException {

    request = null;

    mApi.refreshUserToken(...)
            .subscribeOn(mSchedulerProvider.io())
            .observeOn(mSchedulerProvider.ui())
            .doOnSubscribe(d -> mDisposables.add(d))
            .subscribe(tokenResponse -> {
                if(tokenResponse.isSuccessful()) {
                    saveUserToken(tokenResponse.body());
                    request = response.request().newBuilder()
                            .header("Authorization", getUserAccessToken())
                            .build();
                } else {
                    logoutUser();
                }
            },error -> {

            },() -> {});

    mDisposables.clear();
    stop();
    return request;

}

我的logcat:

Error:(55, 16) error: Found a dependency cycle:
com.yasinkacmaz.myapp.service.APIService is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideTokenAuthenticator(…, mApi, …)
com.yasinkacmaz.myapp.service.token.TokenAuthenticator is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideOkHttpClient(…, tokenAuthenticator, …)
okhttp3.OkHttpClient is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideRetrofit(…, okHttpClient)
retrofit2.Retrofit is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideAPI(retrofit)
com.yasinkacmaz.myapp.service.APIService is provided at
com.yasinkacmaz.myapp.darkvane.components.ApplicationComponent.exposeAPI()

所以我的问题是:我的TokenAuthenticator课程取决于APIService,但我需要在创建TokenAuthenticator时提供APIService。这会导致依赖循环错误。我怎么打败这个,是否有人面临这个问题? 提前谢谢。

4 个答案:

答案 0 :(得分:20)

你的问题是:

  1. 您的OKHttpClient取决于您的身份验证器
  2. 您的身份验证器取决于改装服务
  3. 改造取决于OKHttpClient(如第1点所述)
  4. 因此循环依赖。

    此处的一种可能解决方案是让checkLogin(url: string): Observable<boolean> { if (this.authService.login_data && (this.authService.login_data.userprofile || !this.authService.login_data.need_login)) { return Observable.of(true); // wrap true in Observable } return this.authService.get_login_data() .map( data => { if (data.userprofile || !data.need_login) { return true; } this.authService.redirectUrl = url; this.router.navigate(['/login']); return false; }, error => { this.authService.errMsg = <any>error;} ); 依赖TokenAuthenticator而不是APIServiceHolder。然后,在配置APIService时,可以将TokenAuthenticator作为依赖项提供,无论OKHttpClient(对象图下方是否已经实例化)是否已实例化。

    一个非常简单的APIServiceHolder:

    APIService

    然后重构你的TokenAuthenticator:

    public class APIServiceHolder {
    
        private APIService apiService;
    
        @Nullable
        APIService apiService() {
            return apiService;
        }
    
        void setAPIService(APIService apiService) {
            this.apiService = apiService;
        }
    }
    

    请注意,检索令牌的代码应为同步。这是@Inject public TokenAuthenticator(@NonNull APIServiceHolder apiServiceHolder, @NonNull ImmediateSchedulerProvider schedulerProvider) { this.apiServiceHolder = apiServiceHolder; this.schedulerProvider = schedulerProvider; this.disposables = new CompositeDisposable(); } @Override public Request authenticate(Route route, Response response) throws IOException { if (apiServiceHolder.get() == null) { //we cannot answer the challenge as no token service is available return null //as per contract of Retrofit Authenticator interface for when unable to contest a challenge } request = null; TokenResponse tokenResponse = apiServiceHolder.get().blockingGet() if (tokenResponse.isSuccessful()) { saveUserToken(tokenResponse.body()); request = response.request().newBuilder() .header("Authorization", getUserAccessToken()) .build(); } else { logoutUser(); } return request; } 合同的一部分。 Authenticator内的代码将运行关闭主线程。

    当然,您需要编写相同的Authenticator方法:

    @Provides

    重构提供者方法:

    @Provides
    @ApplicationScope
    apiServiceHolder() {
        return new APIServiceHolder();
    }
    

    请注意,可变全局状态通常不是一个好主意。但是,如果您的软件包组织得很好,您可以适当地使用访问修饰符,以避免持有者的意外使用。

答案 1 :(得分:4)

使用Dagger 2的Lazy接口是这里的解决方案。 在您的TokenAuthenticator中,将APIService mApi替换为Lazy<APIService> mApiLazyWrapper

@Inject
public TokenAuthenticator(Lazy<APIService> mApiLazyWrapper,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
    this.mApiLazyWrapper= mApiLazyWrapper;
    this.mSchedulerProvider=mSchedulerProvider;
    mDisposables=new CompositeDisposable();
}

要从包装器中获取APIService实例,请使用mApiLazyWrapper.get()

如果mApiLazyWrapper.get()返回null,则也从authenticate的{​​{1}}方法返回null。

答案 2 :(得分:1)

非常感谢@Selvin和@David。我有两种方法,其中一种是David's answer,另一种是:

创建另一个OkHttpRetrofit或另一个库,用于处理TokenAuthenticator类内的操作。

如果您想使用其他OkHttpRetrofit实例,则必须使用限定符注释。

例如:

@Qualifier
public @interface ApiClient {}


@Qualifier
public @interface RefreshTokenClient {}

然后提供:

@Provides
@ApplicationScope
@ApiClient
OkHttpClient provideOkHttpClientForApi(TokenAuthenticator tokenAuthenticator, TokenInterceptor tokenInterceptor, Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .authenticator(tokenAuthenticator)
            .addInterceptor(tokenInterceptor)
            .dispatcher(dispatcher)
            .build();
}

@Provides
@ApplicationScope
@RefreshTokenClient
OkHttpClient provideOkHttpClientForRefreshToken(Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .dispatcher(dispatcher)
            .build();
}

@Provides
@ApplicationScope
@ApiClient
Retrofit provideRetrofitForApi(Resources resources, Gson gson,@ApiClient OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

@Provides
@ApplicationScope
@RefreshTokenClient
Retrofit provideRetrofitForRefreshToken(Resources resources, Gson gson,@RefreshTokenClient OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

然后我们可以提供我们的分离界面:

@Provides
@ApplicationScope
public APIService provideApi(@ApiClient Retrofit retrofit) {
    return retrofit.create(APIService.class);
}

@Provides
@ApplicationScope
public RefreshTokenApi provideRefreshApi(@RefreshTokenClient Retrofit retrofit) {
    return retrofit.create(RefreshTokenApi.class);
}

提供TokenAuthenticator时:

@Provides
@ApplicationScope
TokenAuthenticator provideTokenAuthenticator(RefreshTokenApi mApi){
    return new TokenAuthenticator(mApi);
}

优点:您有两个独立的api接口,这意味着您可以独立维护它们。您也可以使用普通OkHttpHttpUrlConnection或其他图书馆。

缺点:您将拥有两个不同的OkHttp和Retrofit实例。

P.S:确保在Authenticator类中进行同步调用。

答案 3 :(得分:0)

您可以通过Lazy类型将服务依赖项注入到身份验证器中。这样,您将避免对实例化的周期性依赖。

检查此link上的Lazy的工作方式。