使用 Retrofit 2.0 和 Dagger 2 设置动态基础 url

Set dynamic base url using Retrofit 2.0 and Dagger 2

我正在尝试使用 Dagger 2 使用 Retrofit 2.0 执行登录操作

下面是我设置 Retrofit 依赖的方法

@Provides
@Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient client) {
    Retrofit retrofit = new Retrofit.Builder()
                            .addConverterFactory(GsonConverterFactory.create(gson)
                            .client(client)
                            .baseUrl(application.getUrl())
                            .build();
    return retrofit;     
}

这是 API 界面。

interface LoginAPI {
   @GET(relative_path)
   Call<Boolean> logMe();
}

我有三个不同的 urls 用户可以登录。所以我无法在设置 Retrofit 依赖项时设置静态 url。我在 Application class 上创建了 setUrl() 和 getUrl() 方法。用户登录后,我在调用 API 调用之前将 url 设置到 Application 上。

我这样使用延迟注入进行改造

Lazy<Retrofit> retrofit

这样,Dagger 仅在我可以调用时注入依赖关系

retrofit.get()

这部分效果很好。我将 url 设置为改造依赖项。但是,当用户输入错误的基数 url(比如 mywifi.domain.com)时,问题就出现了,理解它是错误的并改变它(比如 mydata.domain.com)。由于 Dagger 已经为 retrofit 创建了依赖,所以它不会再做。 所以我必须重新打开应用程序并输入正确的 url.

我阅读了有关使用 Dagger 在 Retrofit 上设置动态 urls 的不同帖子。就我而言,没有什么能真正奏效。我想念什么吗?

Support for this use-case was removed in Retrofit2. The recommendation is to use an OkHttp interceptor instead.

HostSelectionInterceptor made by swankjesse

import java.io.IOException;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;

/** An interceptor that allows runtime changes to the URL hostname. */
public final class HostSelectionInterceptor implements Interceptor {
  private volatile String host;

  public void setHost(String host) {
    this.host = host;
  }

  @Override public okhttp3.Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    String host = this.host;
    if (host != null) {
      //HttpUrl newUrl = request.url().newBuilder()
      //    .host(host)
      //    .build();
      HttpUrl newUrl = HttpUrl.parse(host);
      request = request.newBuilder()
          .url(newUrl)
          .build();
    }
    return chain.proceed(request);
  }

  public static void main(String[] args) throws Exception {
    HostSelectionInterceptor interceptor = new HostSelectionInterceptor();

    OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(interceptor)
        .build();

    Request request = new Request.Builder()
        .url("http://www.coca-cola.com/robots.txt")
        .build();

    okhttp3.Call call1 = okHttpClient.newCall(request);
    okhttp3.Response response1 = call1.execute();
    System.out.println("RESPONSE FROM: " + response1.request().url());
    System.out.println(response1.body().string());

    interceptor.setHost("www.pepsi.com");

    okhttp3.Call call2 = okHttpClient.newCall(request);
    okhttp3.Response response2 = call2.execute();
    System.out.println("RESPONSE FROM: " + response2.request().url());
    System.out.println(response2.body().string());
  }
}

或者您可以替换您的 Retrofit 实例(并可能将实例存储在 RetrofitHolder 中,您可以在其中修改实例本身,并通过 Dagger 提供持有者)...

public class RetrofitHolder {
   Retrofit retrofit;

   //getter, setter
}

或者重新使用您当前的 Retrofit 实例并通过反射破解新的 URL,因为违反了规则。 Retrofit 有一个 baseUrl 参数,它是 private final,因此您只能通过反射访问它。

Field field = Retrofit.class.getDeclaredField("baseUrl");
field.setAccessible(true);
okhttp3.HttpUrl newHttpUrl = HttpUrl.parse(newUrl);
field.set(retrofit, newHttpUrl);

Retrofit2 库带有一个 @Url 注释。您可以像这样覆盖 baseUrl

API接口:

public interface UserService {  
    @GET
    public Call<ResponseBody> profilePicture(@Url String url);
}

然后这样调用 API:

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("https://s3.amazon.com/profile-picture/path");

有关详细信息,请参阅此 link:https://futurestud.io/tutorials/retrofit-2-how-to-use-dynamic-urls-for-requests

感谢@EpicPandaForce 的帮助。如果有人遇到 IllegalArgumentException,这是我的工作代码。

public class HostSelectionInterceptor implements Interceptor {
    private volatile String host;

    public void setHost(String host) {
        this.host = HttpUrl.parse(host).host();
    }

    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        String reqUrl = request.url().host();

        String host = this.host;
        if (host != null) {
            HttpUrl newUrl = request.url().newBuilder()
                .host(host)
                .build();
            request = request.newBuilder()
                .url(newUrl)
                .build();
        }
        return chain.proceed(request);
    }
}

动态 url 使用 Retrofit 2 和 Dagger 2

您可以使用 un-scoped 提供方法实例化新对象。

@Provides
LoginAPI provideAPI(Gson gson, OkHttpClient client, BaseUrlHolder baseUrlHolder) {
    Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)
                        .client(client)
                        .baseUrl(baseUrlHolder.get())
                        .build();
    return retrofit.create(LoginAPI.class);     
}

@AppScope
@Provides
BaseUrlHolder provideBaseUrlHolder() {
    return new BaseUrlHolder("https://www.default.com")
}

public class BaseUrlHolder {
    public String baseUrl;

    public BaseUrlHolder(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }
}

现在您可以通过从组件中获取 baseUrlHolder 来更改基 url

App.appComponent.getBaseUrlHolder().set("https://www.changed.com");
this.loginApi = App.appComponent.getLoginApi();

对于最新的 Retrofit 库,您可以简单地使用单例实例并将其更改为 retrofitInstance.newBuilder().baseUrl(newUrl)。无需创建另一个实例。

这可能会迟到,但 Retrofit 允许您在使用 @Url 注释进行网络调用时使用动态 URL。 我还使用 Dagger2 在我的存储库中注入 Retrofit 实例,这个解决方案对我来说工作正常。

这将使用基数 url

由您在创建 Retrofit 实例时提供。

@GET("/product/123")
fun fetchDataFromNetwork(): Call<Product>

这里忽略基数 url

并使用 url 您将在 运行 时间提供此呼叫。

@GET()
fun fetchDataFromNetwork(@Url url : String): Call<Product> //

这在 Kotlin 中对我有用

class HostSelectionInterceptor: Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        var request = chain.request()

        val host: String = SharedPreferencesManager.getServeIpAddress()

        val newUrl = request.url().newBuilder()
            .host(host)
            .build()

        request = request.newBuilder()
            .url(newUrl)
            .build()

        return chain.proceed(request)
    }

}

将拦截器添加到 OkHttpClient 构建器

val okHttpClient = OkHttpClient.Builder()
                .addInterceptor(HostSelectionInterceptor())
                .cache(null)
                .build()

请查看我的 Dagger 动态变通方法 URL。

第一步:创建拦截器

import android.util.Patterns;

import com.nfs.ascent.mdaas.repo.network.ApiConfig;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class DomainURLInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();

        String requestUrl = original.url().toString();
        String PROTOCOL = "(?i:http|https|rtsp)://";
        String newURL = requestUrl.replaceFirst(PROTOCOL, "")
                .replaceFirst(Patterns.DOMAIN_NAME.toString(), "");
        newURL = validateBackSlash(newURL) ? ApiConfig.BASE_URL.concat(newURL) : newURL.replaceFirst("/", ApiConfig.BASE_URL);
        original = original.newBuilder()
                .url(newURL)
                .build();

        return chain.proceed(original);
    }

    private boolean validateBackSlash(String str) {
        if (!str.substring(str.length() - 1).equals("/")) {
            return true;
        }
        return false;
    }

}

第 2 步:

在模块中添加新创建的拦截器

    @Provides
    @Singlton
    DomainURLInterceptor getChangeURLInterceptor() {
        return new DomainURLInterceptor();
    }

第 3 步: 将拦截器添加到 HttpClient 拦截器列表中

    @Provides
    @Singlton
    OkHttpClient provideHttpClient() {
        return new OkHttpClient.Builder()
                .addInterceptor(getChangeURLInterceptor())
                .readTimeout(ApiConfig.API_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                .connectTimeout(ApiConfig.API_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                .build();
    }

第 4 步:

    @Provides
    @Singlton
    Retrofit provideRetrofit() {
        return new Retrofit.Builder()
                .baseUrl(ApiConfig.BASE_URL) // this is default URl,
                .addConverterFactory(provideConverterFactory())
                .client(provideHttpClient())
                .build();
    }

注意:如果用户必须从设置中更改基础 URL,请记住使用以下方法验证新创建的 URL:

    public final static boolean isValidUrl(CharSequence target) {
        if (target == null) {
            return false;
        } else {
            return Patterns.WEB_URL.matcher(target).matches();
        }
    }