【问题标题】:Set dynamic base url using Retrofit 2.0 and Dagger 2使用 Retrofit 2.0 和 Dagger 2 设置动态基础 url
【发布时间】:2016-07-29 15:07:56
【问题描述】:

我正在尝试使用使用 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();
}

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

我使用惰性注入进行这样的改造

Lazy<Retrofit> retrofit

这样,Dagger 只有在我可以调用时才注入依赖

retrofit.get()

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

我阅读了使用 Dagger 在 Retrofit 上设置动态 url 的不同帖子。在我的情况下,没有什么事情真的很顺利。我想念什么吗?

【问题讨论】:

    标签: android dagger-2 retrofit2


    【解决方案1】:

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

    HostSelectionInterceptormade 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);
    

    【讨论】:

    • 感谢您的回复!我对你提到的第二个选项有一个灰色的想法。我认为第二个选项更简洁,因为它不必拦截从应用程序发出的每个请求。您认为哪个选项值得推荐?
    • 官方推荐是第一个。一种可能的解决方法是第二种。第三个是一个可怕的黑客,但我喜欢反思,哈哈。老实说,与我对 Realm 所做的一样(从范围提供程序注入 RealmHolder),我可能会做 2
    • 但是如果你同时并行运行这两个请求呢?是否会出现错误,因为在第一个示例中两个请求将具有相同的 baseUrl?
    • @IhorKostenko 好吧,我不认为对RetrofitHolder 的访问是线程安全的。
    • 但可以肯定的是,在 url 替换之后,你就有了你的路径。示例:oldurl.com/api/v2/someapi 您想在替换后将域更改为 newurl.com 将是newurl.com,但您可以看到此处不存在 api 路径。对我来说最好的解决方案: final String BASE_URL = "http://{subdomain}.domain.com/ String url = request.url().toString().replace("{domain}", domain); 在你的拦截器中: request = request.newBuilder().url(url).build();
    【解决方案2】:

    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");
    

    更多详情请参考此链接:https://futurestud.io/tutorials/retrofit-2-how-to-use-dynamic-urls-for-requests

    【讨论】:

    • 异常:Url 不能与 GET URL(参数 #1)一起使用
    • @TeeTracker 的问题是关于改造 + DI
    • 这就是我一直在寻找的,当您可以使用带有 @Url 的自定义 url 时非常方便
    • @TeeTracker 您可以使用@GET(NEW_BASE_URL + "/v1/whatever")@GET 和参数@Url String url。不能同时使用。
    • 我认为它只适用于 GET 方法,但是 POST 方法呢?
    【解决方案3】:

    感谢@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);
        }
    }
    

    【讨论】:

    • 哇,你让我的一天变得简单了。
    【解决方案4】:

    使用 Retrofit 2 和 Dagger 2 的动态网址

    您可以使用无范围的提供方法来实例化新对象。

    @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();
    

    【讨论】:

    • 假设这意味着每次使用 LoginAPI 时都“构建”了改造?这是不好的做法吗?
    【解决方案5】:

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

    【讨论】:

    • 也许,你可以使用范围,不确定,但我认为,这是用 Dagger 解决它的一种方法。
    【解决方案6】:

    这可能晚了,但Retrofit 允许您使用动态 URL,同时使用 @Url 注释进行网络调用。 我还使用Dagger2 在我的存储库中注入Retrofit 实例,这个解决方案对我来说很好。

    这将使用基本网址

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

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

    这会忽略基本 url

    并使用您将在运行时提供此调用的 url。

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

    【讨论】:

    • 这不起作用。我收到此错误@Url cannot be used with @GET URL (parameter #1)
    【解决方案7】:

    这在 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()
    

    【讨论】:

    【解决方案8】:

    请查看我的 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();
        }
    

    第四步:

        @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();
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2018-01-02
      • 2016-08-14
      • 2017-03-30
      • 1970-01-01
      • 2022-01-22
      • 2011-01-07
      • 1970-01-01
      • 2013-02-28
      • 1970-01-01
      相关资源
      最近更新 更多