xml.application {
config.countNotTranslucentSingleTop.times {
activity(
"${name}": "${applicationID}.${infix}N1STPNTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeNTS}",
"${launchMode}": "singleTop")
}
}
Retrofit简介
Retrofit
是大名鼎鼎的 Square 公司开源的适用于Android
与Java
的网络请求库,官方的介绍非常简短
A type-safe HTTP client for Android and Java
Retrofit
使用注解,能够极大的简化网络请求,在2.0版本默认使用Square自家的OkHttp
作为底层Http Client
,关于如何使用OkHttp
配合Retrofit
本文后面也会讲到。首先在build.gradle
中加入
compile 'com.squareup.retrofit2:retrofit:2.0.2'
定义一个网络请求:
public interface ZhiHuApi {
@GET("users")
Call> listRepos(@Path("user") String user);
}
Retrofit
将网络请求转变成了Java interface
的形式,interface
要获得实例调用listRepos(String user)
,需要借助Retrofit.java
这个类,通过Retrofit.Builder
来配置Retrofit
,再通过retrofit.create(final Class
获取接口的实例
class Factory {
public static ZhiHuApi create() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://news-at.zhihu.com/")
.build();
return retrofit.create(ZhiHuApi.class);
}
}
@GET
注解表示请求方式为GET
,其中的字符串表示相对URL
,而scheme host port
被认为是BaseUrl
设置到Retrofit
中,注意BaseUrl
需要以/
结尾,这样每个接口定义的相对URL就不需要以/
开始。如果在接口中定义的URL
为全路径,将用这个全路径作为请求URL
,BaseUrl
将不起作用。@Path
标识get请求的请求参数,上面的listRepos
可以认为是在请求http://news-at.zhihu.com/users?user=user
@POST
注解表示请求方式为POST
,通常和@FormUrlEncoded
注解一起使用,在使用@FormUrlEncoded
时可以使用@Field
标识表单字段
@FormUrlEncoded
@POST("user/login.do")
Call login(@Field("username") String userName, @Field("password") String password);
或者使用@FieldMap
提交整个map
@FormUrlEncoded
@POST("user/login.do")
Call login(@FieldMap Map formMap);
当然你也可以把整个表单封装为一个实体,使用@Body
一次提交
@FormUrlEncoded
@POST("user/login.do")
Call login(@Body User user);
Multipart
请求时使用@Multipart
注解,用@Part
标识每个RequestBody
@Multipart
@PUT("user/photo")
Call updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
定义好请求之后就可以调用call.enqueue()
来执行请求,需要传入Callback
,其中T
的类型编译器会根据Call
中的类型来判断,Retrofit
和其他网络请求库一样对于Android 平台做了线程切换,请求在后台执行,Callback
会回到main (UI) thread
,如果是Java
程序Callback
会继续回到调用它的线程。
ZhiHuApi zhiHuApi = BaseNetwork.Factory.create(ZhiHuApi.class);
Call call = zhiHuApi.login("username", "pwd");
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
为请求添加请求头时使用@Headers
,这里就不做举例,因为app中通常是每个请求都需要携带请求头,不建议在Retrofit
定义请求时传入,而是使用OkHttp
来实现统一请求头。
Converter
Retrofit
在默认情况下只能将Http
的响应体反序列化到OkHttp
的ResponseBody
中,加入Converter
可以将返回的数据直接格式化成你需要的样子,现有6个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, and String): com.squareup.retrofit2:converter-scalars
之后在代码里加入(此处以GsonConverterFactory
为例)
Retrofit retrofit =new Retrofit.Builder()
.baseUrl(Constants.BASE_HTTP_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
返回的数据会使用Gson
解析为对应传入的实体类,你也可以自定义Converter
来实现更复杂的需求,只需要extends Converter.Factory
然后重写
@Override
public Converter responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
//your own implements
}
@Override
public Converter, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
//your own implements
}
Retrofit
终归只是应用层的api
,真正的执行器是OkHttp
,较为复杂的需求都需要从执行层入手,可以做到Retrofit
对外不变的多种自定义统一封装。
OkHttp配合Retrofit使用
前文已经提到在Retrofit 2.0
中已经默认使用OkHttp
作为网络请求执行器,关于OkHttp
的优点简单提一下:(原文链接)
- 1.支持HTTP2/SPDY黑科技
- 2.socket自动选择最好路线,并支持自动重连
- 3.拥有自动维护的socket连接池,减少握手次数
- 4.拥有队列线程池,轻松写并发
- 5.拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
- 6.基于Headers的缓存策略
想要使用OkHttp
为Retrofit
提供更高的定制性,给Retrofit
设置自定义的OkHttpClient
就可以了
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_HTTP_URL)
.client(client)
.build();
之后就是构建一个OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
// 向Request Header添加一些业务相关数据,如APP版本,token
.addInterceptor(new HeadInterceptor())
//日志Interceptor,可以打印日志
.addInterceptor(logging)
// 连接超时时间设置
.connectTimeout(10, TimeUnit.SECONDS)
// 读取超时时间设置
.readTimeout(10, TimeUnit.SECONDS)
// 失败重试
.retryOnConnectionFailure(true)
// 支持Https需要加入SSLSocketFactory
.sslSocketFactory(sslSocketFactory)
// 信任的主机名,返回true表示信任,可以根据主机名和SSLSession判断主机是否信任
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
// 使用host name作为cookie保存的key
.cookieJar(new CookieJar() {
private final HashMap> cookieStore = new HashMap<>();
@Override
public void saveFromResponse(HttpUrl url, List cookies) {
cookieStore.put(HttpUrl.parse(url.host()), cookies);
}
@Override
public List loadForRequest(HttpUrl url) {
List cookies = cookieStore.get(HttpUrl.parse(url.host()));
return cookies != null ? cookies : new ArrayList();
}
})
.build();
如果设置了sslSocketFactory
却没有配置对应的hostnameVerifier
,那么Https请求是无法成功的。上面用到两个Interceptor
分别是HeadInterceptor
和HttpLoggingInterceptor
,分别是用来添加请求头和打印请求日志的拦截器,OkHttp
支持自定义拦截器,例如下面代码自定义的HeadInterceptor
为请求加入Headers
public class HeadInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request compressedRequest = originalRequest.newBuilder()
.headers(Headers.of(getHeaders()))
.build();
return chain.proceed(compressedRequest);
}
}
有时服务器会对POST
提交的表单做参数校验,一种方式是在请求头里加入特定方式加密过的表单参数的Map
,那么就需要先获取到请求的Map
,通过FormBody
可以实现
// if the server needs to verify post params, use this to get post params;
RequestBody oidBody = originalRequest.body();
Map params = new HashMap<>();
if (oidBody instanceof FormBody) {
FormBody formBody = (FormBody) oidBody;
for (int i = 0; i < formBody.size(); i++) {
params.put(formBody.encodedName(i), formBody.encodedValue(i));
}
}
HttpLoggingInterceptor
是 Square 提供的请求信息日志打印工具类,如果需要可以在build.gradle
中加入
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
可以根据不同情况配置日志输出的Level
:
- NONE 不输出日志
- BASIC 只输出请求方式响应码等基本信息
- HEADERS 只输出请求和响应的头部信息
- BODY 输出请求和响应的头部和请求体信息
另外如果遇到两个接口有相互依赖关系,必须请求完第一个接口拿到数据后才知道第二个请求的URL
,通常我们会定义两个Retrofit
,因为Retrofit
的BaseUrl
是统一配置的,不过现在可以通过实现动态BaseUrl
来避免这个问题,先看DynamicBaseUrlInterceptor
的代码
public class DynamicBaseUrlInterceptor implements Interceptor {
private volatile String host;
public void setHost(String host) {
this.host = host;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (!TextUtils.isEmpty(host)) {
HttpUrl newUrl = originalRequest.url().newBuilder()
.host(host)
.build();
originalRequest = originalRequest.newBuilder()
.url(newUrl)
.build();
}
return chain.proceed(originalRequest);
}
}
在BaseUrl
改变时只需要setHost()
就可以让下次请求的Baseurl
改变
Retrofit 与 RxJava 结合使用
本节需要对RxJava
基本用法有了解,如果不了解可以忽略或者先去熟悉一下RxJava
的wiki,介绍的目的是因为两者结合使用确实很方便,关于RxJava
之后会单独写。
RxJava
是Rx
(全称Reactive Extensions
)家族中的一员,是最近很火的响应式编程库,官方对于它的解释很简单
RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
一个异步的基于事件的观察者序列,可以理解为扩展的观察者模式,在 Android 中使用RxJava
需要引入两个compile
,RxAndroid
是专为 Android 平台打造来提供主线程切换等便利的工具项目。
compile 'io.reactivex:rxandroid:1.2.0'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
compile 'io.reactivex:rxjava:1.1.5'
Retrofit
提供了CallAdapterFactory
,它是一个知道如何将call
实例转换成其他类型的工厂类,目前支持的有:
- RxJava
- Guava
- Java8
这些和Retrofit
本身都是分离的,需要单独引入compile
例如
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
在代码中配置CallAdapterFactory
Retrofit retrofit =new Retrofit.Builder()
.baseUrl(Constants.BASE_HTTP_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
之后就可以把请求的返回改为Observable
了
@GET("users")
Observable> listRepos(String user);
请求时只需要
BaseNetwork.Factory.create(Foo.class)
.listRepos("user")
.observeOn(AndroidSchedulers.mainThread())//观察者所在的线程
.subscribeOn(Schedulers.io())//请求执行的线程
//如果正常执行会顺序调用onNext,onCompleted,如果出错则会调用onError
.subscribe(new Observer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(List list) {
}
});
如果需要配合服务器返回固定的格式,通过状态码判断业务是否出错,如果出错获取错误信息,类似如下格式
{
"state":0,//状态码,0为业务正常
"msg":"",//如果业务出错,携带错误信息
"data":{}//包含实际业务实体
}
需要定义统一的响应实体,根据T
传入的类型来获取业务实体真实的类型
public class BaseResult {
private int state;
private String msg;
private T data;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
请求中的泛型类型需要是BaseResult
@GET("users")
Observable>> listRepos(@Path("user") String user);
调用时也会有改变,需要经过一次拆解统一返回,处理错误的过程
BaseNetwork.Factory.create(Foo.class)
.listRepos("user")
.flatMap(new NetworkResultFunc1>())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(observer);
flatMap
需要传入Func1
,Func1
继承了Function
,只有一个方法,将泛型参数列表的第一个转换为第二个返回,它可以将Observable
做一个展开,并返回一个新的Observable
public interface Func1 extends Function {
R call(T t);
}
NetworkResultFunc1
实现了>
Func1
,代码如下
public class NetworkResultFunc1 implements Func1, Observable> {
@Override
public Observable call(final BaseResult tBaseResult) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super T> subscriber) {
int state = tBaseResult.getState();
String msg = tBaseResult.getMsg();
switch (state) {
case 0://if success, return data to client
subscriber.onNext(tBaseResult.getData());
break;
case 1000://if this means error
subscriber.onError(new ApiException(state, msg));
break;
}
subscriber.onCompleted();//no error, will execute onCompleted()
}
});
}
}
如果state为0,则调用subscriber.onNext()
向调用者返回数据,当state不等于0时意味着业务出错了,向subscriber.onError()
中抛了一个ApiException
,这样在Observer
处会回调onError()
终止整个事件流,调用者也能获得业务错误的相关信息。ApiException
代码如下,就是一个自定义的RuntimeException
public class ApiException extends RuntimeException {
private int state;
private String msg;
public ApiException(int state, String msg) {
this.state = state;
this.msg = msg;
}
public int getState() {
return state;
}
public String getMsg() {
return msg;
}
}
对于Retrofit
的介绍就先到这里,相信看到这里,你已经能够在项目中优雅的使用Retrofit
了。