1.最近在学习 rxjava2+rxandroid2+retrofit2 封装网络请求 ,学了好久了,一头的包,今天就把之前学习的整理下。
注意:1.rxjava,rxandroid 更新到2.0以上,用法都不一样。
2.retrofit2 ,会自动去拉去okhttp3,所以不需要我们去添加依赖
Retrofit 是什么,官方文档解释说明,是一个封装好的网络请求客户端,也就是类似与我们安卓装的DefaultHttpClient,只不过Retrofit ,更强大。
Retrofit 的使用:
如何进行网络请求,首先我们要建一个AppService,接口类,主要是用来声明,进行网络请求要
带入的参数,比如公共参数,上传文件,自定义参数,比如登录(用户名,用户密码)
public interface AppService {
@GET("api/")
Call> getCarCount(
);
@POST("api?")
Call getCarCount(
@QueryMap Map 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 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:
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 getCarCount(
@QueryMap Map<String, String> options
);
我们在请求网络请求的时候肯定会自定义传入参数,比如 登录,需要加入,用户名,用户密码参数,如何添加。
先来说下GET 请求传参数的方法:
1.不带参数
@GET("News")
Call getItem() ;
相当于:http://102.10.10.132/api/News
2.带入url修改某一个节点
@Path(”要修改那个一个节点名字”) ,String newsId ; 声明类型,传入实参替换
@GET("News/{newsId}")
Call 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 getItem(@Query("newsId") String newsId) ;
相当于:
http://102.10.10.132/api/News?newsId=1
4. URL 带入多个参数
@GET("News")
Call 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 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 onUpdateVersion(
@Field("name") String name
) ;
相当于:
http://102.10.10.132/api/Comments
post请求提交多个表单字段
@FieldMap 用于支持post请求,提交多个表单字段
@FormUrlEncoded
@POST("Comments")
Observable onUpdateVersion(
@FieldMap Map 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>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(BaseResultEntity 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> getCarCount(
@FieldMap Map options
);
@FormUrlEncoded
@POST("api")
Observable> onUpdateVersion(
@FieldMap Map options
);
}
可以看到GetCarCountInput 继承了BaseInput 重写了这个方法,getData()
public class GetCarCountInput extends BaseInput {
@Override
public Map getData() {
Gson gson = new Gson();
Type type = new TypeToken
这段代码的作用就是将这个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 getData() {
Gson gson = new Gson();
Type type = new TypeToken
使用:
UpdateVersionInput countInput=new UpdateVersionInput();
countInput.setMethod("mobileapi.app.version");
countInput.setOs("android");
ApiMnager.getInstance().onUpdateVersion(countInput).subscribe(
new Observer >() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(BaseResultEntity 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 getProperties() {
HashMap map = new HashMap();
//调用时很可能是子类对象,必须使用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 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> 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> getCarCount(GetCarCountInput input) {
return toObservable(appService.getCarCount(doSign(input)));
}
public Observable> 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 postBody;
private String bodySign;
protected Observable toObservable(Observable 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 needSignMap = new TreeMap<>();
//解析参数,放入TreeMap排序
BaseInput input = (BaseInput) object;
HashMap 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 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 createService(final Class 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 {
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;
}
}