Retrofit2 简明教程(一)

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 使用Demo.zip

Retrofit2 简明教程(一)就到这了,文章中的示例代码在下面,如有问题欢迎在下方留言,及时让我知道,写一篇好的技术blog好难,但您的支持是我的动力,希望您在下方的留言让我做的更好。

作者:fuchenxuan
出处:http://blog.csdn.net/vfush
欢迎访问我的个人站点:http://fuchenxuan.cn
转载请注明出处–http://blog.csdn.net/vfush

Android 之美 从0到1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值