Retrofit2 简明教程(一)
相信大家都听过Retrofit的大名但是没有实际运用,或是已经运用过Retrofit1.x,因为Retrofit1.x和Retrofit2.x差别非常大,Retrofit1.x教程也是非常多,为了简单易懂,所以本文将以最新Retrofit2实践运用满足我们的Retrofit日常开发,后续我们也会更深入的了解Retrofit2,最后在本文中的尾页将附上Demo。
在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan de blog
简介
Retrofit 是一个Square开发的类型安全的REST安卓客户端请求库。这个库为网络认证、API请求以及用OkHttp发送网络请求提供了强大的框架 。Retrofit 可以利用接口,方法和注解参数来声明式定义一个请求应该如何被创建。并且可更换或自定义HTTP client,以及可更换或自定义Converter,返回数据解析方式。Retrofit可用于Android和Java的一个类型安全(type-safe)的REST客户端,如果你的服务器使用的使RESTAPI,那么你将非常适合使用它。
安装
请选择以下三种方式中一种进行安装,最后如果你正在使用PROGUARD,请添加下方PROGUARD配置。
SOURCE
关于Retrofit源代码以及官方简单例子,请访问http://github.com/square/retrofit
GRADLE(推荐)
如果你正在使用GRADLE在你的项目中的build.gradle
添加以下代码到您的配置:
<code class="hljs bash has-numbering">compile <span class="hljs-string">'com.squareup.retrofit2:retrofit:2.1.0'</span></code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
MAVEN
如果你正在使用MAVEN在你的项目中的pom.xml
添加以下代码到您的配置:
<code class="hljs xml has-numbering"><span class="hljs-tag"><<span class="hljs-title">dependency</span>></span> <span class="hljs-tag"><<span class="hljs-title">groupId</span>></span>com.squareup.retrofit2<span class="hljs-tag"></<span class="hljs-title">groupId</span>></span> <span class="hljs-tag"><<span class="hljs-title">artifactId</span>></span>retrofit<span class="hljs-tag"></<span class="hljs-title">artifactId</span>></span> <span class="hljs-tag"><<span class="hljs-title">version</span>></span>2.1.0<span class="hljs-tag"></<span class="hljs-title">version</span>></span> <span class="hljs-tag"></<span class="hljs-title">dependency</span>></span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
PROGUARD
如果你正在使用PROGUARD在你的项目中添加以下代码到您的配置:
<code class="hljs haml has-numbering">-<span class="ruby">dontwarn retrofit2.** </span>-<span class="ruby">keep <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">retrofit2</span>.** { *;</span> } </span>-<span class="ruby">keepattributes <span class="hljs-constant">Signature</span> </span>-<span class="ruby">keepattributes <span class="hljs-constant">Exceptions</span></span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
最初的步骤
创建Retrofit实例
在使用Retrofit前,我们需要先创建Retrofit实例,并且做一系列配置,然而Retrofit设计的也是非常好,这些配置都是可插拔的:
<code class="hljs axapta has-numbering"> Retrofit retrofit = <span class="hljs-keyword">new</span> Retrofit.Builder() <span class="hljs-comment">//设置baseUrl,注意baseUrl 应该以/ 结尾。</span> .baseUrl(<span class="hljs-string">"http://news-at.zhihu.com/api/4/"</span>) <span class="hljs-comment">//使用Gson解析器,可以替换其他的解析器</span> .addConverterFactory(GsonConverterFactory.create()) <span class="hljs-comment">//设置OKHttpClient,如果不设置会提供一个默认的</span> .<span class="hljs-keyword">client</span>(<span class="hljs-keyword">new</span> OkHttpClient()) <span class="hljs-comment">// .client(new UrlConnectionClient())</span> <span class="hljs-comment">// .client(new ApacheClient())</span> <span class="hljs-comment">// .client(new CustomClient())</span> .build(); </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
更换HTTP client与Converter
Retrofit 背后的 HTTP client,以及序列化机制(JSON/XML 协议)等都是可以替换,因此你可以选择自己合适的方案。Retrofit 最早出来的时候,只支持 Apache 的 HTTP client。后来增加了 URL connection,以及 OkHttp 的支持。如果你想使用其他的 HTTP client,可以通过以下方式了替换,或者更改为自定义的HTTP client:
<code class="hljs axapta has-numbering"> <span class="hljs-comment">//设置OKHttpClient,如果不设置会提供一个默认的OkHttpClient</span> .<span class="hljs-keyword">client</span>(<span class="hljs-keyword">new</span> OkHttpClient()) <span class="hljs-comment">// .client(new UrlConnectionClient())</span> <span class="hljs-comment">// .client(new ApacheClient())</span> <span class="hljs-comment">// .client(new CustomClient())</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
序列化功能也是可替换的。默认是用的 GSON,你当然也可以用 Jackson 来替换掉。
<code class="hljs livecodeserver has-numbering"> <span class="hljs-comment"> //使用Gson解析器,可以替换其他的解析器</span> .addConverterFactory(GsonConverterFactory.<span class="hljs-built_in">create</span>()) <span class="hljs-comment"> //当需要返回原始String数据时</span> .addConverterFactory(ScalarsConverterFactory.<span class="hljs-built_in">create</span>())</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
除此之外Retrofit还提供以下几种Converter:
- 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,andString):
com.squareup.retrofit2:converter-scalars
基本使用
我们将看一下如何用Retrofit与服务器交互,通过它你将学会如何运用Retrofit于日常开发。
Retrofit使用接口,方法和参数,使用注解表明了请求将如何处理,每一种方法都必须有一个HTTP标注提供请求的方法和相对URL,有五种内置注解:GET
, POST
, PUT
, DELETE
, 和 HEAD
,在注解中指定URL
。请选择以下方式中合适的请求方式来处理您的请求。
GET
在这里我们最开始第一个GET请求使用的是知乎日报的api,为了更好使用Retrofit其他请求方式而又没有比较好的公开api,我自行编写了配合使用Retrofit的测试服务端,放置在外网以便大家测试使用。
普通GET
基于上面的最初的步骤,接下来我们需要定义一个接口,并且使用注解(@GET
)表明一次GET请求:
<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> ZhihuService { <span class="hljs-comment">//获取启动页大图</span> @GET(<span class="hljs-string">"start-image/1080*1776"</span>) Call<StartImageBean> getStartImage(); }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
这是一个普通的GET请求,接着我们来看如何利用Retrofit 创建服务接口,并且设置参数:
<code class="hljs sql has-numbering"> ZhihuService messageService = retrofit.<span class="hljs-operator"><span class="hljs-keyword">create</span>(ZhihuService.class);</span> <span class="hljs-operator"><span class="hljs-keyword">Call</span><StartImageBean> startImage = messageService.getStartImage();</span> </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
最后,使用startImage.enqueue
进行异步请求,并且获取了我们期待的数据(实体对象):
<code class="hljs avrasm has-numbering"> startImage<span class="hljs-preprocessor">.enqueue</span>(new Callback<StartImageBean>() { @Override public void onResponse(<span class="hljs-keyword">Call</span><StartImageBean> <span class="hljs-keyword">call</span>, Response<StartImageBean> response) { if (response<span class="hljs-preprocessor">.isSuccessful</span>()) { Log<span class="hljs-preprocessor">.d</span>(TAG, response<span class="hljs-preprocessor">.body</span>()<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span> resultTextView<span class="hljs-preprocessor">.setText</span>(<span class="hljs-string">""</span> + response<span class="hljs-preprocessor">.body</span>()<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span> } } @Override public void onFailure(<span class="hljs-keyword">Call</span><StartImageBean> <span class="hljs-keyword">call</span>, Throwable t) { resultTextView<span class="hljs-preprocessor">.setText</span>(<span class="hljs-string">""</span> + <span class="hljs-string">"error:"</span> + t<span class="hljs-preprocessor">.getMessage</span>())<span class="hljs-comment">;</span> } })<span class="hljs-comment">;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
如果你想使用call.execute()
进行同步请求,需要注意的是不要放在UI线程:
<code class="hljs avrasm has-numbering">try{ Response<StartImageBean> response = <span class="hljs-keyword">call</span><span class="hljs-preprocessor">.execute</span>()<span class="hljs-comment">; // 同步</span> Log<span class="hljs-preprocessor">.d</span>(TAG, <span class="hljs-string">"response:"</span> + response<span class="hljs-preprocessor">.body</span>()<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span> } catch (IOException e) { e<span class="hljs-preprocessor">.printStackTrace</span>()<span class="hljs-comment">;</span> }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
因为一次call.execute() 的request只能执行一次,否则你将会得到如下错误:
<code class="hljs http has-numbering"><span class="hljs-attribute">java.lang.IllegalStateException</span>: <span class="hljs-string">Already executed</span></code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
如果你想取消本次请求可以使用 startImage.cancel()
或者是复制一次request,再次请求:
<code class="hljs php has-numbering"> startImage.cancel();<span class="hljs-comment">//取消</span> Call<StartImageBean> cloneRequsest = startImage.<span class="hljs-keyword">clone</span>();<span class="hljs-comment">//复制</span></code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
是不是感觉特别简单,使用时候只需调用接口,这一切都简化了我们的操作。
动态参数(GET)
我们需要先定义一个接口,并且使用注解(@Query
)或者是@QueryMap
表明动态参数请求如何处理:
相应的URL是这样:
http://baseurl/app/test/sayHello?username=fuchenxuan&age=110
<code class="hljs vbnet has-numbering"> @<span class="hljs-keyword">GET</span>(<span class="hljs-string">"test/sayHello"</span>) <span class="hljs-keyword">Call</span><<span class="hljs-built_in">String</span>> sayHello(@Query(<span class="hljs-string">"username"</span>) <span class="hljs-built_in">String</span> username, @Query(<span class="hljs-string">"age"</span>) <span class="hljs-built_in">String</span> age);</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
接着我们忽略接口的创建,直接使用Retrofit与服务器交互,值得注意的是我们此次返回的数据是String,而不是一个自定义的实体类对象。所以我们需要更换Converter,否则你将会遇到不必要的麻烦(而我觉得Retrofit应该提供一个默认Stirng的实现):
<code class="hljs livecodeserver has-numbering"> <span class="hljs-comment"> //当需要返回原始String数据时</span> .addConverterFactory(ScalarsConverterFactory.<span class="hljs-built_in">create</span>())</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
接着我们就得到了我们期待的数据:
<code class="hljs vbscript has-numbering"><span class="hljs-keyword">Call</span><<span class="hljs-built_in">String</span>> doubanCall = myTestApiService.sayHello(<span class="hljs-string">"fuchenxuan"</span>, <span class="hljs-string">"110"</span>); doubanCall.enqueue(<span class="hljs-keyword">new</span> Callback<<span class="hljs-built_in">String</span>>() { @Override <span class="hljs-keyword">public</span> void onResponse(<span class="hljs-keyword">Call</span><<span class="hljs-built_in">String</span>> <span class="hljs-keyword">call</span>, <span class="hljs-built_in">Response</span><<span class="hljs-built_in">String</span>> <span class="hljs-built_in">response</span>) { <span class="hljs-keyword">if</span> (<span class="hljs-built_in">response</span>.isSuccessful()) { <span class="hljs-built_in">Log</span>.d(TAG, <span class="hljs-built_in">response</span>.body().toString()); resultTextView.setText(<span class="hljs-string">""</span> + <span class="hljs-built_in">response</span>.body().toString()); } } @Override <span class="hljs-keyword">public</span> void onFailure(<span class="hljs-keyword">Call</span><<span class="hljs-built_in">String</span>> <span class="hljs-keyword">call</span>, Throwable t) { } });</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
RESTful方式(动态PATH)
GET 动态PATH 就是优雅的RESTful api方式,
相应的URL是这样:
http://news-at.zhihu.com/api/4/start-image/1024*782
<code class="hljs css has-numbering"><span class="hljs-at_rule">@<span class="hljs-keyword">GET("start-image/{size}")</span> Call<StartImageBean> <span class="hljs-function">getStartImageByPath(@<span class="hljs-function">Path(<span class="hljs-string">"size"</span>)</span> String size)</span></span>;</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
POST
form-data(表单数据)
form-data 就是如表单K-V参数形式
这里其实就跟GET的动态参数是一致的只是替换了@POST
注解
<code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">MyTestApiService</span> {</span> <span class="hljs-annotation">@POST</span>(<span class="hljs-string">"test/sayHello"</span>) Call<ResultBean> postSayHello(<span class="hljs-annotation">@Query</span>(<span class="hljs-string">"username"</span>) String username, <span class="hljs-annotation">@Query</span>(<span class="hljs-string">"age"</span>) String age); }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
JSON参数(raw)
当服务器需要你POST 参数以json打包数据格式请求时,然而这种参数方式RESTful api 也是非常常见的,我们需要使用@Body
注解:
<code class="hljs perl has-numbering"> <span class="hljs-variable">@POST</span>(<span class="hljs-string">"test/sayHi"</span>) // <span class="hljs-variable">@Headers</span>(<span class="hljs-string">"Accept-Encoding: application/json"</span>) //使用<span class="hljs-variable">@Headers</span> 可添加header Call<ResultBean> postSayHi(<span class="hljs-variable">@Body</span> UserBean userBean);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
上面我们还示例了如何使用@Headers
@Headers("Accept-Encoding: application/json")
添加头部信息,或者我们有需求需要使用@Header
实现动态头部信息:
<code class="hljs java has-numbering"> <span class="hljs-annotation">@POST</span>(<span class="hljs-string">"test/sayHi"</span>) <span class="hljs-annotation">@Headers</span>(<span class="hljs-string">"Accept-Encoding: application/json"</span>) <span class="hljs-comment">//也可以使用@Header 可添加header</span> Call<ResultBean> postSayHi(<span class="hljs-annotation">@Body</span> UserBean userBean, <span class="hljs-annotation">@Header</span>(<span class="hljs-string">"city"</span>) String city);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
RESTful方式(动态PATH)
前面也说了retrofit非常适用于RESTful url的格式,这里因为知乎的就是RESTful api,我们直接使用和GET动态URL一样的注解(@PATH
)来表明请求处理:
相应的URL是这样:
http://news-at.zhihu.com/api/4/start-image/1024*782
<code class="hljs css has-numbering"><span class="hljs-at_rule">@<span class="hljs-keyword">POST("start-image/{size}")</span> Call<StartImageBean> <span class="hljs-function">getStartImageByPath(@<span class="hljs-function">Path(<span class="hljs-string">"size"</span>)</span> String size)</span></span>;</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
文件上传
在我们开发当中肯定必不可少图片上传了,我们使用表单上传文件时,必须让 <form>
表单的 enctyped
等于 multipart/form-data
。
文件上传我们需要使用@MultiPart
和@Part
,MultiPart意思就是允许多个@Part
多部分上传。
<code class="hljs css has-numbering"><span class="hljs-at_rule">@<span class="hljs-keyword">Multipart</span> @<span class="hljs-function">POST(<span class="hljs-string">"test/upload"</span>)</span> Call<ResultBean> <span class="hljs-function">upload(@<span class="hljs-function">Part(<span class="hljs-string">"file\"; filename=\"launcher_icon.png"</span>)</span> RequestBody file)</span></span>; </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
值得注意的是我们需要在@Part
指定file和filename的值,避免一些不必要的麻烦。
相应的我们在使用retrofit的时候,首先先获取到文件,并且创建RequestBody实例,然后调用接口请求,相应代码块如下:
<code class="hljs avrasm has-numbering">File file = new File(getExternalFilesDir(null), <span class="hljs-string">"launcher_icon.png"</span>)<span class="hljs-comment">;</span> RequestBody fileBody = RequestBody<span class="hljs-preprocessor">.create</span>(MediaType<span class="hljs-preprocessor">.parse</span>(<span class="hljs-string">"image/png"</span>), file)<span class="hljs-comment">;</span> <span class="hljs-keyword">Call</span><ResultBean> doubanCall = myTestApiService<span class="hljs-preprocessor">.upload</span>(fileBody)<span class="hljs-comment">;</span> doubanCall<span class="hljs-preprocessor">.enqueue</span>(new Callback<ResultBean>() { @Override public void onResponse(<span class="hljs-keyword">Call</span><ResultBean> <span class="hljs-keyword">call</span>, Response<ResultBean> response) { if (response<span class="hljs-preprocessor">.isSuccessful</span>()) { Log<span class="hljs-preprocessor">.d</span>(TAG, response<span class="hljs-preprocessor">.body</span>()<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span> resultTextView<span class="hljs-preprocessor">.setText</span>(<span class="hljs-string">""</span> + response<span class="hljs-preprocessor">.body</span>()<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span> } } @Override public void onFailure(<span class="hljs-keyword">Call</span><ResultBean> <span class="hljs-keyword">call</span>, Throwable t) { // Log<span class="hljs-preprocessor">.d</span>(TAG, response<span class="hljs-preprocessor">.body</span>()<span class="hljs-preprocessor">.toString</span>())<span class="hljs-comment">;</span> resultTextView<span class="hljs-preprocessor">.setText</span>(<span class="hljs-string">""</span> + <span class="hljs-string">"error:"</span> + t<span class="hljs-preprocessor">.getMessage</span>())<span class="hljs-comment">;</span> } })<span class="hljs-comment">;</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
大文件下载
文件下载我们需要使用@Url
和 @Streaming
,@Url
动态Url正好非常适合我们的场景,而使用@Streaming
注解可以让我们下载非常大的文件时,避免Retrofit将整个文件读进内存,否则可能造成OOM现象。
声明接口如下:
<code class="hljs ruby has-numbering"> <span class="hljs-variable">@Streaming</span> <span class="hljs-variable">@GET</span> <span class="hljs-constant">Call</span><<span class="hljs-constant">ResponseBody</span>> downloadFileByDynamicUrlAsync(<span class="hljs-variable">@Url</span> <span class="hljs-constant">String</span> downloadUrl);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
需要注意的是我们需要使用Retrofitcall.execute
同步获取ResponseBody,那么我们就需要放进一个单独的工作线程中:
<code class="hljs java has-numbering"><span class="hljs-keyword">new</span> AsyncTask<Void, Long, Void>() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> Void <span class="hljs-title">doInBackground</span>(Void... voids) { Call<ResponseBody> call = myTestApiService.downloadFileByDynamicUrlAsync(API_BASE_URL.concat(<span class="hljs-string">"/res/atom-amd64.deb"</span>)); <span class="hljs-keyword">try</span> { Response<ResponseBody> response = call.execute(); <span class="hljs-keyword">boolean</span> writtenToDisk = writeResponseBodyToDisk(response.body()); Log.d(TAG, <span class="hljs-string">"下载文件 "</span> + writtenToDisk); } <span class="hljs-keyword">catch</span> (IOException e) { e.printStackTrace(); } <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>; } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPreExecute</span>() { <span class="hljs-keyword">super</span>.onPreExecute(); } }.execute();</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
最后我们需要将文件写入磁盘根目录中:
<code class="hljs java has-numbering"><span class="hljs-comment">//写入到磁盘根目录</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">writeResponseBodyToDisk</span>(ResponseBody body) { <span class="hljs-keyword">try</span> { File futureStudioIconFile = <span class="hljs-keyword">new</span> File(Environment.getExternalStorageDirectory() + File.separator + <span class="hljs-string">"atom.deb"</span>); InputStream inputStream = <span class="hljs-keyword">null</span>; OutputStream outputStream = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">try</span> { <span class="hljs-keyword">byte</span>[] fileReader = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">4096</span>]; <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> fileSize = body.contentLength(); <span class="hljs-keyword">long</span> fileSizeDownloaded = <span class="hljs-number">0</span>; inputStream = body.byteStream(); outputStream = <span class="hljs-keyword">new</span> FileOutputStream(futureStudioIconFile); <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) { <span class="hljs-keyword">int</span> read = inputStream.read(fileReader); <span class="hljs-keyword">if</span> (read == -<span class="hljs-number">1</span>) { <span class="hljs-keyword">break</span>; } outputStream.write(fileReader, <span class="hljs-number">0</span>, read); fileSizeDownloaded += read; Log.d(TAG, <span class="hljs-string">"file download: "</span> + fileSizeDownloaded + <span class="hljs-string">" of "</span> + fileSize); <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> finalFileSizeDownloaded = fileSizeDownloaded; runOnUiThread(<span class="hljs-keyword">new</span> Runnable() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() { resultTextView.setText(<span class="hljs-string">"file download: "</span> + finalFileSizeDownloaded + <span class="hljs-string">" of "</span> + fileSize); } }); } outputStream.flush(); <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; } <span class="hljs-keyword">catch</span> (IOException e) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } <span class="hljs-keyword">finally</span> { <span class="hljs-keyword">if</span> (inputStream != <span class="hljs-keyword">null</span>) { inputStream.close(); } <span class="hljs-keyword">if</span> (outputStream != <span class="hljs-keyword">null</span>) { outputStream.close(); } } } <span class="hljs-keyword">catch</span> (IOException e) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
这样我们就可以非常高效的下载大文件了,最后友情提醒(如果是6.0以上另外再申请权限):
<code class="hljs xml has-numbering"> <span class="hljs-tag"><<span class="hljs-title">uses-permission</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">"android.permission.INTERNET"</span> /></span> <span class="hljs-tag"><<span class="hljs-title">uses-permission</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">"android.permission.WRITE_EXTERNAL_STORAGE"</span> /></span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
注解
尽管文章前面已经把Retrofit2注解基本了解完了,但是有必要多了解一些其他的注解,或许您正好有这样的场景需求,他们分别可作用于方法和参数:
@Headers
:用于在方法添加请求头:
<code class="hljs ruby has-numbering"> <span class="hljs-variable">@POST</span>(<span class="hljs-string">"test/sayHi"</span>) <span class="hljs-variable">@Headers</span>(<span class="hljs-string">"Accept-Encoding: application/json"</span>) <span class="hljs-constant">Call</span><<span class="hljs-constant">ResultBean</span>> postSayHi(<span class="hljs-variable">@Body</span> <span class="hljs-constant">UserBean</span> userBean, <span class="hljs-variable">@Header</span>(<span class="hljs-string">"city"</span>) <span class="hljs-constant">String</span> city);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
@Streaming
如果您正在下载一个大文件,Retrofit2将尝试将整个文件移动到内存中。为了避免这种,我们必须向请求声明中添加一个特殊的注解@Streaming
<code class="hljs ruby has-numbering"><span class="hljs-variable">@Streaming</span> <span class="hljs-variable">@GET</span> <span class="hljs-constant">Call</span><<span class="hljs-constant">ResponseBody</span>> downloadFileByDynamicUrlAsync(<span class="hljs-variable">@Url</span> <span class="hljs-constant">String</span> fileUrl); </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
@Header
:用于在方法参数里动态添加请求头:
<code class="hljs sql has-numbering"><span class="hljs-operator"><span class="hljs-keyword">Call</span><ResultBean> postSayHi(@Header(<span class="hljs-string">"city"</span>) String city);</span></code><ul class="pre-numbering"><li>1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
@Body
:用于Body的JSON格式参数
<code class="hljs ruby has-numbering"> <span class="hljs-variable">@POST</span>(<span class="hljs-string">"test/sayHi"</span>) <span class="hljs-constant">Call</span><<span class="hljs-constant">ResultBean</span>> postSayHi(<span class="hljs-variable">@Body</span> <span class="hljs-constant">UserBean</span> userBean);</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
-
@Url
:使用动态的请求的网址,会复写之前的baseUrl,值得注意的是
@Url
需要在所有参数之前:
<code class="hljs css has-numbering"> <span class="hljs-at_rule">@<span class="hljs-keyword">POST</span> Call<ResultBean> <span class="hljs-function">postSayHelloByURL(@Url String url,@<span class="hljs-function">Query(<span class="hljs-string">"username"</span>)</span> String username, @<span class="hljs-function">Query(<span class="hljs-string">"age"</span>)</span> String age)</span></span>;</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
-
@Path
:主要适合用于RESTful api ,动态URL,比如
https://baseUrl/api/v1/
,将API的版本号放入URL,变换Path切换不同版本的API。
<code class="hljs css has-numbering"><span class="hljs-at_rule">@<span class="hljs-keyword">POST("/api/{version}/{size}")</span> Call<StartImageBean> <span class="hljs-function">getStartImageByPath(@<span class="hljs-function">Path(<span class="hljs-string">"size"</span>)</span> String size,@<span class="hljs-function">Path(<span class="hljs-string">"version"</span>)</span> String version)</span></span>;</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
以下几个都是用于服务器接受表单参数类型(form-data)时使用
@Filed
-
@FiledMap
这两个需要和
@FormUrlEncoded
配合使用,参数形式体现在请求体上:
<code class="hljs css has-numbering"> <span class="hljs-at_rule">@<span class="hljs-keyword">FormUrlEncoded</span> @<span class="hljs-function">POST(<span class="hljs-string">"test/sayHello"</span>)</span> Call<ResultBean> <span class="hljs-function">postSayHelloByForm(@<span class="hljs-function">Field(<span class="hljs-string">"username"</span>)</span> String username, @<span class="hljs-function">Field(<span class="hljs-string">"age"</span>)</span> String age)</span></span>;</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
@Query
-
@QueryMap
:这两个和
@Filed
、@FiledMap
功能是一致的,区别在于参数形式体现在URL上,类似这样:http://baseurl/app/test/sayHello?username=fuchenxuan&age=110
的URL
<code class="hljs vbnet has-numbering"> @<span class="hljs-keyword">GET</span>(<span class="hljs-string">"test/sayHello"</span>) <span class="hljs-keyword">Call</span><<span class="hljs-built_in">String</span>> sayHello(@Query(<span class="hljs-string">"username"</span>) <span class="hljs-built_in">String</span> username, @Query(<span class="hljs-string">"age"</span>) <span class="hljs-built_in">String</span> age);</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
@Part
-
@PartMap
:这两个用于上传文件,与
@MultiPart
注解结合使用
<code class="hljs css has-numbering"> <span class="hljs-at_rule">@<span class="hljs-keyword">Multipart</span> @<span class="hljs-function">POST(<span class="hljs-string">"test/upload"</span>)</span> Call<ResultBean> <span class="hljs-function">upload(@<span class="hljs-function">Part(<span class="hljs-string">"file\"; filename=\"launcher_icon.png"</span>)</span> RequestBody file)</span></span>; </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;"><img src="http://static.blog.csdn.net/images/save_snippets.png" /></a></div>
更多的Retrofit内容
如果你已经完全读完了这篇文章并且也实践着编写了这个程序,那么你一定已经能够非常熟练自如地使用Retrofit2了,你可能也已经编写了一些Retrofit2程序来尝试练习各种Retrofit2特性。如果你还没有那样做的话,那么你一定要快点去实践。
本文末尾将略带一下Retrofit2其他特性,后续文章我们也会有更伟大的实践。
CallAdapter
Retrofit2提供了三个CallAdapter:
- guava:com.squareup.retrofit2:adapter-guava
- Java8:com.squareup.retrofit2:adapter-java8
-
rxjava:com.squareup.retrofit2:adapter-rxjava
你肯定很期待将和与Java8 或者是rxjava结合使用,当然你也可以自定义CallAdapter,满足自己的需求。那么我们将会在下篇文章中一起学习这个快乐的编程。
附件
Retrofit2 简明教程(一)就到这了,文章中的示例代码在下面,如有问题欢迎在下方留言,及时让我知道,写一篇好的技术blog好难,但您的支持是我的动力,希望您在下方的留言让我做的更好。
作者:fuchenxuan
出处:http://blog.csdn.net/vfush
欢迎访问我的个人站点:http://fuchenxuan.cn
转载请注明出处–http://blog.csdn.net/vfush