相信作过Android网络请求的同窗都绕不开Volley,Retrofit,OkHttp这几座大山,至于他们的前世姻缘以及孰优孰劣,不在本博客的讨论范围。如题,这篇博客主要介绍一个小白(其实就是我本身)的Retrofit2进阶之路,会结合一个开发实例介绍5节内容:html
Call<T>
响应结果的处理问题先来回顾一下Retrofit2
在项目中的完整使用流程:建立Bean类 --> 建立接口形式的http
请求方法 --> 经过Retrofit.builder()
建立接口对象并调用http
方法请求网络数据 --> 在RxJava
的Observable
(被观察者)中异步处理请求结果!java
那么Retrofit2 Http 请求方法注解有那么多字段,都表明什么含义呢?添加请求头或者大文件上传的请求方法该怎么写呢?这将在第二节介绍。另外,Retrofit2基本用法的网络响应结果是一个Call<T>
,那么怎样在Android中解析Call<T>
呢?将在第二节介绍。第三节根据Retrofit2使用流程介绍了一个实践项目是怎样使用Retrofit2+RxJava
作网络请求。第四节和四五节是Retrofit实现一些复杂需求的必杀技,介绍了自定义OkHttp Interceptor实现日志输出,保存和添加Cookie;自定义ResponseConverter,自定义HTTP请求注解等内容。react
从Retrofit2的官方文档来看,Retrofit2 进行网络请求的URL分为两部分:BaseURL和relativeURL。BaseURL须要以/
结尾, 通常不须要变化直接定义便可,固然在特殊的状况下,好比后一次网络访问URL须要从前一次访问结果中获取相关参数,那么就须要动态的操做URL,这种用法会在第五节进行介绍;relativeURL与每次请求的参数相关,因此每一个request 方法都须要 http annotation 来提供请求的relativeURL,Retrofit2内置的注解有五个:GET, POST, PUT, DELETE, and HEAD.
这些注解在使用时涉及到哪些相关的字段呢?我从参考文献的博客中引用了一张图:android
能够看到,有URL请求参数,Query
参数这些简单网络请求参数;同时还支持用@Header
添加请求头;POST
请求中经常使用@FormUrlEncoded
提交表单,并用@Field
定义表单域;@MultiPart
文件上传并用@Part
定义请求体。来看一个具体的例子(摘自Retrofit2官方文档):git
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
Retrofit2把网络请求定义成接口的形式,如上是一个GET请求,@Path
表示一个占位符,@Path
中的变量必须与@GET
变量中{
和 }
中间的部分一致。下面是一个POST请求,@FormUrlEncoded
用于提交一个表单,@Field
定义了表单的name和value。更多详细的用法详见Retrofit2官方文档API Declaration ,另外Retrofit请求参数注解字段说明 这篇博客介绍的比较详细可做参考:github
public interface GitHubService { @FormUrlEncoded @POST("user/edit") Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last); }
Call<T>
响应结果的处理细心的你有木有发现,发现官方文档中给出的请求方法示例,返回结果都是 Call<List<User>>
这种形式?没错!这就是Retrofit2最原始的网络请求用法,官方文档上介绍的很简洁,能够在 call<T>
响应对象上作异步或者同步的操做,每一个 call<T>
对象只能用一次,要想屡次使用能够调用 clone()
方法来克隆出多个 call
对象以供更多操做使用。由于Retrofit2 是一个类型安全的Java和Android网络请求库,因此以上的操做对 Java 网络请求也是适用的。json
针对JVM而言,网络请求和结果处理会放在同一个线程中执行,那么在Android中,咱们怎样处理请求结果对象 call
呢?官方文档也给出了答案,咱们都知道Android中网络请求这类耗时操做都是放在工做线程(即worker thread)来执行的,而后在主线程(也即 UI thread)处理网络请求结果,天然Retrofit2也不例外,因为Retrofit2抛弃了饱受诟病的Apache HttpClient底层只依赖OkHttp3.0,网络访问层的操做都会交由OkHttp来完成,而OkHttp不只拥有自动维护的socket链接池,减小握手次数,并且还拥有队列线程池,能够轻松写并发,同时还支持socket自动选择最好路线,并支持自动重连,OkHttp的优势远不止于此,因此Retrofit2选择用OkHttp做为网络请求执行器是一个再明智不过的决定。api
若是你想异步的执行网络请求,最简单的就是在Activity或者Fragment中View控件的监听器中进行网络访问,并经过call.enqueue()
处理请求结果,并更新UI,下面是一个小demo:缓存
Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class); final Call<List<Contributor>> call = gitHubService.repoContributors("square", "retrofit"); call.enqueue(new Callback<List<Contributor>>() { @Override public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) { final TextView textView = (TextView) findViewById(R.id.textView); textView.setText(response.body().toString()); } @Override public void onFailure(Call<List<Contributor>> call, Throwable t) { final TextView textView = (TextView) findViewById(R.id.textView); textView.setText("Something went wrong: " + t.getMessage()); } }); } });
若是你须要在工做线程中执行网络请求,而不是在一个Activity或者一个Fragment中去执行,那么也就意味着,你能够在同一个线程中同步的去执行网络请求,使用call.execute()
方法来处理请求结果便可,代码以下:安全
try { Response<User> response = call.execute(); } catch (IOException e ){ // handle error }
Retorfit是支持RxJava,Guava,Java8 等等一系列扩展的,关于RxJava这个网红我就不作介绍了,RactiveX项目对 JVM 的扩展,你能够把它当作一个超级强大的异步事件处理库,可他的NB之处远不止于此,至少作Android的都应该听过他的鼎鼎大名,不熟悉的能够去看看RxJava Wiki!!而这里Retrofit2+RxJava
组合就能够实现开发效率的大幅提高,至于怎样提高的?对比一下你之前写的网络请求的代码量就知道了!结合一个实践项目的源码来分析,这里是请求果壳网最新的100条文章数据,返回结果为Json,首先build.gradle
添加依赖:
compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid compile 'io.reactivex:rxjava:1.1.0' // 推荐同时添加RxJava compile 'com.squareup.retrofit2:retrofit:2.1.0' // Retrofit网络处理 compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' // Retrofit的rx解析库 compile 'com.squareup.retrofit2:converter-gson:2.1.0' // Retrofit的gson库 compile 'com.squareup.okhttp3:okhttp:3.2.0' // OkHttp3
第一步,定义服务器Json数据对应的POJO类,这里咱们能够偷一下懒能够直接经过jsonschema2pojo 这个网站自动生成POJO类,就不用咱们手动去写了,而后copy到项目目录的bean包下。接着即是定义HTTP请求方法了,以接口的形式定义,以下:
// 服务器数据对应的实体类 public class Guokr { // 定义序列化后的名字 public @SerializedName("ok") Boolean response_ok; // 定义序列化后的名字 public @SerializedName("result") List<GuokrResult> response_results; public static class GuokrResult { public int id; public String title; public String headline_img_tb; // 用于文章列表页小图 public String headline_img; // 用于文章内容页大图 public String link; public String author; public String summary; } } // HTTP请求方法 public interface GuokrService { @GET("handpick/article.json") Observable<Guokr> getGuokrs(@Query("retrieve_type") String type, @Query("category") String category, @Query("limit") int limit, @Query("ad") int ad); }
其中 Observable<Guokr>
是RxJava中的被观察者,对应请求结果Call<T>
。只是由于Retrofit提供了很是强大的CallAdapterFactory
完美兼容了RxJava
这个超级大网红,才致使咱们日常看到的写法是这样的。第二步, 须要经过Retrofit.builder()
建立 GuokrService
接口对象,经过接口对象执行 getGuokrs
方法进行网络访问,代码以下:
// 封装 GuokrService 请求 public static GuokrService getGuokrService() { if (guokrService == null) { Retrofit retrofit = new Retrofit.Builder() .client(mClient) .baseUrl("http://apis.guokr.com/") .addCallAdapterFactory(rxJavaCallAdapterFactory) .addConverterFactory(gsonConverterFactory) .build(); guokrService = retrofit.create(GuokrService.class); } return guokrService; } // 默认加载最新的100条数据 subscription = RetrofitClient.getGuokrService().getGuokrs("by_since", "all", 100, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Guokr>() { @Override public void onCompleted() { Log.e(TAG, "--------completed-------"); } @Override public void onError(Throwable e) { Log.e(TAG, "--------error-------"); Log.e(TAG, e.getMessage()); } @Override public void onNext(Guokr guokr) { if (guokr.response_ok) { List<Guokr.GuokrResult> guokrResults = guokr.response_results; List<GuokrItem> guokrItems = new ArrayList<>(guokrResults.size()); for (Guokr.GuokrResult result : guokrResults) { GuokrItem item = new GuokrItem(); item.headline_img_tb = result.headline_img_tb; item.title = result.title; item.id = result.id; item.headline_img = result.headline_img; item.summary = result.summary; guokrItems.add(item); } mAdapter.addAll(guokrItems); mAdapter.notifyDataSetChanged(); });
注意到封装 GuokrService 请求:
addCallAdapterFactory(rxJavaCallAdapterFactory)
方法指定使用RxJava
做为CallAdapter
,须要传入一个RxJavaCallAdapterFactory
对象:CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create()
;addConverterFactory(gsonConverterFactory)
方法指定 Gson
做为解析Json数据的Converter
:Converter.Factory gsonConverterFactory = GsonConverterFactory.create()
;client(mClient)
方法指定网络执行器为OkHttp
以下建立一个默认的OkHttp对象传入便可:OkHttpClient mClient = new OkHttpClient()
;而加载网络数据这个链式调用就是RxJava最大的特点,用在这里逻辑就是,被观察者Observable<Guokr>
订阅观察者Observer<Guokr>
,当服务器一有response,观察者就会当即处理response result。由于RxJava
最大的亮点就是异步,能够很方便的切换当前任务所在的线程,并能对事件流进行各类Map变换,好比压合、转换、缓存等操做。这里是最基本的用法,被观察者直接把事件流订阅到观察者,中间没有作转换处理。
到此网络访问就完成了,是否是很简洁?简洁就对了,那是由于太多东西Retrofit2和RxJava甚至是OkHttp都帮咱们作好了!再回顾一下整个网络访问流程:建立Bean类 --> 建立接口形式的http
请求方法 --> 经过Retrofit.builder()
建立接口对象并调用http
方法请求网络数据 --> 在RxJava
的Observable
中异步处理请求结果!
在Retrofit2作网络请求的第二步,咱们须要经过Retrofit.builder()
方法来建立Retrofit
对象,其中client(mClient)
这个方法指定一个OkHttpClient
客户端做为请求的执行器,须要传入一个OkHttpClient
对象做为参数,那么在这里,咱们就能够进行一些OkHttp相关的操做,好比自定义Interceptor
,经过自定义Interceptor
能够实现网络请求日志的分级输出,能够实现保存和添加Cookie这些功能,固然,这些功能的实现都是基于OkHttp,因此要对OkHttp有必定的了解才能灵活运用。
Retrofit使用指南-->OkHttp配合Retrofit使用 这篇博客在OkHttp配合Retrofit使用这一节,关于OkHttpClient添加HttpLoggingInterceptor
进行日志输出,以及如何设置SslSocketFactory作了详细的说明,有兴趣的同窗能够参考!值得注意的是,若是后一次请求的URL,须要从前一次请求结果数据中获取,这时候就须要动态的改变BaseURL,也可经过自定义Interceptor
来实现这一需求,在BaseURL改变时,只须要setHost()
就可让下次请求的BaseURL改变,代码以下:
public class DynamicBaseUrlInterceptor implements Interceptor { private volatile String host; public void setHost(String host) { this.host = host; } @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); if (!TextUtils.isEmpty(host)) { HttpUrl newUrl = originalRequest.url().newBuilder() .host(host) .build(); originalRequest = originalRequest.newBuilder() .url(newUrl) .build(); } return chain.proceed(originalRequest); } }
那么怎样在经过OkHttp保存和添加Cookie呢?其实实现原理和上面添加日志拦截器差很少,只是添加的Intercepter
不一样而已,其实就是自定义了一个Interceptor
接口实现类,接收和保存返回结果中的Cookie,或者添加Cookie,最后,在建立OkHttp实例的时候,传入以上Interceptor
实现类的对象便可。Retrofit使用OkHttp保存和添加cookie这篇博客讲的很好,能够做为参考!
简而言之,以上这Retorfit2些高级运用都是基于定制化OkHttp来实现的,若是想玩得很溜就必须对OkHttp了解一二,推荐看这篇博客OkHttp3源码分析综述!最起码须要弄清楚OkHttpClient自定义Interceptor这一块内容,推荐看OkHttp Github Wiki --> Interceptors!
默认状况下,Retrofit会把HTTP响应体反序列化到OkHttp的ResponseBody
中,加入Converter能够将返回的数据直接格式化成你须要的样子,Retrofit提供了以下6个Converter能够直接使用,使用前须要加上相应的Gradle依赖:
在前面Retrofit2+RxJava实例中,咱们指定GsonConverterFactory做为解析Json数据的Converter,当面对更复杂的需求时,仍然能够经过继承Converter.Factory
来自定义Converter,只须要重写如下这两个方法便可:
@Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { //your own implements } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { //your own implements }
咱们不妨来看看GsonConverterFactory
源码,果真GsonConverterFactory
也是继承Converter.Factory
来实现的,重写了responseBodyConverter
和 requestBodyConverter
这两个方法,代码只有70多行仍是很简洁的,以下:
public final class GsonConverterFactory extends Converter.Factory { /** * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will use UTF-8. */ public static GsonConverterFactory create() { return create(new Gson()); } /** * Create an instance using {@code gson} for conversion. Encoding to JSON and * decoding from JSON (when no charset is specified by a header) will use UTF-8. */ 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); } }
这里须要详细解释一下TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type))
中的TypeAdapter<?>
,TypeAdapte
是Gson提供的自定义Json解析器,Type就是HTTP请求接口GuokrService
中getGuokrs()
方法返回值的泛型类型,若是返回值类型是Call<T>
,那么这里的Type就是泛型类型 T ,若是返回值类型是Observable<List<Guokr>>
,那么Type就是List<Guokr>
;关于Gson的详细用法能够参考:你真的会用Gson吗?Gson使用指南(四)。
咱们看到responseBodyConverter
方法返回的是一个GsonResponseBodyConverter
对象,跟进去看一下GsonResponseBodyConverter
源码,也很简单,源码 以下:
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(); } } }
咱们看到GsonResponseBodyConverter<T>
实现了Converter<ResponseBody, T>
,重写了convert(ResponseBody value)
方法,这就给咱们提供了一个思路:自定义Converter关键一步就是要实现Converter<ResponseBody, T>
接口而且重写convert(ResponseBody value)
方法,具体重写的代码我就不贴出来了,能够参考如何使用Retrofit请求非Restful API 这篇博客自定义Converter的作法!
另外,若是需求更复杂,须要咱们自定义HTTP请求方法的注解,又该怎么作呢?咱们还注意到GsonConverterFactory
类的重写方法responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit)
中的Annotation[] methodAnnotations
这个参数,对的,或许你已经猜到了,这就是咱们在HTTP请求接口方法中定义的注解,先看 @GET
注解的源码,以下:
/** Make a GET request. */ @Documented @Target(METHOD) @Retention(RUNTIME) public @interface GET { String value() default ""; }
那咱们自定义注解的思路也就有了,模仿上面 @GET
注解写一个 @WONDERTWO
注解便可。这里我点到即止,主要是提供一种思路,具体实现仍然能够参考上面提到的 如何使用Retrofit请求非Restful API 这篇博客自定义HTTP请求注解的作法!
有一个结论说的是在网络上,只有 1% 的用户贡献了内容,10% 的用户比较活跃,会评论和点赞,剩下的都是网络透明人,他们只是默默地在看,既不贡献内容,也不点赞。这篇文章但愿能让你成为网络上贡献内容的 TOP 1%。若是暂时作不到,那就先点个赞吧,成为活跃的 10%。
参考文献