本文是围绕Retrofit2.1版本来讲的,一些老版本API和新版本不一样了,就不讲了,与时俱进。
Retrofit是Square公司开发的,大神是JakeWharton。官网对其描述是:
a type-safe REST client for Android and Java.
你可以使用注解去描述一个HTTP请求、Url参数替换、查询参数。另外也支持文件上传等。
如何引用:
dependencies {
// Retrofit & OkHttp
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
}
第一个是Retrofit库,第二个是Gson库(用来将string转换成json的库),在retrofit中默认使用gson这个converter来转换response,后面会解释converter。
开始使用:
例如我现在要发出一个请求,url如下:
http://10.240.88.201:8080/repos/{owner}/contributors?loginname={name}
在后面路径中有{owner},这是一个替换位,这个值非固定,传不同的owner则返回不同的值。返回的是一个jsonArray,并且是一个get请求。在参数中,有一对键值对,{name}也是替换位,传入不同参数则返回不同值。
那看下retrofit怎么解决这个问题:
public class ServiceGenerator {
public static final String API_BASE_URL = "http://10.240.88.201:8080/";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
public static <S> S createService(Class<S> serviceClass) {
Retrofit retrofit = builder.client(httpClient.build()).build();
return retrofit.create(serviceClass);
}
}
public interface RequestClient {
@GET("/repos/{owner}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Query("loginname") String loginname
);
class Contributor {
String login;
int contributions;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//发起网络请求
RequestClient client = ServiceGenerator.createService(RequestClient.class);
Call<List<Contributor>> call = client.contributors("company", "lxx");
call.enqueue(new Callback<List<Contributor>>() {
@Override
public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
List<Contributor> contributors = response.body();
Toast.makeText(MainActivity.this, contributors.size() + "", Toast.LENGTH_LONG).show();
}
@Override
public void onFailure(Call<List<Contributor>> call, Throwable t) {
Log.e("tag", "------------onFailure:");
t.printStackTrace();
}
});
}
}
如果服务器返回的值为:
[
{
"login":"ssss",
"contributions":1
},
{
"login":"ssss",
"contributions":2
}
]
则在界面上会弹出toast,显示2。
现在来解释下,在ServiceGenerator中使用Builder创建一个新的REST client,并且基于API_BASE_URL。Retrofit其实是对OkHttp库的封装,简化里面参数的封装以及数据转换,所以在创建Retrofit的时候,还需要传入OkHttpClient。如对OkHTTP不熟悉的话,可以看OkHttp完全解析
和OkHttp不同的是,Retrofit需要定义一个借口,用来返回我们的Call对象,这个Call对象可不是OkHttp中的Call,而是定义在Retrofit中的Call。
这里我们定义的是RequestClient这个接口,在这个接口中定义了一个函数contributors。看下修饰该函数的注解,首先用了注解@GET来标识这是一个GET请求,当然你也可以使用@POST。在@GET括号中的是该请求的具体路径,如果在路径中出现{xxx}这样的格式,则说明该处为替换位,可以在函数参数中看到,有注解@Path(“owner”)来标识该处是用来替换的。
而@Query参数是用来标示参数键值对的。
在MainActivity中我们调用了该接口的contributor(“company”,”lxx”);,那么我们发出请求的url便是:
http://10.240.88.201:8080/repos/company/contributors?loginname=lxx
这样一看Retrofit是不是很方便,MainActivity在创建了Call之后,就会去请求,调用的方法和OkHttp的Call一样。其实我们仔细看Retrofit.Call和OkHttp.Call接口,其实是一样的,那为什么要搞两个呢?我猜想是和后面的切面编程有关系。
我们继续回到ServiceGenerator中的createService方法,在该方法中创建了一个retrofit后,调用了create(Class class)的方法,具体进去看看:
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@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);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
该方法前面是对该类进行基本检查,检查是否是接口,并且该接口不能继承自其它接口。然后就是调用了Proxy(动态代理,运用了切面编程的思想,不懂可以看如下两篇文章:Java的动态代理(dynamic proxy) 和 深入理解Java Proxy机制)
接下来再看下创建ServiceMethod,使用method初始化该ServiceMethod,method变量就是我们外面调用的contributors这个方法,而loadServiceMethod就是获取该method的注解、参数类型、参数的注解。
看代码:
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
所以我们外面定义的接口RequestClient只是用来获取这三样而已,里面不用方法则不用实现。而切面编程中,我们返回了一个实现Retrofit.Call接口的实例Retrofit.OkHttpCall。在Retrofit.OkHttpCall中封装了请求OkHttp的操作。所以这就是需要两个Call的原因,Retrofit.Call封装了OkHttp.Call的一些操作。
讲完这些对Retrofit应该有个大概的了解了。那么再讲讲Converter,其实就是对数据的转换。
最常见的就是json的转换,比如说我拿到一个json的字符串:
[
{
"login":"ssss",
"contributions":1
},
{
"login":"ssss",
"contributions":2
}
]
我希望我只用定义
class Contributor {
String login;
int contributions;
}
转换库可以在拿到服务器数据后自动帮我填充到类的变量上去。
这样类似的库有很多,Retrofit提供如下的库:
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
具体功能可以自己去查。那么我们可以自定义这样的converter么?当然可以!
看下Retrofit提供的接口:
public interface Converter<F, T> {
T convert(F value) throws IOException;
/** Creates {@link Converter} instances based on a type and target usage. */
abstract class Factory {
/**
* Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if
* {@code type} cannot be handled by this factory. This is used to create converters for
* response types such as {@code SimpleResponse} from a {@code Call<SimpleResponse>}
* declaration.
*/
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
/**
* Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if
* {@code type} cannot be handled by this factory. This is used to create converters for types
* specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap}
* values.
*/
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
/**
* Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if
* {@code type} cannot be handled by this factory. This is used to create converters for types
* specified by {@link Field @Field}, {@link FieldMap @FieldMap} values,
* {@link Header @Header}, {@link HeaderMap @HeaderMap}, {@link Path @Path},
* {@link Query @Query}, and {@link QueryMap @QueryMap} values.
*/
public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
}
}
responseBodyConverter就是对返回数据做转换,requestBodyConverter是对请求数据做转换。(看了这一对函数,其实觉得有点像OkHttp拦截器,也是分请求前和请求后的。)
举个具体的例子:如果服务器返回的数据都是使用Base64解密,那么对于客户端拿到数据之前应该先解密再使用,那么如何用converter来实现呢?
上代码:
public class Base64GsonConverterFactory extends Converter.Factory {
public static Base64GsonConverterFactory create() {
return create(new Gson());
}
public static Base64GsonConverterFactory create(Gson gson) {
return new Base64GsonConverterFactory(gson);
}
private final Gson gson;
private Base64GsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new Base64GsonBodyConverter<>(adapter);
}
private static class Base64GsonBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
Base64GsonBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String temp = value.string();
temp = new String(Base64.decode(temp, Base64.DEFAULT));
return adapter.fromJson(temp);
}
}
}
该Factory继承自Converter.Factory,只重写了responseBodyConverter,说明只对返回的数据进行转换,请求前不转换。在处理responseBody的时候,使用Base64GsonBodyConverter来转换,进行base64解码。当然解码后还支持gson的转换。
从此转换数据是不是变的很方便?如果要使用这些converter的话,只用add进去就好了。
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(Base64GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
那么问题来了!这么多的converter,我返回数据只有一个,不可能说每个converter都处理啊,这和RxJava可不一样,不是一个事件流处理,一个response只能被一个converter处理,那么那个来处理呢?
在Retrofit中维护一个converters的列表,记录着所有支持的converter。还记得刚刚的这个方法么?
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new Base64GsonBodyConverter<>(adapter);
}
在该方法中的第一个参数会返回该请求需要返回的类型,也就是刚刚我们说的Retrofit.Call<T>
,也就是T类型,每个converter可以只处理自己想要处理的T的类型,举个例子,看下ScalarsConverterFactory这个converter处理的类型:
public final class ScalarsConverterFactory extends Converter.Factory {
public static ScalarsConverterFactory create() {
return new ScalarsConverterFactory();
}
private ScalarsConverterFactory() {
}
@Override public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if (type == String.class
|| type == boolean.class
|| type == Boolean.class
|| type == byte.class
|| type == Byte.class
|| type == char.class
|| type == Character.class
|| type == double.class
|| type == Double.class
|| type == float.class
|| type == Float.class
|| type == int.class
|| type == Integer.class
|| type == long.class
|| type == Long.class
|| type == short.class
|| type == Short.class) {
return ScalarRequestBodyConverter.INSTANCE;
}
return null;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
if (type == String.class) {
return StringResponseBodyConverter.INSTANCE;
}
if (type == Boolean.class || type == boolean.class) {
return BooleanResponseBodyConverter.INSTANCE;
}
if (type == Byte.class || type == byte.class) {
return ByteResponseBodyConverter.INSTANCE;
}
if (type == Character.class || type == char.class) {
return CharacterResponseBodyConverter.INSTANCE;
}
if (type == Double.class || type == double.class) {
return DoubleResponseBodyConverter.INSTANCE;
}
if (type == Float.class || type == float.class) {
return FloatResponseBodyConverter.INSTANCE;
}
if (type == Integer.class || type == int.class) {
return IntegerResponseBodyConverter.INSTANCE;
}
if (type == Long.class || type == long.class) {
return LongResponseBodyConverter.INSTANCE;
}
if (type == Short.class || type == short.class) {
return ShortResponseBodyConverter.INSTANCE;
}
return null;
}
}
这个ScalarsConverterFactory只处理String、boolean这种基本类型的数据,而其他类型的response都不会处理,直接返回null。
当一个response返回后,retrofit会遍历converters,并将response的type传给converter,询问该converter是否要处理,如果返回的不为空,则代表可以处理,不再遍历列表。如果遍历完都没有一个converter愿意处理的话,也会提示。所以添加converter的顺序很重要,像GsonConverterFactory这种是所有类型都会处理的,要放在后面添加,否则如果直接返回“string”的话,不为json,解析会出错。
所以小集合的converter放前面,大集合的converter放后面。
Retrofit官网:
- Retrofit 2 — Adding & Customizing the Gson Converter
- Retrofit — Getting Started and Create an Android Client
- https://github.com/square/retrofit/wiki/Converters
上面讲了基本的一些用法和概念,下面讲些其他的小点:
- Multiple Query Parameters of Same Name:如果我们要发出这样的url(https://api.example.com/tasks?id=123&id=124&id=125),参数中有多个名字相同的参数,该怎么做?
public interface TaskService {
@GET("/tasks")
Call<List<Task>> getTask(@Query("id") List<Long> taskIds);
}
- Send Objects in Request Body:使用post请求发送请求时,需将参数放在body中传输。刚刚我们前面讲的都是get请求,按照HTTP协议规范,GET请求的参数应该放在url中,而不是body中,GET请求的body为空。所以我们前面@query是将参数放在url中,那现在如果post要传递参数则使用@Body注解来,例如:
public interface TaskService {
@POST("/tasks")
Call<Task> createTask(@Body Task task);
}
public class Task {
private long id;
private String text;
public Task(long id, String text) {
this.id = id;
this.text = text;
}
}
Task task = new Task(1, "my task title");
Call<Task> call = taskService.createTask(task);
call.enqueue(new Callback<Task>() {});