什么是jsonp
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决浏览器的跨域数据访问的问题。返回数据格式如:
jsonp1382016430883([{"id":1,"title":"XXXX"},{"id":2,"title":"YYYYY"}])
或
jsonpCallback({"code":200,"data":[{"id":1,"title":"XXXX"},{"id":2,"title":"YYYYY"}]})
可以看出jsonp和json格式仅仅只是在收尾加了"()"和特殊字符串。我们暂且不管jsonp的概念,今天我要讲的是使用Retrofit请求服务端接口返回的是jsonp格式如何处理问题。
问题引出
我们都知道服务端接口返回数据一般是json/xml/text/html等,而我们现有的数据解析库有pull/sax/gson/fastjson等,用的最多的是gson,但是gson是不支持jsonp格式的,这就需要我们在gson解析返回数据之前做处理,即把非json的数据格式处理成标准的json格式。
解决方案
既然我们要在gson解析响应数据之前处理响应数据,首先会想到Retrofit中的拦截器(Interceptor),当然其实这个部分是属于okhttp的,而我们的项目中用的是Retrofit网络框架自然就离不开okhttp相关的api。 其实有两种方案,一个是利用okhttp的Interceptor这个类在响应之前将数据处理好再交给gson处理,另一个是利用Retrofit的Converter.Factory这个类进行数据转换。
方案一
自定义拦截器(Interceptor),在拦截器里对ResponseBody做处理。这个方法比较挫,但是兼容性很好,比较灵活,适用不同的服务端接口返回数据,比如既有json又有jsonp等。
import java.io.IOException;import okhttp3.Interceptor;import okhttp3.MediaType;import okhttp3.Response;import okhttp3.ResponseBody;public class JsonpHandleInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); ResponseBody responseBody = response.body(); MediaType mediaType = responseBody.contentType(); String content = responseBody.string(); int index = content.indexOf("("); if (/*content.startsWith("jsonp") &&*/ index != -1) { content = content.substring(index + 1, content.length() - 1); } return response.newBuilder() .body(ResponseBody.create(mediaType, content)) .build(); }}复制代码
为okhttp添加拦截器并且设置给Retrofit
OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(new JsonpHandleInterceptor()) .build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://juejin.im") .client(httpClient) .build();复制代码
方案二
自定义Converter.Factory
import android.support.annotation.Nullable;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.ResponseBody;import retrofit2.Converter;import retrofit2.Retrofit;public class GsonJsonpConverterFactory extends Converter.Factory { private final Gson gson; public static GsonJsonpConverterFactory create() { return create(new Gson()); } public static GsonJsonpConverterFactory create(Gson gson) { if (gson == null) { throw new NullPointerException("gson == null"); } return new GsonJsonpConverterFactory(gson); } private GsonJsonpConverterFactory(Gson gson) { this.gson = gson; } @Nullable @Override public ConverterresponseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); return new GsonJsonpResponseBodyConverter<>(gson, adapter); }}复制代码
自定义Converter
import com.google.gson.Gson;import com.google.gson.TypeAdapter;import com.google.gson.stream.JsonReader;import java.io.IOException;import java.io.Reader;import okhttp3.ResponseBody;import retrofit2.Converter;public final class GsonJsonpResponseBodyConverterimplements Converter { private final Gson gson; private final TypeAdapter adapter; GsonJsonpResponseBodyConverter(Gson gson, TypeAdapter adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { Reader reader = value.charStream(); int item = reader.read(); while (item != '(' && item != -1) { item = reader.read(); } JsonReader jsonReader = gson.newJsonReader(reader); try { return adapter.read(jsonReader); } finally { reader.close(); } }}复制代码
配置Retrofit
Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(httpClient) .addConverterFactory(GsonJsonpConverterFactory.create()) .build();复制代码
总结
需要参考GsonConverterFactory部分源码。