3.注解的种类java
4.Retrofit相关请求参数android
5.Retrofit与RxJava结合git
6.OkHttpClientgithub
7.踩坑经验chrome
8.Form表单提交与multipart/form-datajson
9.content-type介绍设计模式
10.Retrofit源码深刻分析api
N.关于其余数组
RxJava + Retrofit + okHttp组合,流行的网络请求框架浏览器
为何要使用Retrofit?
优势
其余说明
在处理HTTP请求的时候,由于不一样场景或者边界状况等比较难处理。你须要考虑网络状态,须要在请求失败后重试,须要处理HTTPS等问题,二这些事情让你很苦恼,而Retrofit能够将你从这些头疼的事情中解放出来。
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") Observable<DouBookBean> getBook(@Query("tag") String tag, @Query("start") int start, @Query("count") int count); }
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 Observable<DouBookBean> getHotMovie(String tag, int start , int count) { Observable<DouBookBean> 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; } public <T> T create(final Class<T> service) { return mRetrofit.create(service); } }
DouBookModel model = DouBookModel.getInstance(activity); model.getHotMovie(mType,start,count) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<DouBookBean>() { @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 Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);
参数注解:@Query 、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap
@Path、@Url
@Query:做用于方法参数,用于添加查询参数,即请求参数 用于在url后拼接上参数,例如: @GET("book/search") Call<Book> getSearchBook(@Query("q") String name);//name由调用者传入 至关于 @GET("book/search?q=name") Call<Book> getSearchBook(); 用于Get中指定参数
@QueryMap:做用于方法的参数。以map的形式添加查询参数,即请求参数,参数的键和值都经过String.valueOf()转换为String格式。默认map的值进行URL编码,map中的每一项发键和值都不能为空,不然跑出IllegalArgumentException异常。 固然若是入参比较多,就能够把它们都放在Map中,例如: @GET("book/search") Call<Book> getSearchBook(@QueryMap Map<String, String> options);
/** * http://api.zhuishushenqi.com/ranking/582ed5fc93b7e855163e707d * @return */ @GET("/ranking/{rankingId}") Observable<SubHomeTopBean> getRanking(@Path("rankingId") String rankingId); @GET("group/{id}/users") Call<Book> groupList(@Path("id") int groupId); * 像这种请求接口,在group和user之间有个不肯定的id值须要传入,就能够这种方法。咱们把待定的值字段用{}括起来,固然 {}里的名字不必定就是id,能够任取,但需和@Path后括号里的名字同样。若是在user后面还须要传入参数的话,就能够用Query拼接上,好比: @GET("group/{id}/users") Call<Book> groupList(@Path("id") int groupId, @Query("sort") String sort); * 当咱们调用这个方法时,假设咱们groupId传入1,sort传入“2”,那么它拼接成的url就是group/1/users?sort=2,固然最后请求的话还会加上前面的baseUrl
使用@Body 注解定义的参数不能为null 。当你发送一个post或put请求,可是又不想做为请求参数或表单的方式发送请求时,使用该注解定义的参数能够直接传入一个实体类,retrofit会经过convert把该实体序列化并将序列化的结果直接做为请求体发送出去。 能够指定一个对象做为HTTP请求体,好比: @POST("users/new") Call<User> createUser(@Body User user); 它会把咱们传入的User实体类转换为用于传输的HTTP请求体,进行网络请求。 多用于post请求发送非表单数据,好比想要以post方式传递json格式数据
用于传送表单数据: @FormUrlEncoded @POST("user/edit") Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last); 注意开头必须多加上@FormUrlEncoded这句注释,否则会报错。表单天然是有多组键值对组成,这里的first_name就是键,而具体传入的first就是值啦 多用于post请求中表单字段,Filed和FieldMap须要FormUrlEncoded结合使用
@FormUrlEncoded @POST("user/login") Call<User> login(@FieldMap Map<String,String> map);
用于动态添加请求头部: @GET("user") Call<User> getUser(@Header("Authorization") String authorization) 表示将头部Authorization属性设置为你传入的authorization;固然你还能够用@Headers表示,做用是同样的好比: @Headers("Cache-Control: max-age=640000") @GET("user") Call<User> getUser() 固然你能够多个设置: @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" }) @GET("user") Call<User> getUser()
使用该注解定义的参数,参数值能够为空,为空时,则忽略。使用该注解定义的参数类型有以下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("/") Call<ResponseBody> example( @Part("description") String description, @Part(value = "image", encoding = "8-bit") RequestBody image);
以map的方式定义Multipart请求的每一个part map中每一项的键和值都不能为空,不然抛出IllegalArgumentException异常。 使用@PartMap 注解定义的参数类型有一下两种: 1 若是类型是RequestBody,那么该值将直接与其内容类型与其使用。 2 其它对象类型将经过使用转换器转换为适当的格式。
完整代码 mRetrofit = new Retrofit .Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(client) .build();
public Observable<DouBookBean> getHotMovie(String tag, int start , int count) { Observable<DouBookBean> book = mApiService.getBook(tag, start, count); return book; }
能够看到订阅者
DouBookModel model = DouBookModel.getInstance(activity); model.getHotMovie(mType,start,count) .subscribeOn(Schedulers.io()) //请求数据的事件发生在io线程 .observeOn(AndroidSchedulers.mainThread()) //请求完成后在主线程更显UI .subscribe(new Observer<DouBookBean>() { //订阅 @Override public void onCompleted() { //全部事件都完成,能够作些操做。。 } @Override public void onError(Throwable e) { e.printStackTrace(); //请求过程当中发生错误 } @Override public void onNext(DouBookBean bookBean) { //这里的book就是咱们请求接口返回的实体类 } });
拦截器说明
日志拦截器
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; }
另外一种是建立自定义日志拦截器
/** * 请求头拦截器 * 使用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中 个人理解是,请求头拦截器是为了让服务端能更好的识别该请求,服务器那边经过请求头判断该请求是否为有效请求等...
统一请求拦截器
/** * 统一请求拦截器 * 统一的请求参数 */ 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; }
缓存拦截器
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 * @param builder */ public static void addCookie(OkHttpClient.Builder builder){ builder.cookieJar(new CookieJar() { private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { cookieStore.put(url, cookies); //保存cookie //也可使用SP保存 } @Override public List<Cookie> loadForRequest(HttpUrl url) { List<Cookie> cookies = cookieStore.get(url); //取出cookie return cookies != null ? cookies : new ArrayList<Cookie>(); } }); }
http://api.mydemo.com/api%2Fnews%2FnewsList? 罪魁祸首@Url与@Path注解,咱们开发过程当中,确定会须要动态的修改请求地址 两种动态修改方式以下: @POST() Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map); @POST("api/{url}/newsList") Call<HttpResult<News>> login(@Path("url") String url, @Body News post); 第一种是直接使用@Url,它至关于直接替换了@POST()里面的请求地址 第二种是使用@Path("url"),它只替换了@POST("api/{url}/newsList")中的{url} 若是你用下面这样写的话,就会出现url被转义 @POST("{url}") Call<HttpResult<News>> post(@Path("url") String url); 你若是执意要用@Path,也不是不能够,须要这样写 @POST("{url}") Call<HttpResult<News>> post(@Path(value = "url", encoded = true) String url);
<method> <request-URL> <version> <headers> <entity-body>
这应该是最多见的POST提交数据的方式了。浏览器的原生<form>表单,若是不设置enctype属性,那么最终会以application/x-www-form-urlencoded方法提交数据。请求相似于以下内容(省略了部分无关的内容):
POST http://www.hao123.com/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded;charset=utf-8 title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
POST http://www.hao123.com/ HTTP/1.1 Content-Type: application/json;charset=utf-8 {"title":"test","sub":[1,2,3]}
它是一种使用HTTP做为传输协议,XML做为编码方式的远程调用规范。典型的XML-RPC是这样的:
POST http://www.example.com HTTP/1.1 Content-Type: text/xml <?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>
<form action="/upload" enctype="multipart/form-data" method="post"> Username: <input type="text" name="username"> Password: <input type="password" name="password"> File: <input type="file" name="file"> <input type="submit"> </form>
案例以下所示
header Content-Type: multipart/form-data; boundary={boundary}\r\n body 普通 input 数据 --{boundary}\r\n Content-Disposition: form-data; name="username"\r\n \r\n Tom\r\n 文件上传 input 数据 --{boundary}\r\n Content-Disposition: form-data; name="file"; filename="myfile.txt"\r\n Content-Type: text/plain\r\n Content-Transfer-Encoding: binary\r\n \r\n hello word\r\n 结束标志 --{boundary}--\r\n 数据示例 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--