您的位置:首页 > 其它

Retrofit2 学习笔记(一)

2016-07-27 15:17 399 查看
一 Retrofit 2 简单使用的粗略介绍
环境配置

简单封装 ServiceGenerator

Converter
怎么获取返回的Header和响应码

Log Requests and Responses

同步请求 和 异步请求Synchronous and Asynchronous Requests

Cancel Requests

二 Retrofit 2 注解详解
作用于方法
1添加请求头 Headers

2表示响应体的数据用流的形式返回 Streaming
不使用Streaming的文件下载

使用Streaming的文件下载 大文件的下载

作用于方法参数形参
1用作添加不固定值的 Header

2用作非表单请求体 Body

3用作表单字段 Field FieldMap Part PartMap
FormUrlEncoded

Multipart

4用于URL Path QueryQueryMap Url
Path

Query

QueryMap
基本使用

QueryMap Options encoded

Url

5小注意 补充
QueryField和Part这三者都支持数组和实现了Iterable接口的类型如ListSet等方便向后台传递数组

如何在OkHttp Interceptor 中管理 Headers

如何添加Query参数 给每个请求

Url拼接规范

一. 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和响应码?

可以使用
Call<Response<T>>
代替
Call<T>
,这里的
Response
retrofit2.Response


使用
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)用作表单字段 –>
Field
FieldMap
Part
PartMap

Field
FieldMap
Part
PartMap


用于表单字段

Field
FieldMap
FormUrlEncoded
注解配合

Part
PartMap
Multipart
注解配合,适合有文件上传的情况

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 –>
Path
Query
QueryMap
Url

①. 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


encoded
true
或者
false
,默认为
false


Call<List<News>> getNews((@QueryMap(encoded=true) Map<String, String> options);


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


encoded=false
author=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[/code] 
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[/code] 

(5)小注意–> 补充

①.
Query
Field
Part
这三者都支持数组和实现了
Iterable
接口的类型,如
List
Set
等,方便向后台传递数组。


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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: