Retrofit2 学习笔记(一)

一. Retrofit 2 简单使用的粗略介绍

1. 环境配置

Retrofit 2默认使用Okhttp作为他的网络层,所以不需要再另外导okhttp的包

build.gradle

dependencies {  
    // Retrofit & OkHttp
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
}

2. 简单封装 —》 ServiceGenerator

public class ServiceGenerator {

    public static final String API_BASE_URL = "http://your.api-base.url";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create());

    public static <S> S createService(Class<S> serviceClass) {
        Retrofit retrofit = builder.client(httpClient.build()).build();
        return retrofit.create(serviceClass);
    }
}

使用:

public interface GitHubClient {  
    @GET("/repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo
    );
}

static class Contributor {  
    String login;
    int contributions;
}

public static void main(String... args) {  
    // Create a very simple REST adapter which points the GitHub API endpoint.
    GitHubClient client = ServiceGenerator.createService(GitHubClient.class);

    // Fetch and print a list of the contributors to this library.
    Call<List<Contributor>> call =
        client.contributors("fs_opensource", "android-boilerplate");

    try {
        List<Contributor> contributors = call.execute().body();
    } catch (IOException e) {
        // handle errors
    }

    for (Contributor contributor : contributors) {
        System.out.println(
                contributor.login + " (" + contributor.contributions + ")");
    }
}

3. Converter

我们想来看看有哪些Converter

compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:converter-jackson:2.1.0'
compile 'com.squareup.retrofit2:converter-moshi:2.1.0'
compile 'com.squareup.retrofit2:converter-protobuf:2.1.0'
compile 'com.squareup.retrofit2:converter-wire:2.1.0'
compile 'com.squareup.retrofit2:converter-simplexml:2.1.0'
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'

在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,所以如果没有引入Gson的支持的话,返回值就这样写Call<ResponseBody>,但如果响应体只是支持转换为ResponseBody的话为何要引用泛型呢?返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以为其他类型的,而Converter就是Retrofit为我们提供于将ResponseBody转换为我们想要的类型,有了Converter之后我们可以把接口写成这个样子:

public interface BlogService {
  @GET("blog/{id}") //这里的{id} 表示是一个变量
  Call<Blog> getFirstBlog(/** 这里的id表示的是上面的{id} */@Path("id") int id);
}
怎么获取返回的Header和响应码?
  1. 可以使用Call<Response<T>> 代替 Call<T>,这里的Responseretrofit2.Response
  2. 使用Call<Result<T>>代替Call<T>,这里的Result是指retrofit2.adapter.rxjava.Result,这个Result中包含了Response的实例

4. Log Requests and Responses

compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'  
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();  
// set your desired log level
logging.setLevel(Level.BODY);

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  
// add your other interceptors …

// add logging as last interceptor
httpClient.addInterceptor(logging);  // <-- this is the important line!

Retrofit retrofit = new Retrofit.Builder()  
   .baseUrl(API_BASE_URL)
   .addConverterFactory(GsonConverterFactory.create())
   .client(httpClient.build())
   .build();

Log Level:

None
Basic
Headers
Body

5. 同步请求 和 异步请求(Synchronous and Asynchronous Requests)

Retrofit 支持 同步和异步的请求方式。我们可以通过设置返回类型的服务方法来定义具体的执行。

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks();
}

Retrofit 2中,每个请求都被转为Call对象,
1. 如果要进行同步请求就调用call.execute().body(),注意如果在主线程调用会报错。自己开个线程
2. 如果要进行异步请求就调用call.equeue(new Callback<T>...)

6. Cancel Requests

call.cancel();   //发生某些事情,就取消

检查是否已经取消的判断

new Callback<ResponseBody>() {  
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.d(TAG, "request success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                if (call.isCanceled()) {
                    Log.e(TAG, "request was cancelled");
                }
                else {
                    Log.e(TAG, "other larger issue, i.e. no network connection?");
                }

            }
        };

二. Retrofit 2 注解详解

1. 作用于方法

(1)添加请求头 –> Headers

Headers


public interface UserService {  
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("/tasks/{task_id}")
    Call<Task> getTask(@Path("task_id") long taskId);
}
(2)表示响应体的数据用流的形式返回 –> Streaming

如果没有该注解,默认会把数据全部载入内存,之后你通过流获取数据也不过是读取内存中的数据,所以如你的返回的数据比较大,你就需要使用这个注解

这个可以用下载文件来诠释,

①. 不使用Streaming的文件下载
两种方式 ,第二种是 动态URL的形式,后面会介绍
// option 1: a resource relative to your base URL
@GET("/resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();

// option 2: using a dynamic URL   
@GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);  

调用请求

FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);

call.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccess()) {
            Log.d(TAG, "server contacted and has file");

            boolean writtenToDisk = writeResponseBodyToDisk(response.body());

            Log.d(TAG, "file download was a success? " + writtenToDisk);
        } else {
            Log.d(TAG, "server contact failed");
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "error");
    }
});

保存文件到Disk


private boolean writeResponseBodyToDisk(ResponseBody body) {  
    try {
        // todo change the file location/name according to your needs
        File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + "Future Studio Icon.png");

        InputStream inputStream = null;
        OutputStream outputStream = null;

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

            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);
            }

            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;
    }
}
②. 使用Streaming的文件下载, 大文件的下载
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);  

需要注意的是,如果使用了@Streaming 声明,但是还用了的代码的话会报错 android.os.NetworkOnMainThreadException. 所以要开个线程

final FileDownloadService downloadService =  
                ServiceGenerator.create(FileDownloadService.class);

new AsyncTask<Void, Long, Void>() {  
   @Override
   protected Void doInBackground(Void... voids) {
       Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
       call.enqueue(new Callback<ResponseBody>() {
           @Override
           public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               if (response.isSuccess()) {
                   Log.d(TAG, "server contacted and has file");

                   boolean writtenToDisk = writeResponseBodyToDisk(response.body());

                   Log.d(TAG, "file download was a success? " + writtenToDisk);
               }
               else {
                   Log.d(TAG, "server contact failed");
               }
           }
       return null;
   }
}.execute();    

2. 作用于方法参数(形参)

(1)用作添加不固定值的 –> Header
public interface UserService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);
}
(2)用作非表单请求体 –> Body

@Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。

BlogService service = retrofit.create(BlogService.class);
Blog blog = new Blog();
blog.content = "新建的Blog";
blog.title = "测试";
blog.author = "怪盗kidou";
Call<Result<Blog>> call = service.createBlog(blog);
结果
Result{code=200, msg='OK', data=Blog{id=20, date='2016-04-21 05:29:58', author='怪盗kidou', title='测试', content='新建的Blog'}, count=0, page=0}
(3)用作表单字段 –> FieldFieldMapPartPartMap

FieldFieldMapPartPartMap
用于表单字段
FieldFieldMapFormUrlEncoded注解配合
PartPartMapMultipart注解配合,适合有文件上传的情况
FieldMap 的接受类型是Map<String,String>,
String类型会调用其toString方法。
PartMap的默认接受类型是Map<String,RequestBody>,
RequestBody类型会通过Conventer转换

①. FormUrlEncoded

表示请求体是一个Form表单,你在网站上看到的登录页面就是用这种请求方式
Content-Type:application/x-www-form-urlencoded

/**
  * {@link FormUrlEncoded} 表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
  * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);


// 演示 @FormUrlEncoded 和 @Field
Call<ResponseBody> call1 = service.testFormUrlEncoded1("怪盗kidou", 24);

===============================================================================================
/**
  * Map的key作为表单的键
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);

// 演示 @FormUrlEncoded 和 @FieldMap
// 实现的效果与上面想同
Map<String, Object> map = new HashMap<>();
map.put("username", "怪盗kidou");
map.put("age", 24);
Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
②. Multipart

表示请求体是一个支持文件上传的Form表单,你看到 带文件上传的网页 就是用这种方式
Content-Type:multipart/form-data


/**
  * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
  * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);


// 演示 @Multipart 和 @Part

MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "怪盗kidou");
RequestBody age = RequestBody.create(textType, "25");

File file = new File(filename);
RequestBody requestFile = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);

===============================================================================================

/**
* PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
* 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);

// 演示 @Multipart 和 @PartMap
// 实现和上面同样的效果
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
//fileUpload2Args.put("file", file);
Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
(4)用于URL –> PathQueryQueryMapUrl
①. Path
 public interface BlogService {
        @GET("blog/{id}") //这里的{id} 表示是一个变量
        Call<ResponseBody> getFirstBlog(/** 这里的id表示的是上面的{id} */@Path("id") int id);
    }
②. Query
 public interface BlogService {
        /**
         * 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
         * 对于Query和QueryMap,如果不是String(或Map的第个泛型参数不是String)时
         * 会被调用toString
         * Url支持的类型有 okhttp3.HttpUrl, String, java.net.URI, android.net.Uri
         * {@link retrofit2.http.QueryMap} 用法和{@link retrofit2.http.FieldMap} 用法一样,不再说明
         */
        @GET //当有URL注解时,这里的URL就省略了
        Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);

    }
③. QueryMap
基本使用
public interface NewsService() {  
    @GET("/news")
    Call<ResponseBody> getNews(@QueryMap Map<String, String> options);
}

    Map<String, String> data = new HashMap<>();
    data.put("author", "Marcus");
    data.put("page", String.valueOf(2));

    // simplified call to request the news with already initialized service
    Call<List<News>> call = newsService.getNews(data);
    call.enqueue(…);

结果:http://your.api.url/news?page=2&author=Marcus  
QueryMap Options : encoded

QueryMap有个对于编码的option field

  • encodedtrue 或者 false,默认为false
Call<List<News>> getNews((@QueryMap(encoded=true) Map<String, String> options);  

在请求的URL之前启用编码将编码单个字符(即encoded=true),比如使用keyauthorvaluemarcus-poehls,最后会被转为author=marcus%2Dpoehls

encoded=falseauthor=marcus-poehls

④. Url

使用@Url 可以动态设置Urls

Retrofit 2 使用 OkHttp的 HttpUrl 来解析每个 Url的端点 就像 网站上的 <a href="">…</a>

/**
 *Url支持的类型有 okhttp3.HttpUrl, String, java.net.URI, android.net.Uri
*/
public interface UserService {  
    @GET
    public Call<ResponseBody> profilePicture(@Url String url);
}
Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("https://s3.amazon.com/profile-picture/path");

// 结果为:
// https://s3.amazon.com/profile-picture/path
Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/v2/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("/profile-picture/path");

// 结果为:
// https://your.api.url/profile-picture/path
(5)小注意–> 补充
①. QueryFieldPart这三者都支持数组和实现了Iterable接口的类型,如ListSet等,方便向后台传递数组。
Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//结果:ids[]=0&ids[]=1&ids[]=2
②. 如何在OkHttp Interceptor 中管理 Headers
  • .header(key, val): will override preexisting headers identified by key 会重写
  • .addHeader(key, val): will add the header and don’t override preexisting ones 不会重写
okHttpClient.interceptors().add(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                .header("Authorization", "auth-value"); // <-- this is the important line

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});
okHttpClient.interceptors().add(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                    .addHeader("Cache-Control", "no-cache")
                    .addHeader("Cache-Control", "no-store");

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});
③. 如何添加Query参数 给每个请求
OkHttpClient.Builder httpClient =  
    new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        HttpUrl originalHttpUrl = original.url();

        HttpUrl url = originalHttpUrl.newBuilder()
                .addQueryParameter("apikey", "your-actual-api-key")
                .build();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                .url(url);

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});
④. Url拼接规范
# Example 1
base url: https://futurestud.io/api/v3/  
endpoint: my/endpoint  
Result:   https://futurestud.io/api/v3/my/endpoint

# Example 2
base url: https://futurestud.io/api/v3/  
endpoint: /api/v2/another/endpoint  
Result:   https://futurestud.io/api/v2/another/endpoint  

# Example 3 — completely different url
base url: http://futurestud.io/api/  
endpoint: https://api.futurestud.io/  
Result:   https://api.futurestud.io/

# Example 4 — Keep the base url’s scheme
base url: https://futurestud.io/api/  
endpoint: //api.futurestud.io/  
Result:   https://api.futurestud.io/

# Example 5 — Keep the base url’s scheme
base url: http://futurestud.io/api/  
endpoint: //api.github.com  
Result:   http://api.github.com  

参考整理
https://futurestud.io/blog/retrofit-send-objects-in-request-body
http://www.jianshu.com/p/308f3c54abdd

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值