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 {
    @Override
    public 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:
16:49:33&direct=true&method=mobileapi.goods.get_all_list&page_no=1&son_object=json&task=5a71964ed1884d9da93c1ffd560bc7af&sign=B2D328C19E486B95D9BCDAA01D4F0295

也是网站的根节点

api? 是根节点下的某一个分支,例,webapi,所以是多变的分支,设置 @POST(“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>>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(BaseResultEntity<GetCarCount> aseResultEntity) {
                    Log.i("GetCarCountTask","onNext");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("GetCarCountTask","onError"+e.getMessage());
                    }

                    @Override
                    public 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 {
    @Override
    public 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 {
    @Override
    public 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> >() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(BaseResultEntity<UpdateVersion> baseResultEntity) {


                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("GetCarCountTask","onError"+e.getMessage());
                    }

                    @Override
                    public 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) {
        return
                o.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());
        }

        //RxJava2
        mRetrofit = 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";

    @Override
    public 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;
    }
}