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添加以下代码到您的配置:

compile 'com.squareup.retrofit2:retrofit:2.1.0'

MAVEN

如果你正在使用MAVEN在你的项目中的pom.xml添加以下代码到您的配置:

<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>retrofit</artifactId>
  <version>2.1.0</version>
</dependency>

PROGUARD

如果你正在使用PROGUARD在你的项目中添加以下代码到您的配置:

-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

最初的步骤

创建Retrofit实例

在使用Retrofit前,我们需要先创建Retrofit实例,并且做一系列配置,然而Retrofit设计的也是非常好,这些配置都是可插拔的:

      Retrofit retrofit = new Retrofit.Builder()
                //设置baseUrl,注意baseUrl 应该以/ 结尾。
                .baseUrl("http://news-at.zhihu.com/api/4/")
                //使用Gson解析器,可以替换其他的解析器
                .addConverterFactory(GsonConverterFactory.create())
                //设置OKHttpClient,如果不设置会提供一个默认的
                .client(new OkHttpClient())
//                .client(new UrlConnectionClient())
//                .client(new ApacheClient())
//                .client(new CustomClient())
                .build();

更换HTTP client与Converter

Retrofit 背后的 HTTP client,以及序列化机制(JSON/XML 协议)等都是可以替换,因此你可以选择自己合适的方案。Retrofit 最早出来的时候,只支持 Apache 的 HTTP client。后来增加了 URL connection,以及 OkHttp 的支持。如果你想使用其他的 HTTP client,可以通过以下方式了替换,或者更改为自定义的HTTP client:

 //设置OKHttpClient,如果不设置会提供一个默认的OkHttpClient
                .client(new OkHttpClient())
//                .client(new UrlConnectionClient())
//                .client(new ApacheClient())
//                .client(new CustomClient())

序列化功能也是可替换的。默认是用的 GSON,你当然也可以用 Jackson 来替换掉。

    //使用Gson解析器,可以替换其他的解析器
    .addConverterFactory(GsonConverterFactory.create())
    //当需要返回原始String数据时
    .addConverterFactory(ScalarsConverterFactory.create())

除此之外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请求:

public interface ZhihuService {

    //获取启动页大图
    @GET("start-image/1080*1776")
    Call<StartImageBean> getStartImage();
}

这是一个普通的GET请求,接着我们来看如何利用Retrofit 创建服务接口,并且设置参数:

        ZhihuService messageService = retrofit.create(ZhihuService.class);
        Call<StartImageBean> startImage = messageService.getStartImage();

最后,使用startImage.enqueue进行异步请求,并且获取了我们期待的数据(实体对象):

 startImage.enqueue(new Callback<StartImageBean>() {

            @Override
            public void onResponse(Call<StartImageBean> call, Response<StartImageBean> response) {

                if (response.isSuccessful()) {
                    Log.d(TAG, response.body().toString());
                    resultTextView.setText("" + response.body().toString());
                }

            }

            @Override
            public void onFailure(Call<StartImageBean> call, Throwable t) {
                resultTextView.setText("" + "error:" + t.getMessage());
            }
        });

如果你想使用call.execute()进行同步请求,需要注意的是不要放在UI线程:

try{
    Response<StartImageBean> response = call.execute(); // 同步
    Log.d(TAG, "response:" + response.body().toString());
} catch (IOException e) {
    e.printStackTrace();
}

因为一次call.execute() 的request只能执行一次,否则你将会得到如下错误:

java.lang.IllegalStateException: Already executed

如果你想取消本次请求可以使用 startImage.cancel()或者是复制一次request,再次请求:

 startImage.cancel();//取消
 Call<StartImageBean> cloneRequsest = startImage.clone();//复制

是不是感觉特别简单,使用时候只需调用接口,这一切都简化了我们的操作。

动态参数(GET)

我们需要先定义一个接口,并且使用注解(@Query)或者是@QueryMap表明动态参数请求如何处理:
相应的URL是这样:
http://baseurl/app/test/sayHello?username=fuchenxuan&age=110

    @GET("test/sayHello")
    Call<String> sayHello(@Query("username") String username, @Query("age") String age);

接着我们忽略接口的创建,直接使用Retrofit与服务器交互,值得注意的是我们此次返回的数据是String,而不是一个自定义的实体类对象。所以我们需要更换Converter,否则你将会遇到不必要的麻烦(而我觉得Retrofit应该提供一个默认Stirng的实现):

    //当需要返回原始String数据时
    .addConverterFactory(ScalarsConverterFactory.create())

接着我们就得到了我们期待的数据:

Call<String> doubanCall = myTestApiService.sayHello("fuchenxuan", "110");
        doubanCall.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {

                if (response.isSuccessful()) {
                    Log.d(TAG, response.body().toString());
                    resultTextView.setText("" + response.body().toString());
                }

            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {

            }
        });

RESTful方式(动态PATH)

GET 动态PATH 就是优雅的RESTful api方式,
相应的URL是这样:
http://news-at.zhihu.com/api/4/start-image/1024*782

@GET("start-image/{size}")
    Call<StartImageBean> getStartImageByPath(@Path("size") String size);

POST

form-data(表单数据)

form-data 就是如表单K-V参数形式
这里其实就跟GET的动态参数是一致的只是替换了@POST注解

public interface MyTestApiService {

@POST("test/sayHello")
    Call<ResultBean> postSayHello(@Query("username") String username, @Query("age") String age);

}

JSON参数(raw)

当服务器需要你POST 参数以json打包数据格式请求时,然而这种参数方式RESTful api 也是非常常见的,我们需要使用@Body注解:

 @POST("test/sayHi")
   // @Headers("Accept-Encoding: application/json")
        //使用@Headers 可添加header
    Call<ResultBean> postSayHi(@Body UserBean userBean);

上面我们还示例了如何使用@Headers@Headers("Accept-Encoding: application/json")添加头部信息,或者我们有需求需要使用@Header实现动态头部信息:

 @POST("test/sayHi")
    @Headers("Accept-Encoding: application/json")
        //也可以使用@Header 可添加header
    Call<ResultBean> postSayHi(@Body UserBean userBean, @Header("city") String city);

RESTful方式(动态PATH)

前面也说了retrofit非常适用于RESTful url的格式,这里因为知乎的就是RESTful api,我们直接使用和GET动态URL一样的注解(@PATH)来表明请求处理:
相应的URL是这样:
http://news-at.zhihu.com/api/4/start-image/1024*782

@POST("start-image/{size}")
    Call<StartImageBean> getStartImageByPath(@Path("size") String size);

文件上传

在我们开发当中肯定必不可少图片上传了,我们使用表单上传文件时,必须让 <form> 表单的 enctyped 等于 multipart/form-data
文件上传我们需要使用@MultiPart@Part ,MultiPart意思就是允许多个@Part多部分上传。

@Multipart
    @POST("test/upload")
    Call<ResultBean> upload(@Part("file\"; filename=\"launcher_icon.png") RequestBody file);

值得注意的是我们需要在@Part指定file和filename的值,避免一些不必要的麻烦。

相应的我们在使用retrofit的时候,首先先获取到文件,并且创建RequestBody实例,然后调用接口请求,相应代码块如下:

File file = new File(getExternalFilesDir(null), "launcher_icon.png");
        RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
        Call<ResultBean> doubanCall = myTestApiService.upload(fileBody);

        doubanCall.enqueue(new Callback<ResultBean>() {
            @Override
            public void onResponse(Call<ResultBean> call, Response<ResultBean> response) {

                if (response.isSuccessful()) {
                    Log.d(TAG, response.body().toString());
                    resultTextView.setText("" + response.body().toString());
                }

            }

            @Override
            public void onFailure(Call<ResultBean> call, Throwable t) {
//                Log.d(TAG, response.body().toString());
                resultTextView.setText("" + "error:" + t.getMessage());
            }
        });

大文件下载

文件下载我们需要使用@Url@Streaming@Url动态Url正好非常适合我们的场景,而使用@Streaming注解可以让我们下载非常大的文件时,避免Retrofit将整个文件读进内存,否则可能造成OOM现象。
声明接口如下:

    @Streaming
    @GET
    Call<ResponseBody> downloadFileByDynamicUrlAsync(@Url String downloadUrl);

需要注意的是我们需要使用Retrofitcall.execute同步获取ResponseBody,那么我们就需要放进一个单独的工作线程中:

new AsyncTask<Void, Long, Void>() {

         @Override
            protected Void doInBackground(Void... voids) {
                Call<ResponseBody> call = myTestApiService.downloadFileByDynamicUrlAsync(API_BASE_URL.concat("/res/atom-amd64.deb"));
              try {
                    Response<ResponseBody> response = call.execute();
                    boolean writtenToDisk = writeResponseBodyToDisk(response.body());
                    Log.d(TAG, "下载文件 " + writtenToDisk);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected void onPreExecute() {
                super.onPreExecute();
            }
        }.execute();

最后我们需要将文件写入磁盘根目录中:

//写入到磁盘根目录
    private boolean writeResponseBodyToDisk(ResponseBody body) {
        try {
            File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "atom.deb");
            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                byte[] fileReader = new byte[4096];

                final long fileSize = body.contentLength();
                long fileSizeDownloaded = 0;

                inputStream = body.byteStream();
                outputStream = new FileOutputStream(futureStudioIconFile);
                while (true) {
                    int read = inputStream.read(fileReader);

                    if (read == -1) {
                        break;
                    }

                    outputStream.write(fileReader, 0, read);

                    fileSizeDownloaded += read;

                    Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);

                    final long finalFileSizeDownloaded = fileSizeDownloaded;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            resultTextView.setText("file download: " + finalFileSizeDownloaded + " of " + fileSize);
                        }
                    });
                }

                outputStream.flush();

                return true;
            } catch (IOException e) {
                return false;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            return false;
        }
    }

这样我们就可以非常高效的下载大文件了,最后友情提醒(如果是6.0以上另外再申请权限):

 <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

注解

尽管文章前面已经把Retrofit2注解基本了解完了,但是有必要多了解一些其他的注解,或许您正好有这样的场景需求,他们分别可作用于方法和参数:

  • @Headers:用于在方法添加请求头:
    @POST("test/sayHi")
    @Headers("Accept-Encoding: application/json")
    Call<ResultBean> postSayHi(@Body UserBean userBean, @Header("city") String city);
  • @Streaming
    如果您正在下载一个大文件,Retrofit2将尝试将整个文件移动到内存中。为了避免这种,我们必须向请求声明中添加一个特殊的注解@Streaming
@Streaming
@GET
Call<ResponseBody> downloadFileByDynamicUrlAsync(@Url String fileUrl);
  • @Header :用于在方法参数里动态添加请求头:
Call<ResultBean> postSayHi(@Header("city") String city);
  • @Body :用于Body的JSON格式参数
 @POST("test/sayHi")
 Call<ResultBean> postSayHi(@Body UserBean userBean);
  • @Url

    使用动态的请求的网址,会复写之前的baseUrl,值得注意的是@Url需要在所有参数之前:

    @POST
    Call<ResultBean> postSayHelloByURL(@Url String url,@Query("username") String username, @Query("age") String age);
  • @Path

    主要适合用于RESTful api ,动态URL,比如https://baseUrl/api/v1/,将API的版本号放入URL,变换Path切换不同版本的API。

@POST("/api/{version}/{size}")
    Call<StartImageBean> getStartImageByPath(@Path("size") String size,@Path("version") String version);

以下几个都是用于服务器接受表单参数类型(form-data)时使用

  • @Filed
  • @FiledMap

    这两个需要和@FormUrlEncoded配合使用,参数形式体现在请求体上:

    @FormUrlEncoded
    @POST("test/sayHello")
    Call<ResultBean> postSayHelloByForm(@Field("username") String username, @Field("age") String age);
  • @Query
  • @QueryMap

    这两个和@Filed@FiledMap功能是一致的,区别在于参数形式体现在URL上,类似这样:http://baseurl/app/test/sayHello?username=fuchenxuan&age=110的URL

    @GET("test/sayHello")
    Call<String> sayHello(@Query("username") String username, @Query("age") String age);
  • @Part
  • @PartMap

    这两个用于上传文件,与@MultiPart注解结合使用

    @Multipart
    @POST("test/upload")
    Call<ResultBean> upload(@Part("file\"; filename=\"launcher_icon.png") RequestBody file);

更多的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

原创文章 33 获赞 195 访问量 34万+

猜你喜欢

转载自blog.csdn.net/vfush/article/details/51735965
今日推荐