同事拒绝Retrofit,怎么办?

同事拒绝Retrofit,怎么办?_第1张图片
photo-1449452198679-05c7fd30f416_副本.jpg

为什么使用Retrofit?

我在《如何使用Retrofit请求非Restful API》 前言 提到过HttpClient、OkHttp、Retrofit历史。Retrofit无缝衔接RxJava,请求结果解析都不需要手动写,用Gson处理json->Object

总的来说,就是

  • 更清晰的结构
  • 更简洁的代码

拒绝Retrofit的心态

  1. 旧代码不好改
  2. 担心Retrofit不能满足需求
  3. 自己写的代码比Retrofit屌

1)

我猜大部分人都是第一种情况。项目做到一定程度,http层逻辑跟别的类耦合了,换Retrofit要大动干戈,老板不管你代码重构有的没的,项目催得紧......

2)

第二种情况,也是很头疼的问题。很多公司不一定遵循Restful Api准则写接口,新手对如何修改Retrofit代码无从入手。叹息一下,还是用回旧代码吧。

对于这种情况,希望《如何使用Retrofit请求非Restful API》 对你有帮助!

3)

不用犹豫,拿砖头拍死那个同事......


Retrofit官网例子:

http://square.github.io/retrofit/

Retrofit turns your HTTP API into a Java interface.

public interface GitHubService {
  @GET("users/{user}/repos")
  Call> listRepos(@Path("user") String user);
}

The Retrofit class generates an implementation of the GitHubService interface.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.

Call> repos = service.listRepos("octocat");

即使从来未接触Retrofit的人,一看GitHubService listRepos方法,就了解这个api大概意思,url是什么,传什么参数。


思路:代理旧http代码

我们目的是写到好像Retrofit那样。Retrofit本身就运用了代理模式隐藏了请求的细节,因此,我们就模仿它写一个自己的代理层

旧http使用方式

public loadUser(String value, Callback callback) {
        new Thread(new Runnerable(){
            @Override
            public void run(){
                MyHttpClient httpClient = new MyHttpClient();

                Map params = new HashMap<>();
                params.put("key", value);

                String json = httpClient.get("user/kkmike999.json", params);

                User user = new Gson().fromJson(json, User.class);
        
                if (callback != null) {
                    callback.onLoaded(user);
                }
            }
        }).start();
}
MyHttpClient
public class MyHttpClient {

    public String get(String path, Map params) throws IOException {
        OkHttpClient client = new OkHttpClient();

        // "http://kkmike999-file.b0.upaiyun.com/" + path
        HttpUrl.Builder urlBuilder = new HttpUrl.Builder().scheme("http")
                                                          .host("kkmike999-file.b0.upaiyun.com")
                                                          .addPathSegment(path);

        for (String key : params.keySet()) {
            urlBuilder.addQueryParameter(key, params.get(key)
                                                    .toString());
        }

        HttpUrl httpUrl = urlBuilder.build();

        Request request = new Request.Builder().url(httpUrl)
                                               .build();

        Response response = client.newCall(request)
                                  .execute();
        return response.body()
                       .string();
    }
}

写代理层

大概就是这几个类

同事拒绝Retrofit,怎么办?_第2张图片
Image 2.png
MyRetrofit
public class MyRetrofit {

    public  T create(Class clazz) {
        if (!clazz.isInterface()) {
            throw new RuntimeException("Service not interface");
        }

        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new ServiceProxy());
    }

}
ServiceProxy
public class ServiceProxy implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }

        ServiceMethod serviceMethod = new ServiceMethod(method);
        Converter     converter     = new Converter(new Gson(), serviceMethod.returnType);

        HttpCall call = new HttpCall(serviceMethod, converter, args);

        return call.request();
    }
}
HttpCall
public class HttpCall {

    MyHttpClient  client;
    Converter     converter;
    ServiceMethod serviceMethod;
    Object[]      args;

    public HttpCall(ServiceMethod serviceMethod, Converter converter, Object[] args) {
        this.client = new MyHttpClient();
        
        this.converter = converter;
        this.serviceMethod = serviceMethod;
        this.args = args;
    }

    public T request() {
        try {
            // 参数count与注释count不一致, 抛错
            int argumentCount = args != null ? args.length : 0;
            if (argumentCount != serviceMethod.argumentTypes.length) {
                throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + serviceMethod.argumentTypes.length + ")");
            }

            // 参数
            Map params = new HashMap<>();

            for (int p = 0; p < argumentCount; p++) {
                params.put(serviceMethod.getQueryKey(p), args[p].toString());
            }

            String url = serviceMethod.url();

            String json = client.get(url, params);

            return (T) converter.convert(json);

        } catch (IOException e) {
            e.printStackTrace();

            throw new RuntimeException(e.getMessage());// 请求失败
        }
    }
}
ServiceMethod:
protected Method         method;
    protected Annotation[]   methodAnnotations;
    protected Annotation[][] argumentAnnotations;
    protected Class[]        argumentTypes;
    protected Type           returnType;

    public ServiceMethod(Method method) {
        this.method = method;

        methodAnnotations = method.getDeclaredAnnotations();
        argumentAnnotations = method.getParameterAnnotations();
        argumentTypes = method.getParameterTypes();
        returnType = method.getGenericReturnType();
    }

    /** 
     * 从@Query注释,获取请求参数的key
     */
    public String getQueryKey(int index) {
        for (Annotation annotation : argumentAnnotations[index]) {
            if (annotation instanceof Query) {
                return ((Query) annotation).value();
            }
        }

        return "";
    }

    /**
     * 从@Get注释中,获取url 
     */
    public String url() {
        for (Annotation annotation : methodAnnotations) {
            if (annotation instanceof Get) {
                return ((Get) annotation).value();
            }
        }

        throw new RuntimeException("no GET or POST annotation");
    }
Converter
public class Converter {

    TypeAdapter adapter;

    public Converter(Gson gson, Type type) {
        adapter = gson.getAdapter(TypeToken.get(type));
    }

    T convert(String json) throws IOException {
        return (T) adapter.fromJson(json);
    }
}

最好Converter就抽象成一个接口,你不喜欢用Gson,用fastjson,可以随时切换。

@GET
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {
    String value() default "";
}
@Query
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
    String value() default "";
}

新Api使用方式

json数据:
{
    "uid":1,
    "name":"kkmike999"
}
UserApi
public interface UserApi {

    @Get("user/kkmike999.json")
    User loadUser(@Query("key") String value);
}
public void loadUser(String value, Callback callback) {
        new Thread(new Runnerable(){
            @Override
            public void run(){
                UserApi userApi = new MyRetrofit().create(UserApi.class);

                User user = userApi.loadUser("test");
        
                if (callback != null) {
                    callback.onLoaded(user);
                }
            }
        }).start();
}

调用方式,99%像Retrofit,实际底层http,还是旧代码。


配合RxJava

现在很多开发者都离不开RxJava了。“Retrofit+RxJava简直无敌” 这种想法恐怕不仅仅是我有吧?

你的UserApi应该是这样的:

public interface UserApi {

    @Get("user/kkmike999.json")
    Observable loadUserO(@Query("key") String value);
}

然后,只需要:

新增接口CallAdapter

public interface CallAdapter {

    T adapt(HttpCall call);
}
新增RxCallAdapter
public class RxCallAdapter implements CallAdapter>{

    @Override
    public Observable adapt(final HttpCall call) {
        return Observable
                    .create(new Observable.OnSubscribe() {
                        @Override
                        public void call(Subscriber subscriber) {
                            try {
                                subscriber.onNext((T) call.request());
                                subscriber.onCompleted();
                            } catch (Exception e) {
                                subscriber.onError(e);
                            }
                        }
                    })
                    .subscribeOn(Schedulers.io());
    }
}
修改ServiceProxy
public class ServiceProxy implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }

        ServiceMethod serviceMethod = new ServiceMethod(method);
        Converter     converter     = new Converter(new Gson(), serviceMethod.returnType);
        CallAdapter   adapter       = new RxCallAdapter();
        
        HttpCall call = new HttpCall(serviceMethod, converter, args);

        return adapter.adapt(call);
    }
}
修改ServiceMethod
public class ServiceMethod{

    ......

    public ServiceMethod(Method method) {
        ...

        returnType = method.getGenericReturnType();

        if (returnType instanceof ParameterizedType) {
            // Obserable 里面的type
            ParameterizedType parameterizedType = (ParameterizedType) returnType;

            returnType  = parameterizedType.getActualTypeArguments()[0];
        }
    }
    
    ......
}

RxJava Api调用方式

public void loadUser(String value, Callback callback){
    userApi.loadUserO("test")
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(new Action1() {
                   @Override
                   public void call(User user) {
                        if (callback != null) {
                            callback.onLoaded(user);
                        }
                   }
               });
}

Retrofit使用方式一模一样了!

依赖关系

同事拒绝Retrofit,怎么办?_第3张图片
依赖关系.png

单元测试

使用Retrofit最终目的,就是让我们更好地单元测试!

上面的代码,都没提到UserPresenter,这是担心代码量太多。懂MVP的同学应该一看就明白。

public class UserPresenter {
    UserApi  userApi;
    UserView userView;
    
    public void loadUser(String value){...}
}
public class UserPresenterTest {

    @Mock
    UserApi              userApi;
    @Mock
    UserView             userView;
    @Captor
    ArgumentCaptor captor;

    UserPresenter userPresenter;

    @Before
    public void setUp() throws Exception {
        RxJavaPlugins.getInstance()
                     .registerSchedulersHook(new RxJavaSchedulersHook() {
                         @Override
                         public Scheduler getIOScheduler() {
                             // io scheduler会新建线程,把io scheduler->immediate scheduler, 异步->同步
                             return Schedulers.immediate();
                         }
                     });

        MockitoAnnotations.initMocks(this);

        userPresenter = new UserPresenter(userApi, userView);
    }

    @Test
    public void loadUser() throws InterruptedException {
        User user = new User();
        user.uid = 1;
        user.name = "kkmike999";

        when(userApi.loadUser("test")).thenReturn(Observable.just(user));

        userPresenter.loadUser("test");

        verify(userView).onUserLoaded(captor.capture());

        User result = captor.getValue();

        Assert.assertEquals(result.uid, 1);
        Assert.assertEquals(result.name, "kkmike999");
    }
}

改进

其实ServiceProxy可以写得更解耦,例如ConverterCallAdapter都从外面传进来。

public class MyRetrofit {
    ......

    public Converter converter(Type returnType) {
        return new GsonConverter(new Gson(), returnType);
    }

    public CallAdapter callAdapter() {
        return new RxCallAdapter();
    }
}

可以参考RetrofitMyRetrofitBuilder模式构建会更好,此处就不累赘了。

public class ServiceProxy implements InvocationHandler {
    MyRetrofit retrofit;

    public ServiceProxy(MyRetrofit retrofit) {
        this.retrofit = retrofit;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
        ....

        Converter   converter = retrofit.converter(serviceMethod.actualType);
        CallAdapter adapter   = retrofit.callAdapter();

        HttpCall call = new HttpCall(client, serviceMethod, converter, args);

        return adapter.adapt(call);
    }
}

Java Demo:https://git.oschina.net/kkmike999/ImitationRetrofit.git


结语

无论你的同事出于什么理由,不想使用Retrofit,迁就一下也无妨,除非原来的代码真的一团糟。也许他只是未研究Retrofit,不想冒无谓的风险。

放下砖头,立地成佛

当你自己写一套代理,就可以享受Retrofit的美妙设计模式,同时也不违背原来代码(如果同事连你写代理都不赞同,拿砖头拍死他)。这样我们就能更好地单元测试。(可能你同事还没认识到单元测试重要性)当他看到你行云流水的代码,或许某天他就用你写的这个代理了。

关于MPV、Retrofit、单元测试,请参考《(MVP+RxJava+Retrofit)解耦+Mockito单元测试 经验分享》


关于作者

我是键盘男。
在广州生活,在创业公司上班,猥琐文艺码农。喜欢科学、历史,玩玩投资,偶尔独自旅行。希望成为独当一面的工程师。

你可能感兴趣的:(同事拒绝Retrofit,怎么办?)