Retrofit注解

请求方法
注解代码 请求格式
@GET GET请求
@POST POST请求
@DELETE DELETE请求
@HEAD HEAD请求
@OPTIONS OPTIONS请求
@PATCH PATCH请求
请求参数
注解代码 说明
@Headers 添加请求头
@Path 替换路径
@Query 替代参数值,通常是结合get请求的
@FormUrlEncoded 用表单数据提交
@Field 替换参数值,是结合post请求的
Retrofit请求的简单用法

以官方给出的demo为例:

public final class SimpleService {
public static final String API_URL = “https://api.github.com”;

public static class Contributor {
public final String login;
public final int contributions;

public Contributor(String login, int contributions) {this.login = login;this.contributions = contributions;
}

}

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

public static void main(String… args) throws IOException {
// Create a very simple REST adapter which points the GitHub API.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();

// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");// Fetch and print a list of the contributors to the library.
List<Contributor> contributors = call.execute().body();
for (Contributor contributor : contributors) {System.out.println(contributor.login + " (" + contributor.contributions + ")");
}

}
}

请求方式

Get方法

  1. @Query

Get方法请求参数都会以key=value的方式拼接在url后面,Retrofit提供了两种方式设置请求参数。第一种就是像上文提到的直接在interface中添加@Query注解,还有一种方式是通过Interceptor实现,直接看如何通过Interceptor实现请求参数的添加。

public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl httpUrl = request.url().newBuilder()
.addQueryParameter(“token”, “tokenValue”)
.build();
request = request.newBuilder().url(httpUrl).build();
return chain.proceed(request);
}
}

addQueryParameter就是添加请求参数的具体代码,这种方式比较适用于所有的请求都需要添加的参数,一般现在的网络请求都会添加token作为用户标识,那么这种方式就比较适合。

创建完成自定义的Interceptor后,还需要在Retrofit创建client处完成添加

addInterceptor(new CustomInterceptor())
1
2. @QueryMap

如果Query参数比较多,那么可以通过@QueryMap方式将所有的参数集成在一个Map统一传递,还以上文中的get请求方法为例

public interface BlueService {
@GET(“book/search”)
Call getSearchBooks(@QueryMap Map<String, String> options);
}

调用的时候将所有的参数集合在统一的map中即可

Map<String, String> options = new HashMap<>();
map.put(“q”, “小王子”);
map.put(“tag”, null);
map.put(“start”, “0”);
map.put(“count”, “3”);
Call call = mBlueService.getSearchBooks(options);

  1. Query集合

假如你需要添加相同Key值,但是value却有多个的情况,一种方式是添加多个@Query参数,还有一种简便的方式是将所有的value放置在列表中,然后在同一个@Query下完成添加,实例代码如下:

public interface BlueService {
@GET(“book/search”)
Call getSearchBooks(@Query(“q”) List name);
}

最后得到的url地址为

https://api.douban.com/v2/book/search?q=leadership&q=beyond feelings
1
4. Query非必填

如果请求参数为非必填,也就是说即使不传该参数,服务端也可以正常解析,那么如何实现呢?其实也很简单,请求方法定义处还是需要完整的Query注解,某次请求如果不需要传该参数的话,只需填充null即可。

针对文章开头提到的get的请求,加入按以下方式调用

Call call = mBlueService.getSearchBooks(“小王子”, null, 0, 3);
1
那么得到的url地址为

https://api.douban.com/v2/book/search?q=小王子&start=0&count=3
1
5. @Path

如果请求的相对地址也是需要调用方传递,那么可以使用@Path注解,示例代码如下:

@GET(“book/{id}”)
Call getBook(@Path(“id”) String id);
1
2
业务方想要在地址后面拼接书籍id,那么通过Path注解可以在具体的调用场景中动态传递,具体的调用方式如下:

Call call = mBlueService.getBook(“1003078”);
1
此时的url地址为

https://api.douban.com/v2/book/1003078
1
@Path可以用于任何请求方式,包括Post,Put,Delete等等。

Post请求

  1. @field

Post请求需要把请求参数放置在请求体中,而非拼接在url后面,先来看一个简单的例子

@FormUrlEncoded
@POST(“book/reviews”)
Call addReviews(@Field(“book”) String bookId, @Field(“title”) String title,
@Field(“content”) String content, @Field(“rating”) String rating);

这里有几点需要说明的

@FormUrlEncoded将会自动将请求参数的类型调整为application/x-www-form-urlencoded,假如content传递的参数为Good Luck,那么最后得到的请求体就是

content=Good+Luck
1
FormUrlEncoded不能用于Get请求
@Field注解将每一个请求参数都存放至请求体中,还可以添加encoded参数,该参数为boolean型,具体的用法为

@Field(value = “book”, encoded = true) String book
1
encoded参数为false的话,key-value-pair将会被编码,即将中文和特殊字符进行编码转换

  1. @FieldMap

上述Post请求有4个请求参数,假如说有更多的请求参数,那么通过一个一个的参数传递就显得很麻烦而且容易出错,这个时候就可以用FieldMap

@FormUrlEncoded
@POST(“book/reviews”)
Call addReviews(@FieldMap Map<String, String> fields);

  1. @Body

如果Post请求参数有多个,那么统一封装到类中应该会更好,这样维护起来会非常方便

@FormUrlEncoded
@POST(“book/reviews”)
Call addReviews(@Body Reviews reviews);

public class Reviews {
public String book;
public String title;
public String content;
public String rating;
}

其他请求方式

除了Get和Post请求,Http请求还包括Put,Delete等等,用法和Post相似,所以就不再单独介绍了。

其他必须知道的事项

  1. 添加自定义的header

Retrofit提供了两个方式定义Http请求头参数:静态方法和动态方法,静态方法不能随不同的请求进行变化,头部信息在初始化的时候就固定了。而动态方法则必须为每个请求都要单独设置

静态方法

public interface BlueService {
@Headers(“Cache-Control: max-age=640000”)
@GET(“book/search”)
Call getSearchBooks(@Query(“q”) String name,
@Query(“tag”) String tag, @Query(“start”) int start,
@Query(“count”) int count);
}

当然你想添加多个header参数也是可以的,写法也很简单

public interface BlueService {
@Headers({
“Accept: application/vnd.yourapi.v1.full+json”,
“User-Agent: Your-App-Name”
})
@GET(“book/search”)
Call getSearchBooks(@Query(“q”) String name,
@Query(“tag”) String tag, @Query(“start”) int start,
@Query(“count”) int count);
}

此外也可以通过Interceptor来定义静态请求头

public class RequestInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header(“User-Agent”, “Your-App-Name”)
.header(“Accept”, “application/vnd.yourapi.v1.full+json”)
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}

添加header参数Request提供了两个方法,一个是header(key, value),另一个是.addHeader(key, value),两者的区别是,header()如果有重名的将会覆盖,而addHeader()允许相同key值的header存在
然后在OkHttp创建Client实例时,添加RequestInterceptor即可

private static OkHttpClient getNewClient(){
return new OkHttpClient.Builder()
.addInterceptor(new RequestInterceptor())
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
}

动态方法

public interface BlueService {
@GET(“book/search”)
Call getSearchBooks(
@Header(“Content-Range”) String contentRange,
@Query(“q”) String name, @Query(“tag”) String tag,
@Query(“start”) int start, @Query(“count”) int count);
}

  1. 网络请求日志

调试网络请求的时候经常需要关注一下请求参数和返回值,以便判断和定位问题出在哪里,Retrofit官方提供了一个很方便查看日志的Interceptor,你可以控制你需要的打印信息类型,使用方法也很简单。

首先需要在build.gradle文件中引入logging-interceptor

implementation ‘com.squareup.okhttp3:logging-interceptor:3.12.1’
1
同上文提到的CustomInterceptor和RequestInterceptor一样,添加到OkHttpClient创建处即可,完整的示例代码如下:

private static OkHttpClient getNewClient(){
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.addInterceptor(new CustomInterceptor())
.addInterceptor(logging)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
}

HttpLoggingInterceptor提供了4中控制打印信息类型的等级,分别是NONE,BASIC,HEADERS,BODY,接下来分别来说一下相应的打印信息类型。

NONE

没有任何日志信息

Basic

打印请求类型,URL,请求体大小,返回值状态以及返回值的大小

D/HttpLoggingInterceptor L o g g e r : − − &gt; P O S T / u p l o a d H T T P / 1.1 ( 277 − b y t e b o d y ) D / H t t p L o g g i n g I n t e r c e p t o r Logger: --&gt; POST /upload HTTP/1.1 (277-byte body) D/HttpLoggingInterceptor Logger:−−>POST/uploadHTTP/1.1(277−bytebody)D/HttpLoggingInterceptorLogger: <-- HTTP/1.1 200 OK (543ms, -1-byte body)
1
2
Headers

打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码

<-- 200 OK https://api.douban.com/v2/book/search?q=小王子&start=0&count=3&token=tokenValue (3787ms)
D/OkHttp: Date: Sat, 06 Aug 2016 14:26:03 GMT
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Connection: keep-alive
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: Pragma: no-cache
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Set-Cookie: bid=D6UtQR5N9I4; Expires=Sun, 06-Aug-17 14:26:03 GMT; Domain=.douban.com; Path=/
D/OkHttp: X-DOUBAN-NEWBID: D6UtQR5N9I4
D/OkHttp: X-DAE-Node: dis17
D/OkHttp: X-DAE-App: book
D/OkHttp: Server: dae
D/OkHttp: <-- END HTTP

Body

打印请求和返回值的头部和body信息

<-- 200 OK https://api.douban.com/v2/book/search?q=小王子&tag=&start=0&count=3&token=tokenValue (3583ms)
D/OkHttp: Connection: keep-alive
D/OkHttp: Date: Sat, 06 Aug 2016 14:29:11 GMT
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Pragma: no-cache
D/OkHttp: Connection: keep-alive
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Set-Cookie: bid=ESnahto1_Os; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: X-DOUBAN-NEWBID: ESnahto1_Os
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: X-DAE-Node: dis5
D/OkHttp: Pragma: no-cache
D/OkHttp: X-DAE-App: book
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Server: dae
D/OkHttp: Set-Cookie: bid=5qefVyUZ3KU; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
D/OkHttp: X-DOUBAN-NEWBID: 5qefVyUZ3KU
D/OkHttp: X-DAE-Node: dis17
D/OkHttp: X-DAE-App: book
D/OkHttp: Server: dae
D/OkHttp: {“count”:3,“start”:0,“total”:778,“books”:[{“rating”:{“max”:10,“numRaters”:202900,“average”:“9.0”,“min”:0},“subtitle”:"",“author”:["[法] 圣埃克苏佩里"],“pubdate”:“2003-8”,“tags”:[{“count”:49322,“name”:“小王子”,“title”:“小王子”},{“count”:41381,“name”:“童话”,“title”:“童话”},{“count”:19773,“name”:“圣埃克苏佩里”,“title”:“圣埃克苏佩里”}
D/OkHttp: <-- END HTTP (13758-byte body)

  1. 为某个请求设置完整的URL

假如说你的某一个请求不是以base_url开头该怎么办呢?别着急,办法很简单,看下面这个例子你就懂了

public interface BlueService {
@GET
public Call profilePicture(@Url String url);
}

Retrofit retrofit = Retrofit.Builder()
.baseUrl(“https://your.api.url/”); // baseUrl 中的路径(baseUrl)必须以 / 结束
.build();

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

直接用@Url注解的方式传递完整的url地址即可。

动态设置BaseUrl官方例子

/**

  • This example uses an OkHttp interceptor to change the target hostname dynamically at runtime.
  • Typically this would be used to implement client-side load balancing or to use the webserver
  • that’s nearest geographically.
    */
    public final class DynamicBaseUrl {
    public interface Pop {
    @GET(“robots.txt”)
    Call robots();
    }

static final class HostSelectionInterceptor implements Interceptor {
private volatile String host;

public void setHost(String host) {this.host = host;
}@Override public okhttp3.Response intercept(Chain chain) throws IOException {Request request = chain.request();String host = this.host;if (host != null) {HttpUrl newUrl = request.url().newBuilder().host(host).build();request = request.newBuilder().url(newUrl).build();}return chain.proceed(request);
}

}

public static void main(String… args) throws IOException {
HostSelectionInterceptor hostSelectionInterceptor = new HostSelectionInterceptor();

OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(hostSelectionInterceptor).build();Retrofit retrofit = new Retrofit.Builder().baseUrl("http://www.github.com/").callFactory(okHttpClient).build();Pop pop = retrofit.create(Pop.class);Response<ResponseBody> response1 = pop.robots().execute();
System.out.println("Response from: " + response1.raw().request().url());
System.out.println(response1.body().string());hostSelectionInterceptor.setHost("www.pepsi.com");Response<ResponseBody> response2 = pop.robots().execute();
System.out.println("Response from: " + response2.raw().request().url());
System.out.println(response2.body().string());

}
}

  1. 取消请求

Call提供了cancel方法可以取消请求,前提是该请求还没有执行

String fileUrl = “http://futurestud.io/test.mp4”;
Call call =
downloadService.downloadFileWithDynamicUrlSync(fileUrl);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response 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?");}
}

});
}

// 触发某个动作,例如用户点击了取消请求的按钮
call.cancel();
}

Retrofit在项目中实际使用

封装特点:

1.支持日志拦截
2.支持设置全局超时时间
3.支持 RESTful 设计标准设计(全面支持GET、POST、PUT、DELETE等请求方式)
4.支持请求缓存
5.支持设置通用请求头和请求参数
6.与LifecycleOwner结合,网络请求可以根据lifecycleOwner生命周期选择执行请求或者自动取消请求
7.请求路径如果是全url路径的话,会覆盖baseUrl,如请求第三方接口获取天气数据或微信登录授权等
8.其他后期完善
项目中采用了组件化开发,我们把网络请求封装成请求库(如:module_net_retrofit_lib),在网络请求库中配置如下:

dependencies {
//自行封装的依赖库(根据情况配置)
compileOnly ‘cc.times.lib:core-common:1.1.5’
compileOnly ‘cc.times.lib:core-widget:1.0.13’
compileOnly ‘cc.times.lib:lifecycle:1.0.4’

// 网络请求框架,项目地址:https://github.com/square/retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'// 网络请求框架,项目地址:https://github.com/square/okhttp
api 'com.squareup.okhttp3:okhttp:3.12.1'
api 'com.squareup.okhttp3:logging-interceptor:3.12.1'// OkHttp3 Cookie 缓存框架,项目地址:https://github.com/franmontiel/PersistentCookieJar
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'// RxJava2,项目地址:https://github.com/ReactiveX/RxJava
implementation "io.reactivex.rxjava2:rxjava:2.2.8"// json解析框架,项目地址:https://github.com/google/gson
implementation 'com.google.code.gson:gson:2.8.5'

}

网络配置初始化:在Application中

/*** 开发环境网络请求配置*/
fun debugConfig() {val httpConfig = HttpConfig.Builder().baseUrl(CommonApi.apiBaseUrl)// 打印使用http请求日志.addInterceptor(ChuckInterceptor(AppUtil.context)).setLogLevel(HttpLoggingInterceptor.Level.BODY)// 设置全局超时时间.connectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT).readTimeoutMillis(OTHER_TIME_OUT).writeTimeoutMillis(OTHER_TIME_OUT).build()HttpUtil.initHttpConfig(httpConfig)
}

工具类:

object HttpUtil {

internal lateinit var httpConfig: HttpConfigfun initHttpConfig(config: HttpConfig) {httpConfig = config
}fun get(url: String): GetRequest = GetRequest(url)fun post(url: String, isJson: Boolean = false): PostRequest = PostRequest(url, isJson)fun put(url: String, isJson: Boolean = false): PutRequest = PutRequest(url, isJson)fun delete(url: String): DeleteRequest = DeleteRequest(url)fun head(url: String): HeadRequest = HeadRequest(url)fun options(url: String): OptionsRequest = OptionsRequest(url)fun patch(url: String): PatchRequest = PatchRequest(url)fun <T> retryRequest(baseCallback: BaseCallback<T>): Disposable? {return baseCallback.request.execute(baseCallback)
}

}

网络请求配置工具类:

class HttpConfig(
baseUrl: String,
interceptors: MutableList,
networkInterceptors: MutableList,
private val defaultConnectTimeout: Long,
private val defaultReadTimeout: Long,
private val defaultWriteTimeout: Long,
retryOnConnectionFailure: Boolean,
isUseCookie: Boolean,
isUseCache: Boolean,
logLevel: HttpLoggingInterceptor.Level,
val commonHeaders: ArrayMap<String, String>,
val commonParams: ArrayMap<String, String>,
sslParam: SSLParam,
hostnameVerifier: HostnameVerifier
) {
companion object {
const val LOG_MAX_LENGTH = 10_000
const val CACHE_SIZE = 10 * 1024 * 1024L
const val CACHE_DIR = “okhttp”
}

private val okHttpClient: OkHttpClient
internal val retrofit: Retrofit
internal val httpMethod: HttpMethodinit {val okHttpClientBuilder = OkHttpClient.Builder()// 设置超时时间okHttpClientBuilder.connectTimeout(defaultConnectTimeout, TimeUnit.MILLISECONDS)okHttpClientBuilder.readTimeout(defaultReadTimeout, TimeUnit.MILLISECONDS)okHttpClientBuilder.writeTimeout(defaultWriteTimeout, TimeUnit.MILLISECONDS)// 设置是连接失败时是否重试okHttpClientBuilder.retryOnConnectionFailure(retryOnConnectionFailure)// 添加拦截器interceptors.forEach { okHttpClientBuilder.addInterceptor(it) }networkInterceptors.forEach { okHttpClientBuilder.addNetworkInterceptor(it) }// 设置是否使用Cookieif (isUseCookie) {okHttpClientBuilder.cookieJar(PersistentCookieJar(SetCookieCache(),SharedPrefsCookiePersistor(AppUtil.context)))}// 设置是否使用Cacheif (isUseCache) {okHttpClientBuilder.cache(Cache(File(AppUtil.context.cacheDir, CACHE_DIR), CACHE_SIZE))}// 设置打印日志if (logLevel != HttpLoggingInterceptor.Level.NONE) {val httpLoggingInterceptor = HttpLoggingInterceptor {if (it.isEmpty()) {return@HttpLoggingInterceptor} else if (it.startsWith("{") && it.endsWith("}")) {LogUtil.json(it, false)} else {if (it.length > LOG_MAX_LENGTH) {LogUtil.v(it.substring(0, LOG_MAX_LENGTH), false)} else {LogUtil.v(it, false)}}}httpLoggingInterceptor.level = logLevelokHttpClientBuilder.addInterceptor(httpLoggingInterceptor)}// 配置httpsokHttpClientBuilder.sslSocketFactory(sslParam.sslSocketFactory, sslParam.trustManager)okHttpClientBuilder.hostnameVerifier(hostnameVerifier)okHttpClient = okHttpClientBuilder.build()retrofit = Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).callFactory { newCall(it) }.build()httpMethod = retrofit.create(HttpMethod::class.java)
}private fun newCall(request: Request): Call {// 判断用户是否在请求中设置了超时时间,如果设置了移除该Header// 同时判断该超时时间是否和设置的通用超时时间是否相同,如果相同,不认为用户单为这个请求设置了单独的超时时间val builder = request.newBuilder()var connectTimeout = 0Lrequest.header(HttpHeader.HEAD_SINGLE_REQUEST_CONNECT_TIMEOUT)?.let {val timeout = it.toLong()if (timeout != defaultConnectTimeout) {connectTimeout = timeout}builder.removeHeader(HttpHeader.HEAD_SINGLE_REQUEST_CONNECT_TIMEOUT)}var readTimeout = 0Lrequest.header(HttpHeader.HEAD_SINGLE_REQUEST_READ_TIMEOUT)?.let {val timeout = it.toLong()if (timeout != defaultReadTimeout) {readTimeout = timeout}builder.removeHeader(HttpHeader.HEAD_SINGLE_REQUEST_READ_TIMEOUT)}var writeTimeout = 0Lrequest.header(HttpHeader.HEAD_SINGLE_REQUEST_WRITE_TIMEOUT)?.let {val timeout = it.toLong()if (timeout != defaultWriteTimeout) {writeTimeout = timeout}builder.removeHeader(HttpHeader.HEAD_SINGLE_REQUEST_WRITE_TIMEOUT)}return if (connectTimeout + readTimeout + writeTimeout > 0L) {// 超时时间大于0,说明用户设置了新超时时间,基于原来的okHttpClient构建一个使用新的超时时间的okHttpClient执行网络请求okHttpClient.newBuilder().connectTimeout(if (connectTimeout == 0L) defaultConnectTimeout else connectTimeout,TimeUnit.MILLISECONDS).readTimeout(if (readTimeout == 0L) defaultReadTimeout else readTimeout, TimeUnit.MILLISECONDS).writeTimeout(if (writeTimeout == 0L) defaultWriteTimeout else writeTimeout, TimeUnit.MILLISECONDS).build().newCall(builder.build())} else {// 用户没有设置超时时间或设置了通用超时时间一样的超时时间,使用默认的okHttpClient执行网络请求okHttpClient.newCall(request)}
}/*** 网络请求配置构建者*/
class Builder {private var baseUrl = ""private var interceptors: ArrayList<Interceptor> = ArrayList()private var networkInterceptors: ArrayList<Interceptor> = ArrayList()private var defaultConnectTimeout = 10_000Lprivate var defaultReadTimeout = 10_000Lprivate var defaultWriteTimeout = 10_000Lprivate var retryOnConnectionFailure = falseprivate var isUseCookie = falseprivate var isUseCache = falseprivate var logLevel = HttpLoggingInterceptor.Level.NONEprivate val commonHeaders = ArrayMap<String, String>()private val commonParams = ArrayMap<String, String>()private var sslParam: SSLParam = HttpsUtil.getSslSocketFactory()private var hostnameVerifier: HostnameVerifier = HttpsUtil.UnSafeHostnameVerifierfun baseUrl(url: String): HttpConfig.Builder {baseUrl = urlreturn this}fun addInterceptor(interceptor: Interceptor): HttpConfig.Builder {interceptors.add(interceptor)return this}fun addNetworkInterceptor(interceptor: Interceptor): HttpConfig.Builder {networkInterceptors.add(interceptor)return this}/*** 连接超时时间* @param millis 单位是毫秒(默认10秒)*/fun connectTimeoutMillis(millis: Long): HttpConfig.Builder {if (millis <= 0) {throw IllegalArgumentException("connect timeout must Greater than 0")}defaultConnectTimeout = millisreturn this}/*** 读取超时时间* @param millis 单位是毫秒(默认10秒)*/fun readTimeoutMillis(millis: Long): HttpConfig.Builder {if (millis <= 0) {throw IllegalArgumentException("read timeout must Greater than 0")}defaultReadTimeout = millisreturn this}/*** 写入超时时间* @param millis 单位是毫秒(默认10秒)*/fun writeTimeoutMillis(millis: Long): HttpConfig.Builder {if (millis <= 0) {throw IllegalArgumentException("write timeout must Greater than 0")}defaultWriteTimeout = millisreturn this}/*** 连接失败时是否重新进行网络请求* @param retryOnConnectionFailure 默认为false*/fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean): HttpConfig.Builder {this.retryOnConnectionFailure = retryOnConnectionFailurereturn this}/*** 是否开启cookie* @param isUseCookie 默认为false*/fun useCookie(isUseCookie: Boolean): HttpConfig.Builder {this.isUseCookie = isUseCookiereturn this}/*** 是否使用缓存* @param isUseCache 默认为false*/fun useCache(isUseCache: Boolean): HttpConfig.Builder {this.isUseCache = isUseCachereturn this}/*** 设置日志级别,参考[HttpLoggingInterceptor.Level]* @param level 默认为[HttpLoggingInterceptor.Level.NONE]*/fun setLogLevel(level: HttpLoggingInterceptor.Level): HttpConfig.Builder {logLevel = levelreturn this}/*** 设置通用请求header* @param key header键* @param value header值*/fun commonHeader(key: String, value: String): HttpConfig.Builder {commonHeaders[key] = valuereturn this}/*** 设置通用请求参数* @param key 参数键* @param value 参数值*/fun commonParam(key: String, value: String): HttpConfig.Builder {commonParams[key] = valuereturn this}/*** 配置ssl* @param param ssl参数,默认不对证书做任何检查*/fun sslSocketFactory(param: SSLParam): HttpConfig.Builder {sslParam = paramreturn this}/*** 主机名验证* @param verifier 默认允许所有主机名*/fun hostnameVerifier(verifier: HostnameVerifier): HttpConfig.Builder {hostnameVerifier = verifierreturn this}fun build(): HttpConfig {return HttpConfig(baseUrl, interceptors, networkInterceptors, defaultConnectTimeout, defaultReadTimeout, defaultWriteTimeout, retryOnConnectionFailure, isUseCookie, isUseCache, logLevel, commonHeaders, commonParams, sslParam, hostnameVerifier)}
}

}

网络请求基类:

abstract class BaseRequest<N : BaseRequest>(protected val url: String) {

companion object {val userAgent = HttpHeader.getUserAgent()val MEDIA_TYPE_STREAM = MediaType.parse("application/octet-stream")!!val MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8")/*** 错误类型*/const val ERROR_NET = -1const val ERROR_CONNECT = -2const val ERROR_TIMEOUT = -3const val ERROR_SERVER = -4const val ERROR_DATA = -5const val ERROR_HANDLE = -6const val ERROR_UNKNOWN = -7
}// 请求header
protected val headers = ArrayMap<String, String>()
// 请求参数
protected val params = ArrayMap<String, String>()
// 生命周期所有者
var lifecycleOwner: LifecycleOwner? = nullprivate set
// 是否为head请求
protected var isHeadRequest = false@Suppress("UNCHECKED_CAST")
fun header(key: String, value: String): N {headers[key] = valuereturn this as N
}@Suppress("UNCHECKED_CAST")
open fun param(key: String, value: String): N {params[key] = valuereturn this as N
}/*** 设置实现了LifecycleOwner的子类* @param owner 实现了LifecycleOwner的子类,非必传* 如果设置了该字段,那么只能在[Lifecycle.State.DESTROYED]之前发起网络请求,* 如果在网络请求的过程中生命周期到了[Lifecycle.State.DESTROYED],将会自动取消执行网络请求* 如果不设置该字段,网络请求会一直进行下去,直到请求完成*/
@Suppress("UNCHECKED_CAST")
fun attachToLifecycle(owner: LifecycleOwner): N {lifecycleOwner = ownerreturn this as N
}/*** 连接超时时间* @param millis 单位是毫秒*/
@Suppress("UNCHECKED_CAST")
fun connectTimeoutMillis(millis: Long): N {if (millis <= 0) {throw IllegalArgumentException("connect timeout must Greater than 0")}header(HttpHeader.HEAD_SINGLE_REQUEST_CONNECT_TIMEOUT, millis.toString())return this as N
}/*** 读取超时时间* @param millis 单位是毫秒*/
@Suppress("UNCHECKED_CAST")
fun readTimeoutMillis(millis: Long): N {if (millis <= 0) {throw IllegalArgumentException("read timeout must Greater than 0")}header(HttpHeader.HEAD_SINGLE_REQUEST_READ_TIMEOUT, millis.toString())return this as N
}/*** 写入超时时间* @param millis 单位是毫秒*/
@Suppress("UNCHECKED_CAST")
fun writeTimeoutMillis(millis: Long): N {if (millis <= 0) {throw IllegalArgumentException("write timeout must Greater than 0")}header(HttpHeader.HEAD_SINGLE_REQUEST_WRITE_TIMEOUT, millis.toString())return this as N
}/*** 异步执行网络请求* @return 用于解除订阅*/
open fun <T> execute(callback: BaseCallback<T>): Disposable? {// 生命周期所有者不为null且生命周期已经处于销毁状态,那么不执行网络请求if (lifecycleOwner != null && lifecycleOwner!!.lifecycle.currentState == Lifecycle.State.DESTROYED) {return null}// 如果是head请求,那么只能使用HeadRequestCallbackif (isHeadRequest) {if (callback !is HeadRequestCallback) {throw IllegalArgumentException("Head Request should only use HeadRequestCallback")}}checkHeadersAndParams()callback.request = this// 执行网络请求val disposable = getRequestMethod(callback).map {if (it.isSuccessful) {callback.convertResponse(it)} else {throw ServerException(it.message())}}.applyScheduler().subscribe({try {callback.onSuccess(it!!)} catch (e: Exception) {LogUtil.printStackTrace(e)callback.onError(ERROR_HANDLE, ResourcesUtil.getString(R.string.net_retrofit_error_handle))} finally {callback.onComplete()}}, {try {LogUtil.printStackTrace(it)handleRequestError(callback, it as Exception)} catch (e: Exception) {LogUtil.printStackTrace(e)callback.onError(ERROR_HANDLE, ResourcesUtil.getString(R.string.net_retrofit_error_handle))} finally {callback.onComplete()}})// 当生命周期所有者不为null,监听生命周期变化,如果生命周期走到onDestroy,取消网络请求lifecycleOwner?.let { disposable.attachToLifecycle(it) }return disposable
}/*** 自行处理网络请求*/
fun execute(): Observable<Response<ResponseBody>> {checkHeadersAndParams()return getRequestMethod(null)
}abstract fun getRequestMethod(callback: BaseCallback<*>?): Observable<Response<ResponseBody>>protected fun toRequestBody(file: File): RequestBody {return RequestBody.create(guessMimeType(file.name), file)
}protected open fun checkHeadersAndParams() {// 如果用户没有设置userAgent,那么设置默认的userAgentif (!headers.containsKey(HttpHeader.HEAD_KEY_USER_AGENT)) {headers[HttpHeader.HEAD_KEY_USER_AGENT] = userAgent}// 设置通用请求头和请求参数HttpUtil.httpConfig.commonHeaders.entries.forEach { header(it.key, it.value) }HttpUtil.httpConfig.commonParams.entries.forEach { param(it.key, it.value) }
}private fun handleRequestError(callback: BaseCallback<*>, e: Exception) {when (e) {is UnknownHostException -> callback.onError(ERROR_NET,ResourcesUtil.getString(R.string.net_retrofit_error_net))is ConnectException -> callback.onError(ERROR_CONNECT,ResourcesUtil.getString(R.string.net_retrofit_error_connect))is SocketTimeoutException -> callback.onError(ERROR_TIMEOUT,ResourcesUtil.getString(R.string.net_retrofit_error_timeout))is ServerException -> {if (e.message == null || e.message!!.isEmpty()) {callback.onError(ERROR_SERVER, ResourcesUtil.getString(R.string.net_retrofit_error_server))} else {callback.onError(ERROR_SERVER, e.message!!)}}is NullPointerException -> callback.onError(ERROR_DATA,ResourcesUtil.getString(R.string.net_retrofit_error_data))else -> callback.onError(ERROR_UNKNOWN, ResourcesUtil.getString(R.string.net_retrofit_error_unknown))}
}private fun guessMimeType(fileName: String): MediaType {// 解决文件名中含有#号异常的问题val name = fileName.replace("#", "")val fileNameMap = URLConnection.getFileNameMap()val contentType = fileNameMap.getContentTypeFor(name) ?: return MEDIA_TYPE_STREAMreturn MediaType.parse(contentType) ?: return MEDIA_TYPE_STREAM
}

}

POST请求工具类

class PostRequest(url: String, private val isJson: Boolean = false) : BaseRequest(url) {
private val jsonObj = JSONObject()
private var fileParts = ArrayList<MultipartBody.Part>()

override fun param(key: String, value: String): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: Boolean): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: Int): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: Long): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: Float): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: Double): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: JSONObject): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: JSONArray): PostRequest {jsonObj.put(key, value)return this
}fun param(key: String, value: Collection<*>): PostRequest {jsonObj.put(key, JSONArray(JSONTokener(JsonUtil.toJson(value))))return this
}fun param(key: String, value: File): PostRequest {if (isJson) {throw IllegalArgumentException("Content-Type is application/json, param can not be file!")}fileParts.add(MultipartBody.Part.createFormData(key, value.name, toRequestBody(value)))return this
}fun param(key: String, value: List<File>): PostRequest {if (isJson) {throw IllegalArgumentException("Content-Type is application/json, param can not be file!")}for (item in value) {fileParts.add(MultipartBody.Part.createFormData(key, item.name, toRequestBody(item)))}return this
}override fun getRequestMethod(callback: BaseCallback<*>?): Observable<Response<ResponseBody>> {return if (isJson) {val body = RequestBody.create(MEDIA_TYPE_JSON, jsonObj.toString())HttpUtil.httpConfig.httpMethod.post(url, headers, ProgressRequestBody(body, callback))} else {val builder = MultipartBody.Builder()if (jsonObj.length() + fileParts.size == 0) {// 如果没有一个表单项都没有,则增加一个空字符串表单项builder.addFormDataPart("", "")} else {val keys = jsonObj.keys()for (key in keys) {builder.addFormDataPart(key, jsonObj.get(key).toString())}fileParts.forEachByIndex { builder.addPart(it) }}val body = builder.setType(MultipartBody.FORM).build()builder.setType(MultipartBody.FORM)HttpUtil.httpConfig.httpMethod.post(url, headers, ProgressRequestBody(body, callback))}
}

GET请求工具类:(PUT、DELETE、PATCH请求类似)

class GetRequest(url: String) : BaseRequest(url) {

override fun getRequestMethod(callback: BaseCallback<*>?): Observable<Response<ResponseBody>> {return HttpUtil.httpConfig.httpMethod.get(url, headers, params)
}

}

网络请求回调类,根据服务器的返回数据不同(实体类、数组、字符串等分别封装),根据项目需求,同时可以在

CZBaseCallback中添加token过期是否重新请求等功能。

/**

  • 网络请求回调,返回数据为实体类
    **/
    abstract class CZObjectCallback(private val clazz: Class, isHandleErrorSelf: Boolean = false) : CZBaseCallback(isHandleErrorSelf) {

    override fun onSuccess(data: String) {
    val responseData = JSONObject(data)
    val code = responseData.getInt(“code”)
    val message = responseData.getString(“msg”)

      if (code == 0) {val disposable = Observable.just(responseData).map { it.getJSONObject("data").toString() }.map { JsonUtil.parseObject(it, clazz)!! }.applyScheduler().subscribe({success(it)},{LogUtil.printStackTrace(it)onError(BaseRequest.ERROR_DATA, "")})request.lifecycleOwner?.let { disposable.attachToLifecycle(it) }} else {handleAsyncRequestError(code, message,this@CZObjectCallback)}
    

    }

    abstract fun success(data: T)
    }

网络请求回调基类:

abstract class CZBaseCallback(private val isHandleErrorSelf: Boolean) : StringCallback() {

companion object {// 是否正在更新tokenvar isUpdatingToken = false
}override fun onError(code: Int, message: String) {super.onError(code, message)if (code < 0) {if (code == BaseRequest.ERROR_SERVER) {error(code, ResourcesUtil.getString(R.string.common_request_error_server))} else {error(code, ResourcesUtil.getString(R.string.common_request_error_net))}} else {error(code, message)}
}open fun error(code: Int, message: String) {}protected fun handleAsyncRequestError(code: Int, msg: String, callback: CZBaseCallback) {if (isHandleErrorSelf) {// 不需要处理错误情况,交给该请求自行处理onError(code, msg)return}when (code) {// token过期,刷新token103 -> updateToken(callback)// 换手机登录时可能出现104 -> LogoutTool.logout()else -> onError(code, msg)}
}/*** 更新token*/
private fun updateToken(callback: CZBaseCallback) {if (isUpdatingToken) {// 如果已经有请求在更新token,监听token是否更新AuthorityManager.addUpdateTokenCallback {// token更新成功,重新发起请求HttpTool.retryRequest(callback)}return}isUpdatingToken = trueRouteUtil.getServiceProvider(ILaunchService::class.java)?.updateToken()?.execute(object : CZObjectCallback<LoginEntity>(LoginEntity::class.java, true) {override fun success(data: LoginEntity) {AuthorityManager.updateToken(data.token)isUpdatingToken = falseHttpTool.retryRequest(callback)}override fun error(code: Int, message: String) {super.error(code, message)isUpdatingToken = falseLogoutTool.logout(desc = ResourcesUtil.getString(R.string.common_account_error))}})
}

}

接口调用实例(以登录为例):

object LaunchApi {
// 登录
private const val LOGIN = “user/login”
/**
* 登录
* @param account 登录帐号, mobile:手机号,open_id:微信open_id
* @param method 登录方式,sms:短信登录, wechat:微信登录
* @param password 口令, 包括:vcode(验证码),token(微信token)
*/
fun login(account: String, method: String, password: String): CZPostRequest {
return HttpTool.post(LOGIN)
.param(“account”, account)
.param(“method”, method)
.param(“passwd”, password)
}
}

在登录界面调用:

LaunchApi.login(account, method, passwd)
.attachToLifecycle(this)
.execute(object : CZObjectCallback(LoginEntity::class.java) {
override fun success(data: LoginEntity) {
//登录成功
}
override fun error(code: Int, message: String) {
super.error(code, message)
//登录失败
}
})

接口调用说明:在项目中使用了组件化,请求接口LaunchApi中为启动组件,该组件中只定义了启动相关的接口,在请求时,如果添加了attachToLifecycle,网络请求会根据生命周期的不同,自动控制网络请求会自行取消。

Android中网络请求框架的封装-Retrofit+RxJava+OkHttp相关推荐

  1. Retrofit + Kotlin + MVVM 的网络请求框架的封装尝试

    1.前言 之前在学习郭霖<第一行代码>时按部就班地写过一个彩云天气 App,对里面的网络请求框架的封装印象非常深刻,很喜欢这种 Retrofit + Kotlin + 协程的搭配使用.随后 ...

  2. swift中网络请求库的封装(Alamofire+HandyJSON)

    swift中网络请求库的封装(Alamofire+HandyJSON) 我们用swift语言来写iOS的程序,会用Alamofire+HandyJSON来取代AFNetworking+MJExtens ...

  3. Android主流网络请求框架

    一.Volley google推出的异步网络请求框架和图片加载框架.特别适合数据量小,通信频繁的网络操作.android绝大多数都属于这种类型,但是对于数据量比较大的操作,比如:下载,就不太适用了. ...

  4. android okgo 网络请求框架

    OkGo - OkHttpUtils-2.0.0升级后改名 OkGo,全新完美支持RxJava 项目地址:https://github.com/jeasonlzy,欢迎star,欢迎issue 该库是 ...

  5. android自定义网络请求框架,安卓快速开发框架(十九)XBaseAndroid Http网络请求

    网络请求 XBaseAndroid网络请求内置模块是采用hongyangAndroid的工具类二次开发. 目前对应okhttp版本3.3.1. 用法 目前对以下需求进行了封装 一般的get请求 一般的 ...

  6. android之网络请求框架OKhttp、原生http请求

    ===============原生http===================== public class MainActivity extends AppCompatActivity imple ...

  7. android中网络请求中页面关闭了会怎么样

    这是我在面试的时候,别人问的,其实 ,在实际开发中,并没有怎么遇到: 如果activity中开启了一个网络请求,正在请求中的时候,activity关闭了,那网络请求会怎么样? 因为之前没有遇到过这个问 ...

  8. Android中网络请求创建单个线程池的方法

    创建单个线程池的方法 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; impor ...

  9. Kotlin+Retrofit + MVVM 的网络请求框架的封装

    Kotlin 代码我上传了,里面注释写的很详细,大家有什么不懂的可以私信我, 因为文章内容实在是太多了,不想写 代码地址: https://wwp.lanzoum.com/iDOvK0eb0ude 密 ...

最新文章

  1. dataframe for 循环 数据格式 python_Python中的for循环
  2. 基于UDP的socket客户服务器编程
  3. 《IBM-PC汇编语言程序设计》(第2版)【沈美明 温冬婵】——第一章——自编解析与答案
  4. 空军哥军姐新装——帅
  5. lisp java_从Java调用的LISP代码
  6. C#委托和事件实现观察者模式
  7. java jtextfield 居中_有什么办法可以使JFrame的中心居中吗? - java
  8. java 多个队列处理_加入多处理队列需要很长时间
  9. Unity3D 多平台_预编译相关宏定义
  10. DPDK编程指南 (1 --3)
  11. Ubuntu桌面版QQ安装 Linux/UbuntuQQ安装/centos QQ安装教程 2019/10/24
  12. matlab中升余弦滚降滤波器_升余弦滤波器原理
  13. MHDD检测不到硬盘的解决办法
  14. The JSP specification requires that an attribute name is preceded by whitespace 解决
  15. Gooxi国产化服务器专题介绍之海光服务器
  16. 综合实践计算机的入门知识教学设计,3-6年级综合实践活动3.我是电脑小画家_教案、教学设计_市级优课(0001)【信息技术】.doc...
  17. 任务调度框架Quartz用法指南(超详细)
  18. PHP使用 Redis 实现消息队列
  19. 全球与中国汽车线性稳压器市场运营状况及未来前景展望报告2022-2028年版
  20. 计算机教师暑期到企业实践总结,国培教师企业实践总结

热门文章

  1. 2022安徽安全员C考试单选题库预测分享
  2. sheel脚本 centos7环境 自动安装 zeppelin
  3. go语言单元测试之三:go语言用goconvey库做单元测试
  4. Qt控件样式 Style Sheet Demo
  5. 多串口服务器 最多有多少口,多串口服务器原理_多串口服务器接线示意图
  6. 解读老黄历--节气年神
  7. LED显示行业之视频处理器
  8. 立体相机标定数据集_超全的3D视觉数据集汇总
  9. 华为手机同步苹果日历的日程
  10. 配置两台物理机的VMware 虚拟机互联互通