霸气的名字 2020-06-23
背景:Android开发时,需要两个平台切换,项目中网络请求是用的Retrofit这块,发现没有直接切换的接口,百度一下,大部分直接是通过拦截器里面进行切换,说Retrofit中baseUrl是final类型,不能反射修改。通过测试后发现final值是可以修改的(包括基本类型),但是final值的初始化要在构造方法中赋值。
解决思路:
+. 直接替换掉Retrofit中的baseUrl
public final class Retrofit { private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>(); final okhttp3.Call.Factory callFactory; final HttpUrl baseUrl; final List<Converter.Factory> converterFactories; final List<CallAdapter.Factory> callAdapterFactories; final @Nullable Executor callbackExecutor; final boolean validateEagerly;
反射替换代码如下:
//baseUrl:切换的url //retrofit:对于的Retrofit实例 HttpUrl httpUrl = HttpUrl.parse(baseUrl); Field field = retrofit.getClass().getDeclaredField("baseUrl"); field.setAccessible(true); field.set(retrofit, httpUrl);
单点可以跟踪查看,retrofit中的baseUrl确实已经替换成功,但是去请求数据时,发现还是之前的数据。
具体原因跟踪源码:
ServiceMethod<?, ?> loadServiceMethod(Method method) { ServiceMethod<?, ?> result = serviceMethodCache.get(method); if (result != null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = new ServiceMethod.Builder<>(this, method).build(); serviceMethodCache.put(method, result); } } return result; }
Retrofit会根据Method缓存请求,如果之前请求过,就是你切换了baseUrl,请求同个Method,那还是之前的,对于这样,把对应的缓存清理下就行。
field = retrofit.getClass().getDeclaredField("serviceMethodCache"); field.setAccessible(true); Map serviceMethodCache = (Map) field.get(retrofit); serviceMethodCache.clear();
再测试下,可以成功切换。
完整代码如下:
public static void baseUrl(Retrofit retrofit, String baseUrl) throws Exception { HttpUrl httpUrl = HttpUrl.parse(baseUrl); Field field = retrofit.getClass().getDeclaredField("baseUrl"); field.setAccessible(true); field.set(retrofit, httpUrl); field = retrofit.getClass().getDeclaredField("serviceMethodCache"); field.setAccessible(true); Map serviceMethodCache = (Map) field.get(retrofit); serviceMethodCache.clear(); }
当网络请求正在进行时,这种暴力切换方式,可能会有些意想不到的问题,至于其它问题,后续发现追加。