为什么使用Retrofit?
我在《如何使用Retrofit请求非Restful API》 前言 提到过HttpClient、OkHttp、Retrofit历史。Retrofit无缝衔接RxJava,请求结果解析都不需要手动写,用Gson处理json->Object
。
总的来说,就是
- 更清晰的结构
- 更简洁的代码
拒绝Retrofit的心态
- 旧代码不好改
- 担心Retrofit不能满足需求
- 自己写的代码比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<List<Repo>> 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<List<Repo>> 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<String, Object> 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<String, Object> 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();
}
}
写代理层
大概就是这几个类
MyRetrofit
public class MyRetrofit {
public <T> T create(Class<T> 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<T> {
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<String, Object> 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<T> {
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<User> loadUserO(@Query("key") String value);
}
然后,只需要:
新增接口CallAdapter
public interface CallAdapter<T> {
T adapt(HttpCall call);
}
新增RxCallAdapter
public class RxCallAdapter<T> implements CallAdapter<Observable<T>>{
@Override
public Observable<T> adapt(final HttpCall call) {
return Observable
.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> 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<User>() {
@Override
public void call(User user) {
if (callback != null) {
callback.onLoaded(user);
}
}
});
}
跟Retrofit使用方式一模一样了!
依赖关系:
单元测试
使用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<User> 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
可以写得更解耦,例如Converter
、CallAdapter
都从外面传进来。
public class MyRetrofit {
......
public Converter converter(Type returnType) {
return new GsonConverter(new Gson(), returnType);
}
public CallAdapter callAdapter() {
return new RxCallAdapter();
}
}
可以参考Retrofit,MyRetrofit
用Builder模式构建会更好,此处就不累赘了。
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单元测试 经验分享》
关于作者
我是键盘男。
在广州生活,在创业公司上班,猥琐文艺码农。喜欢科学、历史,玩玩投资,偶尔独自旅行。希望成为独当一面的工程师。