您的位置:首页 > 其它

Retrofit遇到Restful API怎么办

2016-06-13 14:14 253 查看

何为非Restful Api?

本文转自简书-键盘男kkmike999 :http://www.jianshu.com/p/2263242fa02d

Restful Api

User数据,有uid、name,Restful Api返回数据:

{
"name": "kkmike999",
"uid": 1
}


在数据库没找到User,直接返回错误的http code。但弊端是当在浏览器调试api,后端查询出错时,很难查看错误码&错误信息。(当然用chrome的开发者工具可以看,但麻烦)

Not Restful Api

但不少后端工程师,并不一定喜欢用Restful Api,他们会自己在json中加入ret、msg这种数据。当User正确返回:

{
"ret": 0,
"msg": "成功",
"data": {
"uid": 1,
"name": "kkmike999"
}
}


错误返回:

{
"ret": -1,
"msg": "失败"
}


这样的好处,就是调试api方便,在任意浏览器都可以直观地看到错误码&错误信息。

文章虽然是转载的,但是我对下面的东西有点犹豫不决,迟疑了好几天要不要继续这个博客。

Retrofit一般用法

本来Retrofit对restful的支持,可以让我们写少很多冤枉代码。但后端这么搞一套,前端怎么玩呀?既然木已成舟,我们做APP的总不能老对后端指手画脚,友谊小船说翻就翻。

先说说retrofit普通用法

public class User {
int    uid;
String name;
}

public interface UserService {

@GET("not_restful/user/{name}.json")
Call<User> loadUser(@Path("name") String name);
}


Bean和Service准备好,接下来就是调用Retrofit了:

OkHttpClient client = new OkHttpClient.Builder().build();

Retrofit retrofit = new Retrofit.Builder().baseUrl("http://***.b0.upaiyun.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();

UserService userService = retrofit.create(UserService.class);

User user = userService.loadUser("kkmike999")
.execute()
.body();


此处加入了GsonConverterFactory,没有使用RxJavaCallAdapter。如果是restful api,直接返回User的json,那调用execute().body()就能获得正确的User了。然而,not restful api,返回一个不正确的User ,也不抛错,挺难堪的。

ResponseConverter

我们留意到GsonConverterFactory,看看源码:

package retrofit2.converter.gson;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;

public final class GsonConverterFactory extends Converter.Factory {

public static GsonConverterFactory create() {
return create(new Gson());
}

public static GsonConverterFactory create(Gson gson) {
return new GsonConverterFactory(gson);
}

private final Gson gson;

private GsonConverterFactory(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 GsonResponseBodyConverter<>(gson, adapter);
}

@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {

TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
}


responseBodyConverter方法返回GsonResponseBodyConverter,我们再看看GsonResponseBodyConverter源码:

package retrofit2.converter.gson;

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson           gson;
private final TypeAdapter<T> adapter;

GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}

@Override
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
}


先给大家科普下,TypeAdapter

重写GsonResponseConverter

由源码看出,是GsonResponseBodyConverter对json进行解析的,只要重写GsonResponseBodyConverter,自定义解析,就能达到我们目的了。

但GsonResponseBodyConverter和GsonConverterFactory都是final class,并不能重写。靠~ 不让重写,我就copy代码!

新建retrofit2.converter.gson目录,新建CustomConverterFactory,把GsonConverterFactory源码拷贝过去,同时新建CustomResponseConverter。 把CustomConverterFactory的GsonResponseBodyConverter替换成CustomResponseConverter:

public final class CustomConverterFactory extends Converter.Factory {
......

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new CustomResponseConverter<>(gson, adapter);
}
......
}


写CustomResponseConverter:

public class CustomResponseConverter<T> implements Converter<ResponseBody, T> {

private final Gson gson;
private final TypeAdapter<T> adapter;

public CustomResponseConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}

@Override
public T convert(ResponseBody value) throws IOException {
try {
String body = value.string();

JSONObject json = new JSONObject(body);

int    ret = json.optInt("ret");
String msg = json.optString("msg", "");

if (ret == 0) {
if (json.has("data")) {
Object data = json.get("data");

body = data.toString();

return adapter.fromJson(body);
} else {
return (T) msg;
}
} else {
throw new RuntimeException(msg);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
value.close();
}
}
}


为什么我们要新建retrofit2.converter.gson目录?因为GsonRequestBodyConverter不是public class,所以CustomConverterFactory要import GsonRequestBodyConverter就得在同一目录下。当然你喜欢放在自己目录下,可以拷贝源码如法炮制。

接下来,只要 new Retrofit.Builder().addConverterFactory(CustomConverterFactory.create())就大功告成了!

注:以上写的Converter如果不理解没有关系,上面的用法基本用不到,毕竟我们可以通过别的方法解决。下面的Service注解很有意思。

玩转Service注解

既然retrofit能“理解”service方法中的注解,我们为何不试试?GsonConverterFactory的方法responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit),这里有Annotation[],没错,这就是service方法中的注解。

我们写一个@Data注解类:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Data {
String value() default "data";
}


在loadUser(…)添加@Data:

@Data("user")
@GET("not_restful/user/{name}.json")
Call<User> loadUser(@Path("name") String name);


修改CustomResponseConverter

public class CustomResponseConverter<T> implements Converter<ResponseBody, T> {

private final Gson gson;
private final TypeAdapter<T> adapter;
private final String name;

public CustomResponseConverter(Gson gson, TypeAdapter<T> adapter, String name) {
this.gson = gson;
this.adapter = adapter;
this.name = name;
}

@Override
public T convert(ResponseBody value) throws IOException {
try {
...
if (ret == 0) {
if (json.has(name)) {
Object data = json.get(name);

body = data.toString();

return adapter.fromJson(body);
}
...
}
}


给CustomConverterFactory的responseBodyConverter(…)加上

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
String name = "data";// 默认"data"

for (Annotation annotation : annotations) {
if (annotation instanceof Data) {
name = ((Data) annotation).value();
break;
}
}
...

return new CustomResponseConverter<>(gson, adapter, name);
}


这么写后,后端改什么名称都不怕!

更灵活的Converter

有个需求:APP显示某班级信息&学生信息。后台拍拍脑袋:

{
"ret": 0,
"msg": "",
"users": [
{
"name": "鸣人",
"uid": 1
},
{
"name": "佐助",
"uid": 2
}
],
"info": {
"cid": 7,
"name": "第七班"
}
}


哭了吧,灭了后端工程师恐怕也难解心头之恨!

我们意识到,CustomResponseConverter责任太重,又是判断ret、msg,又是解析json数据并返回bean,如果遇到奇葩json,CustomResponseConverter远远不够强大,而且不灵活。

怎么办,干嘛不自定义converter呢?

问题来了,这个converter应该如何传给CustomConverterFactory?因为在new Retrofit.Builder().addConvertFactory(…)时就要添加ConverterFactory,那时并不知道返回json是怎样,哪个service要用哪个adapter。反正通过构造方法给CustomConverterFactory传Converter肯定行不通。

我们上面不是用过Annotaion吗?同样手段再玩一把如何。写一个@Converter注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Converter {

Class<? extends AbstractResponseConverter> converter();
}


并且写一个Converter抽象类:

public abstract class AbstractResponseConverter<T> implements Converter<ResponseBody, T>{

protected Gson gson;

public AbstractResponseConverter(Gson gson) {
this.gson = gson;
}
}


为什么要写一个继承Converter抽象类?让我们自定义的Converter直接继承Converter不行吗?

注意了,@Adapter只能携带Class<?>和int``String等基本类型,并不能带converter对象。而我们需要CustomConverterFactory在responseBodyConverter()方法中,通过反射,new一个converter对象,而CustomConverterFactory并不知道调用Converter哪个构造函数,传什么参数。所以,干脆就写一个AbstractResponseConverter,让子类继承它,实现固定的构造方法。这样CustomConverterFactory就可以获取固定的构造方法,生成Converter对象并传入如gson``typeAdapter参数了。


public class ClazzInfo{
List<Student> users;
Info     info;
}

public class ClassConverter implements AbstractResponseConverter<ClazzInfo>{

public ClassConverter(Gson gson){
super(gson);
}

@Override
public ClazzInfo convert(ResponseBody value) throws IOException {
// 这里你想怎么解析json就怎么解析啦
ClazzInfo clazz = ...
return clazz;
}
}


@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {

for (Annotation annotation : annotations) {
if (annotation instanceof Converter) {
try {
Class<? extends AbstractResponseConverter> converterClazz = ((Converter) annotation). converter();
// 获取有 以gson参数的 构造函数
Constructor<? extends AbstractResponseConverter> constructor = converterClazz .getConstructor(Gson.class);
AbstractResponseConverter  converter = constructor.newInstance(gson);

return converter;
} catch (Exception e) {
e.printStackTrace();
}
}
}
...
return new CustomResponseConverter<>(gson, adapter, name);
}


Service方法注解:

@Converter(converter = ClassConverter.class)
@GET("not_restful/class/{cid}.json")
Call<ClazzInfo> loadClass(@Path("cid") String cid);


写到这里,已经快吐血了。怎么会有这么奇葩的后端…. 正常情况下,应该把”users”和”class”封装在”data”里,这样我们就可以直接把返回结果写成Call< ClassInfo>就可以了。

Converter,让你的入参和返回类型丰富起来

RequestBodyConverter

详见 http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1117 1.5.1章节。

ResponseBodyConverter

Retrofit 也支持自定义 ResponseBodyConverter。

我们再来看下我们定义的接口:

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


返回值的类型为 List< Repo>,而我们直接拿到的原始返回肯定就是字符串(或者字节流),那么这个返回值类型是怎么来的呢?首先说明的一点是,GitHub 的这个 api 返回的是 Json 字符串,也就是说,我们需要使用 Json 反序列化得到 List< Repo>,这其中用到的其实是 GsonResponseBodyConverter。

问题来了,如果请求得到的 Json 字符串与返回值类型不对应,比如:

接口返回的 Json 字符串:

{"err":0, "content":"This is a content.", "message":"OK"}


返回值类型

class Result{
int code;//等价于 err
String body;//等价于 content
String msg;//等价于 message
}


哇,这时候肯定有人想说,你是不是脑残,偏偏跟服务端对着干?哈哈,我只是示例嘛,而且在生产环境中,你敢保证这种情况不会发生??

这种情况下, Gson 就是再牛逼,也只能默默无语俩眼泪了,它哪儿知道字段的映射关系怎么这么任性啊。好,现在让我们自定义一个 Converter 来解决这个问题吧!

static class ArbitraryResponseBodyConverterFactory extends Converter.Factory{
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return super.responseBodyConverter(type, annotations, retrofit);
}
}

static class ArbitraryResponseBodyConverter implements Converter<ResponseBody, Result>{

@Override
public Result convert(ResponseBody value) throws IOException {
RawResult rawResult = new Gson().fromJson(value.string(), RawResult.class);
Result result = new Result();
result.body = rawResult.content;
result.code = rawResult.err;
result.msg = rawResult.message;
return result;
}
}

static class RawResult{
int err;
String content;
String message;
}


当然,别忘了在构造 Retrofit 的时候添加这个 Converter,这样我们就能够愉快的让接口返回 Result 对象了。

注意!!Retrofit 在选择合适的 Converter 时,主要依赖于需要转换的对象类型,在添加 Converter 时,注意 Converter 支持的类型的包含关系以及其顺序。

结语

了解此功能还是有必要的,但实践中基本用不上,尽量不要这么做!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: