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

Yas*_*maz 15 android dependency-injection dagger-2 retrofit2 rx-java2

嘿,我正在使用Dagger2,Retrofit而且OkHttp我正面临依赖循环问题.

提供时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();
}
Run Code Online (Sandbox Code Playgroud)

提供时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();
}
Run Code Online (Sandbox Code Playgroud)

提供时APIService:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit) {
    return retrofit.create(APIService.class);
}
Run Code Online (Sandbox Code Playgroud)

我的APIService界面:

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

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

}
Run Code Online (Sandbox Code Playgroud)

我的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;

}
Run Code Online (Sandbox Code Playgroud)

我的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()
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*son 21

你的问题是:

  1. 你的OKHttpClient取决于你的身份验证器
  2. 您的身份验证器依赖于改造服务
  3. 改造取决于OKHttpClient(如第1点所述)

因此循环依赖.

这里的一个可能的解决方案是你TokenAuthenticator依赖于 APIServiceHolder而不是APIService.然后,无论是否已经实例化(对象图下方),都TokenAuthenticator可以在配置时将其作为依赖项提供.OKHttpClientAPIService

一个非常简单的APIServiceHolder:

public class APIServiceHolder {

    private APIService apiService;

    @Nullable
    APIService apiService() {
        return apiService;
    }

    void setAPIService(APIService apiService) {
        this.apiService = apiService;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后重构你的TokenAuthenticator:

@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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,检索令牌的代码应该是同步的.这是合同的一部分Authenticator.里面的代码Authenticator将运行关闭主线程.

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

@Provides
@ApplicationScope
apiServiceHolder() {
    return new APIServiceHolder();
}
Run Code Online (Sandbox Code Playgroud)

并重构提供者方法:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit, APIServiceHolder apiServiceHolder) {
    APIService apiService = retrofit.create(APIService.class);
    apiServiceHolder.setAPIService(apiService);
    return apiService;
}
Run Code Online (Sandbox Code Playgroud)

请注意,可变全局状态通常不是一个好主意.但是,如果您的包装井井有条,您可以适当地使用访问修改器以避免持有者的意外使用.

  • 在我的情况下,APIServiceHolder始终返回null。 (2认同)

Tar*_*tar 5

使用LazyDagger 2的接口是这里的解决方案。在您TokenAuthenticator替换APIService mApiLazy<APIService> mApiLazyWrapper

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

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

如果mApiLazyWrapper.get()返回null,也从authenticate方法返回null TokenAuthenticator

  • 这是解决循环依赖错误的 Dagger 方法,因此对于那些想要以这种方式解决问题而不是使用多个 HTTP 客户端的人来说,答案将很有用。 (3认同)