摘要:作用于方法表示请求发送数据,使用该注解,表示请求体是多部分的,每个部分作为一个参数,且用注解声明。其它对象类型将通过使用转换器转换为适当的格式。
目录介绍
1.关于Retrofit基本介绍
2.最简单使用【配合Rx使用】
3.注解的种类
请求方法注解
请求头注解
标记注解
参数注解
其它注解
4.Retrofit相关请求参数
@Query()【备注:get请求/ 接上参数 】
@QueryMap()【备注:get请求/ 接上参数 】
@Path()【备注:get请求/ 替换url中某个字段】
@Body()【备注:post请求/ 指定一个对象作为HTTP请求体】
@Field()【备注:post请求/ 用于传送表单数据】
@FieldMap()【备注:post请求/ 用于传送表单数据】
@Header/@Headers()【备注: 添加请求头部 】
@Part()作用于方法的参数,用于定义Multipart请求的每和part
@PartMap()作用于方法的参数
使用时注意事项
5.Retrofit与RxJava结合
使Rxjava与retrofit结合条件
可以看到 Observable观察者
可以看到订阅者
6.OkHttpClient
拦截器说明
日志拦截器
请求头拦截器
统一请求拦截器
缓存拦截器
自定义CookieJar
7.踩坑经验
url被转义
8.Form表单提交与multipart/form-data
8.1 form表单常用属性
8.2 浏览器提交表单时,会执行如下步骤
8.3 提交方式
8.4 POST请求
8.5 enctype指定的content-type
9.content-type介绍
9.1 application/x-www-form-urlencoded
9.2 application/json
9.3 text/xml
9.4 multipart/form-data
10.Retrofit源码深入分析
10.1 设计模式分析[建造者模式]
10.2 如何理解动态代理模式
10.3 如何拦截方法,解析注解
10.4 如何构建Retrofit的Call
10.5 如何执行网络异步请求enqueue方法
N.关于其他
参考博客
版本更新说明
博客介绍
1.关于Retrofit基本介绍Retrofit是Square 公司开发的一款正对Android 网络请求的框架。底层基于OkHttp 实现,OkHttp 已经得到了google 官方的认可。
Retrofit是由Square公司出品的针对于Android和Java的类型安全的Http客户端,如果看源码会发现其实本质上是OkHttp的封装,使用面向接口的方式进行网络请求,利用动态生成的代理类封装了网络接口请求的底层,其将请求返回JavaBean,对网络认证REST API进行了很友好的支持。使用Retrofit将会极大的提高我们应用的网络体验。
RxJava + Retrofit + okHttp组合,流行的网络请求框架
Retrofit 负责请求的数据和请求的结果,使用接口的方式呈现,OkHttp 负责请求的过程,RxJava 负责异步,各种线程之间的切换。
RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。总之就是让异步操作变得非常简单。
为什么要使用Retrofit?
优点
请求的方法参数注解可以定制
支持同步、异步和RxJava
超级解耦
可以配置不同的反序列化工具来解析数据,如json、xml等
其他说明
在处理HTTP请求的时候,因为不同场景或者边界情况等比较难处理。你需要考虑网络状态,需要在请求失败后重试,需要处理HTTPS等问题,二这些事情让你很苦恼,而Retrofit可以将你从这些头疼的事情中解放出来。
效率高,其次Retrofit强大且配置灵活,第三和OkHttp无缝衔接,第四Jack Wharton主导的(你懂的)。
2.最简单使用Api接口
public interface DouBookApi { /** * 根据tag获取图书 * @param tag 搜索关键字 * @param count 一次请求的数目 最多100 * https://api.douban.com/v2/book/search?tag=文学&start=0&count=30 */ @GET("v2/book/search") ObservablegetBook(@Query("tag") String tag, @Query("start") int start, @Query("count") int count); }
Model类
public class DouBookModel { private static DouBookModel bookModel; private DouBookApi mApiService; public DouBookModel(Context context) { mApiService = RetrofitWrapper .getInstance(ConstantALiYunApi.API_DOUBAN) //baseUrl地址 .create(DouBookApi.class); } public static DouBookModel getInstance(Context context){ if(bookModel == null) { bookModel = new DouBookModel(context); } return bookModel; } public ObservablegetHotMovie(String tag, int start , int count) { Observable book = mApiService.getBook(tag, start, count); return book; } }
抽取类
public class RetrofitWrapper { private static RetrofitWrapper instance; private Retrofit mRetrofit; public RetrofitWrapper(String url) { OkHttpClient.Builder builder = new OkHttpClient.Builder(); //打印日志 HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(logging).build(); OkHttpClient client = builder.addInterceptor(new LogInterceptor("HTTP")).build(); //解析json Gson gson = new GsonBuilder() .setLenient() .create(); mRetrofit = new Retrofit .Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(client) .build(); } public static RetrofitWrapper getInstance(String url){ //synchronized 避免同时调用多个接口,导致线程并发 synchronized (RetrofitWrapper.class){ instance = new RetrofitWrapper(url); } return instance; } publicT create(final Class service) { return mRetrofit.create(service); } }
使用
DouBookModel model = DouBookModel.getInstance(activity); model.getHotMovie(mType,start,count) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber3.注解的种类() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(DouBookBean bookBean) { } });
请求方法注解
@GET get请求 @POST post请求 @PUT put请求 @DELETE delete请求 @PATCH patch请求,该请求是对put请求的补充,用于更新局部资源 @HEAD head请求 @OPTIONS option请求 @HTTP 通用注解,可以替换以上所有的注解,其拥有三个属性:method,path,hasBody
请求头注解
@Headers 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在 @Header 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头
标记注解
@FormUrlEncoded 表示请求发送编码表单数据,每个键值对需要使用@Field注解 用于修饰Fiedl注解 和FileldMap注解 使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field 注解和 @FieldMap 注解,使用@FormUrlEncoded 注解的请求将具有"application/x-www-form-urlencoded" MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码。 @Multipart 作用于方法 表示请求发送multipart数据,使用该注解,表示请求体是多部分的,每个部分作为一个参数,且用Part注解声明。 @Streaming 作用于方法 未使用@Straming 注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解。 处理返回Response的方法的响应体,用于下载大文件 提醒:如果是下载大文件必须加上@Streaming 否则会报OOM @Streaming @GET CalldownloadFileWithDynamicUrlAsync(@Url String fileUrl);
参数注解
参数注解:@Query 、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap
其它注解
@Path、@Url4.Retrofit相关请求参数
@Query()【备注:get请求/ 接上参数 】
@Query:作用于方法参数,用于添加查询参数,即请求参数 用于在url后拼接上参数,例如: @GET("book/search") CallgetSearchBook(@Query("q") String name);//name由调用者传入 相当于 @GET("book/search?q=name") Call getSearchBook(); 用于Get中指定参数
@QueryMap()【备注:get请求/ 接上参数 】
@QueryMap:作用于方法的参数。以map的形式添加查询参数,即请求参数,参数的键和值都通过String.valueOf()转换为String格式。默认map的值进行URL编码,map中的每一项发键和值都不能为空,否则跑出IllegalArgumentException异常。 当然如果入参比较多,就可以把它们都放在Map中,例如: @GET("book/search") CallgetSearchBook(@QueryMap Map options);
@Path()【备注:get请求/ 替换url中某个字段】
/** * http://api.zhuishushenqi.com/ranking/582ed5fc93b7e855163e707d * @return */ @GET("/ranking/{rankingId}") ObservablegetRanking(@Path("rankingId") String rankingId); @GET("group/{id}/users") Call groupList(@Path("id") int groupId); * 像这种请求接口,在group和user之间有个不确定的id值需要传入,就可以这种方法。我们把待定的值字段用{}括起来,当然 {}里的名字不一定就是id,可以任取,但需和@Path后括号里的名字一样。如果在user后面还需要传入参数的话,就可以用Query拼接上,比如: @GET("group/{id}/users") Call groupList(@Path("id") int groupId, @Query("sort") String sort); * 当我们调用这个方法时,假设我们groupId传入1,sort传入“2”,那么它拼接成的url就是group/1/users?sort=2,当然最后请求的话还会加上前面的baseUrl
@Body()【备注:post请求/ 指定一个对象作为HTTP请求体】
使用@Body 注解定义的参数不能为null 。当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。 可以指定一个对象作为HTTP请求体,比如: @POST("users/new") CallcreateUser(@Body User user); 它会把我们传入的User实体类转换为用于传输的HTTP请求体,进行网络请求。 多用于post请求发送非表单数据,比如想要以post方式传递json格式数据
@Field()【备注:post请求/ 用于传送表单数据】
用于传送表单数据: @FormUrlEncoded @POST("user/edit") CallupdateUser(@Field("first_name") String first, @Field("last_name") String last); 注意开头必须多加上@FormUrlEncoded这句注释,不然会报错。表单自然是有多组键值对组成,这里的first_name就是键,而具体传入的first就是值啦 多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用
@FieldMap()【备注:post请求/ 用于传送表单数据】
@FormUrlEncoded @POST("user/login") Calllogin(@FieldMap Map map);
@Header/@Headers()【备注: 添加请求头部 】
用于动态添加请求头部: @GET("user") CallgetUser(@Header("Authorization") String authorization) 表示将头部Authorization属性设置为你传入的authorization;当然你还可以用@Headers表示,作用是一样的比如: @Headers("Cache-Control: max-age=640000") @GET("user") Call getUser() 当然你可以多个设置: @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" }) @GET("user") Call getUser()
@Part()作用于方法的参数,用于定义Multipart请求的每和part
使用该注解定义的参数,参数值可以为空,为空时,则忽略。使用该注解定义的参数类型有如下3中方式可选: 1 okhttp2.MulitpartBody.Part,内容将被直接使用。省略part中的名称,即@Part MultipartBody.Part part 2 如果类型是RequestBody,那么该值直接与其内容类型一起使用。在注释中提供part名称(例如,@Part("foo") RequestBody foo) 3 其它对象类型将通过使用转换器转换为适当的格式。在注释中提供part名称(例如,@Part("foo") Image photo)。 @Multipart @POST("/") Callexample( @Part("description") String description, @Part(value = "image", encoding = "8-bit") RequestBody image);
@PartMap()作用于方法的参数
以map的方式定义Multipart请求的每个part map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常。 使用@PartMap 注解定义的参数类型有一下两种: 1 如果类型是RequestBody,那么该值将直接与其内容类型与其使用。 2 其它对象类型将通过使用转换器转换为适当的格式。使用时注意事项
1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在Url上;Field、FieldMap的数据是请求体
3、{占位符}和PATH尽量只用在URL的path部分,url的参数使用Query、QueryMap代替,保证接口的简洁
4、Query、Field、Part支持数据和实现了iterable接口的类型,如List、Set等,方便向后台传递数组,代码如下:
5、以上部分注解真正的实现在ParameterHandler类中,每个注解的真正实现都是ParameterHandler类中的一个final类型的内部类,每个内部类都对各个注解的使用要求做了限制,比如参数是否可空、键和值是否可空等。
6、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
7、@Path 与@Url 注解不能同时使用,否则会抛出parameterError(p, "@Path parameters may not be used with @Url."),可在ServcieMethod类中parseParameterAnnotation()方法中找到不能同时使用的具体代码。其实原因也是很好理解:Path注解用于替换url中的参数,这就要求在使用path注解时,必须已经存在请求路径。不然没法替换路径中指定的参数。而@Url 注解是在参数中指定了请求路径的,这时候情定请求路径已经晚,path注解找不到请求路径,更别提更换请求路径了中的参数了。
8、使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了FormUrlEncoded或Multipart注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)
5.Retrofit与RxJava结合 使Rxjava与retrofit结合条件在Retrofit对象建立的时候添加一句代码addCallAdapterFactory(RxJavaCallAdapterFactory.create())
完整代码 mRetrofit = new Retrofit .Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(client) .build();
可以看到 Observable观察者
public ObservablegetHotMovie(String tag, int start , int count) { Observable book = mApiService.getBook(tag, start, count); return book; }
可以看到订阅者
RxAndroid其实就是对RxJava的扩展。比如上面这个Android主线程在RxJava中就没有,因此要使用的话就必须得引用RxAndroid
DouBookModel model = DouBookModel.getInstance(activity); model.getHotMovie(mType,start,count) .subscribeOn(Schedulers.io()) //请求数据的事件发生在io线程 .observeOn(AndroidSchedulers.mainThread()) //请求完成后在主线程更显UI .subscribe(new Observer6.OkHttpClient() { //订阅 @Override public void onCompleted() { //所有事件都完成,可以做些操作。。 } @Override public void onError(Throwable e) { e.printStackTrace(); //请求过程中发生错误 } @Override public void onNext(DouBookBean bookBean) { //这里的book就是我们请求接口返回的实体类 } });
拦截器说明
addNetworkInterceptor添加的是网络拦截器Network,Interfacetor它会在request和response时分别被调用一次;
addInterceptor添加的是应用拦截器Application Interceptor他只会在response被调用一次。
日志拦截器
一种是使用HttpLoggingInterceptor,需要使用到依赖
compile "com.squareup.okhttp3:logging-interceptor:3.5.0" /** * 创建日志拦截器 * @return */ public static HttpLoggingInterceptor getHttpLoggingInterceptor() { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { Log.e("OkHttp", "log = " + message); } }); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return loggingInterceptor; }
另一种是创建自定义日志拦截器
参考生活助手自定义日志拦截器:https://github.com/yangchong2...
请求头拦截器
/** * 请求头拦截器 * 使用addHeader()不会覆盖之前设置的header,若使用header()则会覆盖之前的header * @return */ public static Interceptor getRequestHeader() { Interceptor headerInterceptor = new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder builder = originalRequest.newBuilder(); builder.addHeader("version", "1"); builder.addHeader("time", System.currentTimeMillis() + ""); Request.Builder requestBuilder = builder.method(originalRequest.method(), originalRequest.body()); Request request = requestBuilder.build(); return chain.proceed(request); } }; return headerInterceptor; } 使用addInterceptor()方法添加到OkHttpClient中 我的理解是,请求头拦截器是为了让服务端能更好的识别该请求,服务器那边通过请求头判断该请求是否为有效请求等...
统一请求拦截器
使用addInterceptor()方法添加到OkHttpClient中,统一请求拦截器的功能跟请求头拦截器相类似
/** * 统一请求拦截器 * 统一的请求参数 */ public static Interceptor commonParamsInterceptor() { Interceptor commonParams = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originRequest = chain.request(); Request request; HttpUrl httpUrl = originRequest.url().newBuilder() .addQueryParameter("paltform", "android") .addQueryParameter("version", "1.0.0") .build(); request = originRequest.newBuilder() .url(httpUrl) .build(); return chain.proceed(request); } }; return commonParams; }
缓存拦截器
使用okhttp缓存的话,先要创建Cache,然后在创建缓存拦截器
OkHttpClient.Builder builder = new OkHttpClient.Builder(); //添加缓存拦截器 //创建Cache File httpCacheDirectory = new File("OkHttpCache"); Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024); builder.cache(cache); //设置缓存 builder.addNetworkInterceptor(InterceptorUtils.getCacheInterceptor()); builder.addInterceptor(InterceptorUtils.getCacheInterceptor());
缓存拦截器, 缓存时间自己根据情况设定
/** * 在无网络的情况下读取缓存,有网络的情况下根据缓存的过期时间重新请求 * @return */ public static Interceptor getCacheInterceptor() { Interceptor commonParams = new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!NetworkUtils.isConnected()) { //无网络下强制使用缓存,无论缓存是否过期,此时该请求实际上不会被发送出去。 request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); } Response response = chain.proceed(request); if (NetworkUtils.isConnected()) { //有网络情况下,根据请求接口的设置,配置缓存。 // 这样在下次请求时,根据缓存决定是否真正发出请求。 String cacheControl = request.cacheControl().toString(); //当然如果你想在有网络的情况下都直接走网络,那么只需要 //将其超时时间这是为0即可:String cacheControl="Cache-Control:public,max-age=0" int maxAge = 60 * 60; // read from cache for 1 minute return response.newBuilder() .header("Cache-Control", cacheControl) .header("Cache-Control", "public, max-age=" + maxAge) .removeHeader("Pragma") .build(); } else { //无网络 int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale return response.newBuilder() .header("Cache-Control", "public,only-if-cached,max-stale=360000") .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale) .removeHeader("Pragma") .build(); } } }; return commonParams; }
自定义CookieJar
/** * 自定义CookieJar * @param builder */ public static void addCookie(OkHttpClient.Builder builder){ builder.cookieJar(new CookieJar() { private final HashMap7.踩坑经验> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List cookies) { cookieStore.put(url, cookies); //保存cookie //也可以使用SP保存 } @Override public List loadForRequest(HttpUrl url) { List cookies = cookieStore.get(url); //取出cookie return cookies != null ? cookies : new ArrayList (); } }); }
url被转义
http://api.mydemo.com/api%2Fnews%2FnewsList? 罪魁祸首@Url与@Path注解,我们开发过程中,肯定会需要动态的修改请求地址 两种动态修改方式如下: @POST() Call8.Form表单提交与multipart/form-data 8.1 form表单常用属性> post(@Url String url, @QueryMap Map map); @POST("api/{url}/newsList") Call > login(@Path("url") String url, @Body News post); 第一种是直接使用@Url,它相当于直接替换了@POST()里面的请求地址 第二种是使用@Path("url"),它只替换了@POST("api/{url}/newsList")中的{url} 如果你用下面这样写的话,就会出现url被转义 @POST("{url}") Call > post(@Path("url") String url); 你如果执意要用@Path,也不是不可以,需要这样写 @POST("{url}") Call > post(@Path(value = "url", encoded = true) String url);
action:url 地址,服务器接收表单数据的地址
method:提交服务器的http方法,一般为post和get
name:最好好吃name属性的唯一性
enctype: 表单数据提交时使用的编码类型,默认使用"pplication/x-www-form-urlencoded",如果是使用POST请求,则请求头中的content-type指定值就是该值。如果表单中有上传文件,编码类型需要使用"multipart/form-data",类型,才能完成传递文件数据。
8.2 浏览器提交表单时,会执行如下步骤识别出表单中表单元素的有效项,作为提交项
构建一个表单数据集
根据form表单中的enctype属性的值作为content-type对数据进行编码
根据form表单中的action属性和method属性向指定的地址发送数据
8.3 提交方式get:表单数据会被encodeURIComponent后以参数的形式:name1=value1&name2=value2 附带在url?后面,再发送给服务器,并在url中显示出来。
post:content-type 默认"application/x-www-form-urlencoded"对表单数据进行编码,数据以键值对在http请求体重发送给服务器;如果enctype 属性为"multipart/form-data",则以消息的形式发送给服务器。
8.4 POST请求HTTP/1.1 协议规定的HTTP请求方法有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 这几种。其中POST一般用于向服务器提交数据。
大家知道,HTTP协议是以ASCII 码传输,建立在TCP/IP协议之上的应用层规范。规范把HTTP请求分为3大块:状态行、请求头、消息体。类似于如下:
协议规定POST提交的数据必须放在消息主题(entity-body)中,但协议并没有规定数据必须使用什么编码方式。实际上,开发者可以自己决定消息体的格式,只要后面发送的HTTP请求满足上面的格式就可以了。
但是,数据发送出去后,还要服务器解析成功才有意义。一般服务器都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的Content-Type字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到POST提交数据方法,包含了Content-Type和消息主题编码方式两部分。
8.5 enctype指定的content-typeapplication/x-www-form-urlencoded
application/json
text/xml
multipart/form-data
9.content-type介绍 9.1 application/x-www-form-urlencoded
这应该是最常见的POST提交数据的方式了。浏览器的原生
案例如下所示
这个例子稍微复杂点。首先生成了一个boundary用于分割不同的字段,为了避免与正文内容重复,boundary很长很复杂。然后Content-Type里指明了数据以multipart/form-data来编码,本次请求的boundary是什么内容。消息主体里按照字段个数又分为多个结构类型的部分,每个部分都以---boundary开始,紧接着是内容描述信息,然后是回车,然后是字段的具体内容(文本和二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以----boundary----标志结束。
header Content-Type: multipart/form-data; boundary={boundary} body 普通 input 数据 --{boundary} Content-Disposition: form-data; name="username" Tom 文件上传 input 数据 --{boundary} Content-Disposition: form-data; name="file"; filename="myfile.txt" Content-Type: text/plain Content-Transfer-Encoding: binary hello word 结束标志 --{boundary}-- 数据示例 POST /upload HTTP/1.1 Host: 172.16.100.128:5000 Content-Length: 394 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLumpDpF3AwbRwRBn Referer: http://172.16.100.128:5000/ ------WebKitFormBoundaryUNZIuug9PIVmZWuw Content-Disposition: form-data; name="username" Tom ------WebKitFormBoundaryUNZIuug9PIVmZWuw Content-Disposition: form-data; name="password" passwd ------WebKitFormBoundaryUNZIuug9PIVmZWuw Content-Disposition: form-data; name="file"; filename="myfile.txt" Content-Type: text/plain hello world ------WebKitFormBoundaryUNZIuug9PIVmZWuw--关于其他
1.技术博客汇总
2.开源项目汇总
3.生活博客汇总
4.喜马拉雅音频汇总
5.其他汇总
我的GitHub:https://github.com/yangchong211
参考博客Retrofit解析2之使用简介:https://www.jianshu.com/p/345...
Android 手把手教你使用Retrofit2:https://www.jianshu.com/p/732...
http://blog.csdn.net/carson_h...
http://www.jianshu.com/p/5bc8...
系列好文:https://www.jianshu.com/p/345...
版本更新说明V1.0.1 更新2017年3月18日
V1.0.2 更新2017年5月21日
V1.0.3 更新2017年10月12日
V2.0.0 更新2018年2月23日
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/14421.html
摘要:源码分析我第一次接触的时候觉得这个东西挺神奇的,用法跟一般的网络请求不一样。下面就来看看的源码是怎么实现的。主要就是通过动态代理的方式把接口中的解析为响应的网络请求,然后交给去执行。 简介 Retrofit 是 Square 推出的 HTTP 框架,主要用于 Android 和 Java。Retrofit 将网络请求变成方法的调用,使用起来非常简洁方便。本文先简要介绍一下 Retrof...
摘要:关于三请求流程,源码必知掘金前两两片我们介绍了,基本使用和如何查看源码,今天我们正式进入源码分析流程。掘金是一款上的组件。 一篇文章带你走通 OkHttp+Retrofit+Rxjava - Android - 掘金一篇文章带你走通 OkHttp+Retrofit+Rxjava @(Android)[android] ... 升级 Https 的那些事 - Android - 掘金一、...
摘要:关于三请求流程,源码必知掘金前两两片我们介绍了,基本使用和如何查看源码,今天我们正式进入源码分析流程。掘金是一款上的组件。 一篇文章带你走通 OkHttp+Retrofit+Rxjava - Android - 掘金一篇文章带你走通 OkHttp+Retrofit+Rxjava @(Android)[android] ... 升级 Https 的那些事 - Android - 掘金一、...
摘要:协程的判断条件下面我们来着重看下的源码,因为从这里开始就涉及到协程的判断。第二点是关键点,用来判断该方法的调用是否使用到了协程。原理我们先来看下使用协程是怎么写的这是一个标准的协程写法,然后我们再套用上面的条件,发现完全匹配不到。 第一眼看,跟我之前印象中的有点区别(也不知道是什么版本),return的时候居然...
摘要:无网读缓存,有网根据过期时间重新请求使用和实现网络缓存。浅析的你了解过吗为什么每次请求都用了长连接完成一次网络请求都经历了什么感兴趣的不妨可以看下。 Android OKHttp3.0 以上使用方法 Android OKHttp3.0 以上使用方法详解 Retrofit 之日志拦截 Retrofit 日志拦截相关介绍 Retrofit源码解析 Retrofit的源码分析将从基本的使用方...
阅读 906·2021-10-11 10:58
阅读 3619·2021-08-11 11:16
阅读 1470·2019-08-30 15:44
阅读 1897·2019-08-29 18:45
阅读 2115·2019-08-26 18:18
阅读 809·2019-08-26 13:37
阅读 1443·2019-08-26 11:43
阅读 1975·2019-08-26 11:34