当前位置: 代码迷 >> 综合 >> rxjava2+rxandroid2+retrofit2 封装网络请求
  详细解决方案

rxjava2+rxandroid2+retrofit2 封装网络请求

热度:68   发布时间:2024-01-18 10:52:17.0

1.最近在学习 rxjava2+rxandroid2+retrofit2 封装网络请求 ,学了好久了,一头的包,今天就把之前学习的整理下。

注意:1.rxjava,rxandroid 更新到2.0以上,用法都不一样。
2.retrofit2 ,会自动去拉去okhttp3,所以不需要我们去添加依赖


Retrofit 是什么,官方文档解释说明,是一个封装好的网络请求客户端,也就是类似与我们安卓装的DefaultHttpClient,只不过Retrofit ,更强大。


Retrofit 的使用:
如何进行网络请求,首先我们要建一个AppService,接口类,主要是用来声明,进行网络请求要
带入的参数,比如公共参数,上传文件,自定义参数,比如登录(用户名,用户密码)

public interface AppService {
    @GET("api/")Call<BaseResultEntity<GetCarCount>> getCarCount();@POST("api?")Call<BaseResultEntity> getCarCount(@QueryMap Map<String, String> options);
}

我们可以看到上面用到了注解,@POST,@GET,Retrofit 支持多种注解方法,我们慢慢道来

  • @POST
    post请求 ,我们公司用的是post请求

  • @GET
    get请求

  • @PUT
    上传文件专用

  • @DELETE
    删除文件专用

  • @Header/@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()

一般情况,也没有必要去需要请求头部参数,所不会改变

如果真想自定义请求头部,也没有必要在这里写,要不然每次都要注解一下,麻烦很。
我们可以自定义一个拦截器,加入请求当中。

   OkHttpClient.Builder builder = new OkHttpClient.Builder();builder.connectTimeout(DEFAULT_OUT_TIME, TimeUnit.SECONDS); //手动创建一个OkHttpClient并设置超时时间builder.addInterceptor(new HeadersInterceptor());

HeadersInterceptor类

package com.dk.basepack.bseaapplication.network;import java.io.IOException;import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;/*** Created by Administrator on 2017/10/12.*/public class HeadersInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {Request original = chain.request();Request request = original.newBuilder().header("User-Agent", "ttdevs").header("Content-Type", "application/json; charset=utf-8").header("Accept", "application/json").header("token", "abcdefg_ttdevs_hijklmn").header("user_key", "ttdevs").method(original.method(), original.body()).build();long t1 = System.nanoTime();String requestHeader = String.format(">>>>>Sending request %s on %s%n%s",request.url(), chain.connection(), request.headers());System.out.println(requestHeader);Response response = chain.proceed(request);long t2 = System.nanoTime();System.out.println(String.format(">>>>>Received response for %s in %.1fms%n%s",response.request().url(), (t2 - t1) / 1e6d, response.headers()));System.out.println("=====================================================");return response;}
}

现在来讲解,注解@POST ,@GET 后面括号放得是什么内容:
我们在进行网络请求的时候,实际上跟浏览器请求一样,
URL:
http://write.blog.csdn.net/index.php/api&cat_id=68&date=2017-10-20 16:49:33&direct=true&method=mobileapi.goods.get_all_list&page_no=1&son_object=json&task=5a71964ed1884d9da93c1ffd560bc7af&sign=B2D328C19E486B95D9BCDAA01D4F0295

http://write.blog.csdn.net/index.php/ 也是网站的根节点

api? 是根节点下的某一个分支,例,webapi,所以是多变的分支,设置 @POST(“api?”),相当于

http://write.blog.csdn.net/index.php/api

在下面例子中我们可以看到,我放的”api?”,

   @POST("api?")Call<BaseResultEntity> getCarCount(@QueryMap Map<String, String> options);

我们在请求网络请求的时候肯定会自定义传入参数,比如 登录,需要加入,用户名,用户密码参数,如何添加。
先来说下GET 请求传参数的方法:

1.不带参数

  @GET("News")Call<NewsBean> getItem();

相当于:http://102.10.10.132/api/News

2.带入url修改某一个节点
@Path(”要修改那个一个节点名字”) ,String newsId ; 声明类型,传入实参替换

 @GET("News/{newsId}")Call<NewsBean> getItem(@Path("newsId") String newsId);

相当于:http://102.10.10.132/api/News/1
http://102.10.10.132/api/News/{资讯id}

3.URL带入参数

@GET("News")Call<NewsBean> getItem(@Query("newsId") String newsId);

相当于:
http://102.10.10.132/api/News?newsId=1

4. URL 带入多个参数

  @GET("News")Call<NewsBean> getItem(@QueryMap Map<String, String> map);

相当于:
http://102.10.10.132/api/News?newsId={资讯id}&type={类型}…

@QueryMap,@Query 最好只在get请求中用(为什么稍后讲解),@Path可以在post,get请求使用

post 注解使用讲解:
@Path 在post请求中使用:

@FormUrlEncoded@POST("Comments/{newsId}")Call<Comment> reportComment(@Path("newsId") String commentId,@Field("reason") String reason);

相当于:
http://102.10.10.132/api/Comments/1
http://102.10.10.132/api/Comments/{newsId}

post请求 单个参数使用
post请求是看不到参数的,@Field 用于提交单个字段表单,一定要加 @FormUrlEncoded

  @FormUrlEncoded@POST("Comments")Observable<BaseResultEntity> onUpdateVersion(@Field("name") String name);

相当于:
http://102.10.10.132/api/Comments

post请求提交多个表单字段
@FieldMap 用于支持post请求,提交多个表单字段

 @FormUrlEncoded@POST("Comments")Observable<BaseResultEntity> onUpdateVersion(@FieldMap Map<String, String> options);

相当于:
http://102.10.10.132/api/Comments

post请求 提交一个实体参数
@Body 会将对象转换成json上传到服务器

@POST("Comments")Call<Comment> reportComment(@Body CommentBean bean);

相当于:
http://102.10.10.132/api/Comments


可以看到 post请求 ,中 @Body ,@FieldMap,@Field,看不到参数,如果你post请求中用了get请求中的注解,@QueryMap,@Query ,就会直接加入到URL上显示出来,access_token=1234123直接加入请求的后面。如果你想post请求肯定是不想别人看到你的参数,所以建议 post请求,用post请求注解。

@POST("Comments/{newsId}")Call<Comment> reportComment(@Path("newsId") String commentId,@Query("access_token") String access_token,@Body CommentBean bean);

相当于:
http://102.10.10.132/api/Comments/1?access_token=1234123
http://102.10.10.132/api/Comments/{newsId}?access_token={access_token}


rxjava2+rxandroid2+retrofit2 封装网络请求 使用,以post请求为例,进行封装

使用:

  GetCarCountInput countInput=new GetCarCountInput();countInput.setMethod("mobileapi.cart.get_list_group_by_tip");//API方法ApiMnager.getInstance().getCarCount(countInput).subscribe(new Observer<BaseResultEntity<GetCarCount>>() {@Overridepublic void onSubscribe(Disposable d) {}@Overridepublic void onNext(BaseResultEntity<GetCarCount> aseResultEntity) {Log.i("GetCarCountTask","onNext");}@Overridepublic void onError(Throwable e) {Log.i("GetCarCountTask","onError"+e.getMessage());}@Overridepublic void onComplete() {}});

GetCarCountInput 是用来设置自定义参数,还有共有参数,在正常的的项目中,网络请求是有共有参数的,options 这个map集合就是用来设置共有参数,还有装有自定义参数,如何简单的,传入参数呢

public interface AppService {
    @FormUrlEncoded@POST("api")Observable<BaseResultEntity<GetCarCount>> getCarCount(@FieldMap Map<String, String> options);@FormUrlEncoded@POST("api")Observable<BaseResultEntity<UpdateVersion>> onUpdateVersion(@FieldMap Map<String, String> options);}

可以看到GetCarCountInput 继承了BaseInput 重写了这个方法,getData()

public class GetCarCountInput extends BaseInput {
    @Overridepublic Map<String, String> getData() {Gson gson = new Gson();Type type = new TypeToken<Map<String, String>>() {}.getType();return gson.fromJson(gson.toJson(this), type);}
}

这段代码的作用就是将这个GetCarCountInput 的属性转换为Map

      Gson gson = new Gson();Type type = new TypeToken<Map<String, String>>() {}.getType();return gson.fromJson(gson.toJson(this), type);

可以像这样重写

package com.dk.basepack.bseaapplication.input;import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;import java.lang.reflect.Type;
import java.util.Map;/*** Created by Administrator on 2017/10/19.*/public class UpdateVersionInput extends BaseInput {
    @Overridepublic Map<String, String> getData() {Gson gson = new Gson();Type type = new TypeToken<Map<String, String>>() {}.getType();return gson.fromJson(gson.toJson(this), type);}private  String  os;public void setOs(String os) {this.os = os;}public String getOs() {return os;}
}

使用:

UpdateVersionInput countInput=new UpdateVersionInput();countInput.setMethod("mobileapi.app.version");countInput.setOs("android");ApiMnager.getInstance().onUpdateVersion(countInput).subscribe(new Observer<BaseResultEntity<UpdateVersion> >() {@Overridepublic void onSubscribe(Disposable d) {}@Overridepublic void onNext(BaseResultEntity<UpdateVersion> baseResultEntity) {}@Overridepublic void onError(Throwable e) {Log.i("GetCarCountTask","onError"+e.getMessage());}@Overridepublic void onComplete() {}});

共有参数在BaseInput 已经初始化完毕,可以看到,我这里有四个共有参数

   private String date;//传入时间private String direct;private String method;//方法private  String task;
package com.dk.basepack.bseaapplication.input;import android.text.TextUtils;import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;/*** Created by Administrator on 2017/10/19.*/public abstract class BaseInput {
    private String date;private String direct;private String method;private  String task;private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");public String getDate() {return df.format(System.currentTimeMillis());}public void setDate(String date) {this.date = date;}public String getDirect() {return "true";}public void setDirect(String direct) {this.direct = direct;}public String getMethod() {return method;}public String getTask() {return getRandomString();}public void setTask(String task) {this.task = task;}public void setMethod(String method) {this.method = method;}public HashMap<String, String> getProperties() {HashMap<String, String> map = new HashMap<String, String>();//调用时很可能是子类对象,必须使用getMethod,而不是getDeclaredMethod来获取方法。map.put("date",getDate());map.put("direct", getDirect());map.put("method", getMethod());map.put("task", getTask());return map;}public static String getRandomString() {UUID uuid = UUID.randomUUID();return uuid.toString().replace("-", "");}public abstract Map<String, String> getData();/*** 类型转换输出,String 转 int 类型 输出* @param inputStr* @return*/public  static int  onTypeOutputStringToInt(String inputStr){if (TextUtils.isEmpty(inputStr)){inputStr="0";}return    Integer.parseInt(inputStr);}/*** 类型转换输出 String 类型转 布尔值类型输出* @param inputStr* @return*/public static boolean  onTypeOutputStringToBoolean(String inputStr){if (TextUtils.isEmpty(inputStr)){inputStr="false";}return    Boolean.parseBoolean(inputStr);}}

通过上面的写法就可以将自定义参数和共有参数(每次网络请求必传的参数),通过map集合装起来


封装一个ApiMnager类,来管理自己网络请求,doSign()签名使用的

  public Observable<BaseResultEntity<GetCarCount>> getCarCount(GetCarCountInput input) {return toObservable(appService.getCarCount(doSign(input)));}
package com.dk.basepack.bseaapplication.network;import com.dk.basepack.bseaapplication.input.GetCarCountInput;
import com.dk.basepack.bseaapplication.input.UpdateVersionInput;
import com.dk.basepack.bseaapplication.resultbean.GetCarCount;
import com.dk.basepack.bseaapplication.resultbean.UpdateVersion;import io.reactivex.Observable;/*** Created by Administrator on 2017/10/11.*/public class ApiMnager<T> extends BaseApiMnager{
    private  volatile static ApiMnager mnager=null;private final AppService appService;private ApiMnager() {appService = ApiCore.init().createService(AppService.class);}public  static   ApiMnager getInstance(){if (mnager == null) {synchronized (ApiMnager.class) {if (mnager == null) {mnager = new ApiMnager();}}}return mnager;}public Observable<BaseResultEntity<GetCarCount>> getCarCount(GetCarCountInput input) {return toObservable(appService.getCarCount(doSign(input)));}public Observable<BaseResultEntity<UpdateVersion>>  onUpdateVersion(UpdateVersionInput input) {return toObservable(appService.onUpdateVersion(doSign(input)));}
}

package com.dk.basepack.bseaapplication.network;import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;import com.dk.basepack.bseaapplication.input.BaseInput;
import com.dk.basepack.bseaapplication.resultbean.LogoutLogin;
import com.dk.basepack.bseaapplication.util.RxBus;
import com.google.gson.Gson;
import com.socks.library.KLog;import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;/*** Created by Administrator on 2017/10/11.*/public class BaseApiMnager {
    private  String   serviceToken=ApiCore.TOKEN;private Map<String, String> postBody;private String bodySign;protected <T> Observable<T> toObservable(Observable<T> o) {returno.subscribeOn(Schedulers.io())//网络请求在子线程,所以是在io线程,避免阻塞线程.unsubscribeOn(Schedulers.io())//取消请求的的时候在 io 线程,避免阻塞线程.observeOn(AndroidSchedulers.mainThread());}/*** 进行网络请求参数签名* @param object* @return*/public String signBody(Object object) {if(object instanceof BaseInput){TreeMap<String, String> needSignMap = new TreeMap<>();//解析参数,放入TreeMap排序BaseInput input = (BaseInput) object;HashMap<String, String> map = input.getProperties();for (String key :map.keySet()){String value = map.get(key);needSignMap.put(key, value);}needSignMap.putAll(input.getData());postBody=null;postBody = needSignMap;//组合待签名字符串bodySign = "";for (String key :postBody.keySet()){bodySign +=key;bodySign += postBody.get(key);}//签名bodySign = Md5.getMD5(bodySign).toUpperCase();bodySign += serviceToken;bodySign = Md5.getMD5(bodySign).toUpperCase();postBody.put("sign", bodySign);outputNetworkRequestUrl();return bodySign;}return null;}/*** 输出网络请求的URL*/public  void outputNetworkRequestUrl(){String  url="";int  i=0;for (String key :postBody.keySet()){i++;url+=key+"="+ postBody.get(key)+(i==postBody.size()?"":"&");}KLog.i("Network_Request_url==>",ApiCore.BASE_URL+"api"+"?"+url);}/*** 签名异常抛出异常* @param input*/protected    Map<String, String>   doSign(BaseInput input){String  signBody=   signBody(input);if (signBody==null){return null;}else {return  postBody;}}
}
package com.dk.basepack.bseaapplication.network;import android.util.Log;import com.dk.basepack.bseaapplication.BuildConfig;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;import java.io.IOException;
import java.util.concurrent.TimeUnit;import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;/*** Created by Administrator on 2017/10/11.*/public class ApiCore {
    private final static Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").serializeNulls().create();private static final int DEFAULT_OUT_TIME = 30;public static  final String  BASE_URL="你自己的网络请求地址";public static  final String  TOKEN="服务器的唯一标识";private final Retrofit mRetrofit;public   static ApiCore init(){return  new ApiCore();}private ApiCore() {OkHttpClient.Builder builder = new OkHttpClient.Builder();builder.connectTimeout(DEFAULT_OUT_TIME, TimeUnit.SECONDS); //手动创建一个OkHttpClient并设置超时时间// builder.addInterceptor(new HeadersInterceptor());builder.addInterceptor(new ResponseInterceptor());//添加结果拦截器if (BuildConfig.DEBUG)//debug 情况下输出日志{builder.interceptors().add(new LoggingInterceptor());}//RxJava2mRetrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(builder.build()).addConverterFactory(GsonConverterFactory.create(gson)).addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava2.build();}public <T> T createService(final Class<T> clz) {return mRetrofit.create(clz);}
}

请求结果返回截器

package com.dk.basepack.bseaapplication.network;import android.text.TextUtils;import com.dk.basepack.bseaapplication.resultbean.LogoutLogin;
import com.dk.basepack.bseaapplication.util.RxBus;
import com.socks.library.KLog;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;/*** 请求结果拦截器** 作用:* 1.用来拦截网络请求结果,打印出来,然后处理不规范的请求结果* 2.退出登录发送信号拦截*/public class ResponseInterceptor implements Interceptor {
    private String emptyString = ":\"\"";private String emptyObject = ":{}";private String emptyArray = ":[]";private String newChars = ":null";@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Response response = chain.proceed(request);ResponseBody responseBody = response.body();if (responseBody != null) {String json = responseBody.string();KLog.i("responseJson",json);MediaType contentType = responseBody.contentType();if (!json.contains(emptyString)) {json=   handleJosnData(json);ResponseBody body = ResponseBody.create(contentType, json);return response.newBuilder().body(body).build();} else {json=   handleJosnData(json);String replace = json.replace(emptyString, newChars);String replace1 = replace.replace(emptyObject, newChars);String replace2 = replace1.replace(emptyArray, newChars);ResponseBody body = ResponseBody.create(contentType, replace2);return response.newBuilder().body(body).build();}}return response;}/*** json 里面的data 不规范,返回的 下面这个格式,data 对应的是string ,真是搞死人,在这里做特殊处理下* {"rsp": "fail","res": "need_login","data": "请重新登录","timestamp": 1508813312}* @param json*/private   String   handleJosnData(String json){JSONObject  jsonO=null;if(!TextUtils.isEmpty(json)){try {jsonO=new JSONObject(json);} catch (JSONException e) {e.printStackTrace();}String  rsp=     jsonO.optString("rsp");String  res = jsonO.optString("res");String  data = jsonO.optString("data");if ("fail".equals(rsp)&&"need_login".equals(res))//退出登录处理{RxBus.getInstance().post(new LogoutLogin());}if (!TextUtils.isEmpty(data)&&!data.contains("{")&&!data.contains("}"))//如果不是用{} 包裹起来说明返回的就是一个string{jsonO.remove("data");try {JSONObject jsondata=new JSONObject();jsondata.put("msg",data);jsonO.put("data",jsondata);} catch (JSONException e) {e.printStackTrace();}}KLog.i("handleJosnData",jsonO.toString());}return jsonO.toString();}
}

服务器返回的json 标准的示例,T ,是泛型,

package com.dk.basepack.bseaapplication.network;/*** Created by Administrator on 2017/10/12.*/public class BaseResultEntity<T>  {private String rsp;private T data;private String res;private String timestamp;public String getRsp() {return rsp;}public void setRsp(String rsp) {this.rsp = rsp;}public T getData() {return data;}public void setData(T data) {this.data = data;}public String getRes() {return res;}public void setRes(String res) {this.res = res;}public String getTimestamp() {return timestamp;}public void setTimestamp(String timestamp) {this.timestamp = timestamp;}
}