问题背景
Retrofit大家都不陌生,一般情况下,我们都会在组件(Actvity, Fragment)生命周期结束的时候去cancel掉网络请求。这样做当然无可厚非,但是有一点麻烦的是,要定义一个变量来引用这个Call,网络请求少的情况下还好,假如一个类中有很多Call时候呢?单是要给Call命名都让我头大了…
解决思路
可能最容易想到的就是:在Activity中定义一个集合来保存所有的Call,在onDestroy的时候遍历所有Call,调用cancel方法。
这个方案虽然能达到效果,但是每次都要手动把Call add到集合中,不仅繁琐而且还可能会忘
后来有一位小伙伴为了解决上面提到的缺陷,自己定义了一个类,代码大概如下:
public class RetrofitManager {
private final List<Call> mCallList;
public RetrofitManager() {
mCallList = new ArrayList<>();
}
public <T> void call(Call<T> call, Callback<T> callback) {
add(call);
call.enqueue(callback);
}
public <T> Response<T> call(Call<T> call) throws IOException {
add(call);
return call.execute();
}
public void cancelAll() {
synchronized (mCallList) {
Iterator<Call> iterator = mCallList.iterator();
while (iterator.hasNext()) {
Call call = iterator.next();
if (call == null || call.isCanceled()) {
continue;
}
call.cancel();
iterator.remove();
}
}
}
private void add(Call call) {
synchronized (mCallList) {
mCallList.add(call);
}
}
}
使用的时候将Call和Callback作为参数传进去即可。
额…怎么说呢,这样确实能解决上面所提到的缺陷,只不过这样的调用方式与开发者原来的习惯不同,看起来很别扭。
OK,我们还是直接步入正轨吧…
其实我们想做的就是在Call请求执行前,将其加入到集合中,但是又不想打破原有的调用习惯。那为何不试试代理模式呢?
假设有接口 UserAPI.java 如下:
public interface UserAPI {
@GET("v1/user/fan")
Call<ResponseBody> getFanList(@Query("page_index") int pageIndex, @Query("page_size") int pageSize);
@GET("v1/user/focus")
Call<ResponseBody> getFocusList(@Query("page_index") int pageIndex, @Query("page_size") int pageSize);
}
我们定义一个 UserAPI.java 的静态代理类 UserAPIInvokeProxy.java
public class UserAPIInvokeProxy implements UserAPI {
private UserAPI mInterfaceImpl;
private List<Call> mCallList = Collections.synchronizedList(new ArrayList<Call>());
public UserAPIInvokeProxy(UserAPI apiImplGeneratedByRetrofit) {
mInterfaceImpl = apiImplGeneratedByRetrofit;
}
public Call<ResponseBody> getFanList(int pageIndex, int pageSize) {
Call<ResponseBody> call = mInterfaceImpl.getFanList(pageIndex, pageSize);
mCallList.add(call);
return call;
}
public Call<ResponseBody> getFocusList(int pageIndex, int pageSize) {
Call<ResponseBody> call = mInterfaceImpl.getFocusList(pageIndex, pageSize);
mCallList.add(call);
return call;
}
//额外提供一个方法用于取消所有Call
public void cancelAll(Call... excludes) {
if (excludes.length > 0) {
mCallList.removeAll(Arrays.asList(excludes));
}
if (mCallList != null) {
for (Call call : mCallList) {
if (call != null && !call.isCanceled()) {
call.cancel();
}
}
mCallList.clear();
mCallList = null;
}
}
}
使用的时候,将Retrofit所创建的接口实例作为参数传入,让我们的代理类对其进行代理,之后所有的调用都通过代理类来完成。
可能有小伙伴要怼我了。调用习惯是保持一致了,但是这代码量还不如直接用上面2种方案呢… 而且每新增一个方法,都要对代理类进行改动,真的好蠢。
别开枪…我有办法。
对于这种机械式的代码,就应该考虑让编译时注解来解放你双手了!
是不是想到了Butterknife了?
没错,我们要编写一个编译时注解来帮我们完成这种机械式的代码。
最终达到的效果是:只要在 UserAPI.java 中用 @RetrofitInterface 表示这个类需要生成代理类,就会在build的时候自动生成代理类 UserAPIInvokeProxy.java
@RetrofitInterface
public interface UserAPI {
@GET("v1/user/fan")
Call<ResponseBody> getFanList(@Query("page_index") int pageIndex, @Query("page_size") int pageSize);
@GET("v1/user/focus")
Call<ResponseBody> getFocusList(@Query("page_index") int pageIndex, @Query("page_size") int pageSize);
}
然而,这篇文章并不会讲如何编写编译时注解,因为不在范畴内。本文的目的主要是想分享下问题的解决思路。如果对 RetrofitInterface 的解析器源码感兴趣的话,可以点这里传送门
当然我也封装成了一个库,想直接使用的小伙伴可以按照如下指引:
小结
其实很多时候,我们会避重就轻,习惯性地忽略比较复杂的解决方案,这是人之常情。只是当你尝试了多种方案还没有结果的时候,不要忘记了这种复杂方案本来的可行性。
另外,设计模式是一门必修课,它能在你解决问题的时候给你一种思路,让你不会走太多弯路。
End.