一个APP,它所涉及到的内容不只是APP表面这些界面、元素的beautiful和开发。APP之所以为APP它不同于VUE JS、微信小程序,一个APP它内部也分成多个层次,有纯前端、有Adapter、有模型层、有API层、有底层、有缓存、有内嵌存储。
一个APP其实是一个完整的企业级架构体系,因此APP上才需要架构师,因此APP在业界并没有归到“纯前端”,而叫“大前端”,它是自身独立的一整套体系化知识所组成。
特别是近几年来,随着智能手机在市场上进一步普及后APP已经不仅仅只是人们以前认知上一个简单的“前端”了。它也已经开始区分出:前端、中间件、缓存、后端、数据库、网络交互、设备交互、动画这些几个主要大科。在这些大科下再衍生出上百个门类的知识点。
特别是设备交互和网络交互,HTTP/RESULTFUL API交互其实也属于底层。所以作为大前端,架构上非单一层式传统设计时我们经常会进行思考:即我们往往也希望可以像高级语言一样把一些底层features“掩盖”住以加速程序员开发效率。
因此,今天我们就要讲时下最流行的retrofit2+okhttp3+rxjava封装的http/res㔞 api调用原理。这3个东西组合比较复杂,但是我相信跟着我的教程一步步来绝对保证你一次成功。
为什么要从“简”变“难”、择“难”去“易”呢?
当然,这是“码农”的眼光去看待原来一个okhttp3可以搞定的事的角度去思考这件事所能看到的局限。而一旦让我们从全局来看原来所谓“一个okhttp3”可以搞定的事到底是个什么样的情况呢?
假设,我们有一个get请求,返回是一个{"code":0, "data":"4e23h100hm5ffg8l"}这样的一restful api,我们用okhttp3是怎么实现的呢?
先要定义http请求头;
设置一堆的请求变量;
声明okhttp builder为同步还是异步;
覆盖on success, on fail等方法;
判断http返回值是否200;
如果是200,取body;
body取到后转成gson;
然后。。。
然后。。。
还有异步线程handler回调;
刷新界面;
每一个不同的API请求都是上面这一陀。。。对不?然后有人说了。。。你封装呀,把一些重复步骤封装呀。。。哈哈哈。
说到点了,retrofit+okhttp3+rxjava就是这个封装,当然它不可能封装到开发、程序员们只需要一句话就能实现上面所有步骤,这是不现实的。
但是retrofit+okhttp3+rxjava可以把以上一堆步骤中的重复书写代码进行了最大程度的封装。。。so。。。已经有“大牛”给你封装了”,你自己也就不用去造轮子了。这就是我在另一个架构系列-高性能零售架构系列里提到的:当团队超过几十人且属于规模化基于一个产品进行协同作战模式下,根本不需要去做什么封装。因为你是对企业的稳定性、性能负责而不是快点交了可以收合同尾款这种乙方性质作法时,你会诞生一种这样的想法:希望这个东西应封装尽封装,不该封装的就不要封,在解决问题、共享信息、消除黑盒、解放生产力、质量、性能上达到最大的balance。这是一种“企业级架构思想”。
retrofit+okhttp3+rxjava正是这么一种存在,这也是它存在的意义所在即:给予你充分自由又可最大限度消除一些重复代码,并且还掩盖了底层协议部分内容充分解放了程序员的生产力。
看看似乎有三样东西一起做,其实使用上你是无感的。
笔者基于这3个组合上,做了少许的小封装,使得它几乎可以适用于任何的开发团队,同时在笔者自己的mao-sir.com上也是使用得是这一套东西,框架代码完全公开给大家。
下面就让我们一起进入retrofit+okhttp3+rxjava的框架搭建教程。
以上就是mao-sir的retrofit2+okhttp3+rxjava的框架的结构。
任何一个业务模块里只要写一个xxx.xxx.api.业务名API就可以使用restful api的访问了。
看看挺多,其实一点没什么难度,我们一块块把它们“掰开揉碎”了一点点来消化即可。
在模块的build.gradle里引入下面这些包
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//日志拦截器
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
//rxjava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
//gson
implementation 'com.google.code.gson:gson:2.8.7'
引入后它长这样
package com.mkyuan.aset.mall.android.network.errorhandler;
import com.google.gson.JsonParseException;
import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONException;
import java.net.ConnectException;
import java.text.ParseException;
import retrofit2.HttpException;
/**
* 异常处理
* @author mk.yuan
*/
public class ExceptionHandle {
//未授权
private static final int UNAUTHORIZED = 401;
//禁止的
private static final int FORBIDDEN = 403;
//未找到
private static final int NOT_FOUND = 404;
//请求超时
private static final int REQUEST_TIMEOUT = 408;
//内部服务器错误
private static final int INTERNAL_SERVER_ERROR = 500;
//错误网关
private static final int BAD_GATEWAY = 502;
//暂停服务
private static final int SERVICE_UNAVAILABLE = 503;
//网关超时
private static final int GATEWAY_TIMEOUT = 504;
/**
* 处理异常
* @param throwable
* @return
*/
public static ResponseThrowable handleException(Throwable throwable) {
//返回时抛出异常
ResponseThrowable responseThrowable;
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
responseThrowable = new ResponseThrowable(throwable, ERROR.HTTP_ERROR);
switch (httpException.code()) {
case UNAUTHORIZED:
responseThrowable.message = "未授权";
break;
case FORBIDDEN:
responseThrowable.message = "禁止访问";
break;
case NOT_FOUND:
responseThrowable.message = "未找到";
break;
case REQUEST_TIMEOUT:
responseThrowable.message = "请求超时";
break;
case GATEWAY_TIMEOUT:
responseThrowable.message = "网关超时";
break;
case INTERNAL_SERVER_ERROR:
responseThrowable.message = "内部服务器错误";
break;
case BAD_GATEWAY:
responseThrowable.message = "错误网关";
break;
case SERVICE_UNAVAILABLE:
responseThrowable.message = "暂停服务";
break;
default:
responseThrowable.message = "网络错误";
break;
}
return responseThrowable;
} else if (throwable instanceof ServerException) {
//服务器异常
ServerException resultException = (ServerException) throwable;
responseThrowable = new ResponseThrowable(resultException, resultException.code);
responseThrowable.message = resultException.message;
return responseThrowable;
} else if (throwable instanceof JsonParseException
|| throwable instanceof JSONException
|| throwable instanceof ParseException) {
responseThrowable = new ResponseThrowable(throwable, ERROR.PARSE_ERROR);
responseThrowable.message = "解析错误";
return responseThrowable;
} else if (throwable instanceof ConnectException) {
responseThrowable = new ResponseThrowable(throwable, ERROR.NETWORK_ERROR);
responseThrowable.message = "连接失败";
return responseThrowable;
} else if (throwable instanceof javax.net.ssl.SSLHandshakeException) {
responseThrowable = new ResponseThrowable(throwable, ERROR.SSL_ERROR);
responseThrowable.message = "证书验证失败";
return responseThrowable;
} else if (throwable instanceof ConnectTimeoutException){
responseThrowable = new ResponseThrowable(throwable, ERROR.TIMEOUT_ERROR);
responseThrowable.message = "连接超时";
return responseThrowable;
} else if (throwable instanceof java.net.SocketTimeoutException) {
responseThrowable = new ResponseThrowable(throwable, ERROR.TIMEOUT_ERROR);
responseThrowable.message = "连接超时";
return responseThrowable;
}
else {
responseThrowable = new ResponseThrowable(throwable, ERROR.UNKNOWN);
responseThrowable.message = "未知错误";
return responseThrowable;
}
}
/**
* 约定异常
*/
public class ERROR {
/**
* 未知错误
*/
public static final int UNKNOWN = 1000;
/**
* 解析错误
*/
public static final int PARSE_ERROR = 1001;
/**
* 网络错误
*/
public static final int NETWORK_ERROR = 1002;
/**
* 协议出错
*/
public static final int HTTP_ERROR = 1003;
/**
* 证书出错
*/
public static final int SSL_ERROR = 1005;
/**
* 连接超时
*/
public static final int TIMEOUT_ERROR = 1006;
}
public static class ResponseThrowable extends Exception {
public int code;
public String message;
public ResponseThrowable(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
}
public static class ServerException extends RuntimeException {
public int code;
public String message;
}
}
package com.mkyuan.aset.mall.android.network.errorhandler;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
/**
* 网络错误处理
* @author mk.yuan
*/
public class HttpErrorHandler implements Function> {
/**
* 处理以下两类网络错误:
* 1、http请求相关的错误,例如:404,403,socket timeout等等;
* 2、应用数据的错误会抛RuntimeException,最后也会走到这个函数来统一处理;
*/
@Override
public Observable apply(Throwable throwable) throws Exception {
//通过这个异常处理,得到用户可以知道的原因
return Observable.error(ExceptionHandle.handleException(throwable));
}
}
package com.mkyuan.aset.mall.android.network.interceptor;
import com.mkyuan.aset.mall.android.network.INetworkRequiredInfo;
import com.mkyuan.aset.mall.android.network.util.DateUtil;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* 请求拦截器
* @author mk.yuan
*/
public class RequestInterceptor implements Interceptor {
/**
* 网络请求信息
*/
private INetworkRequiredInfo iNetworkRequiredInfo;
public RequestInterceptor(INetworkRequiredInfo iNetworkRequiredInfo){
this.iNetworkRequiredInfo = iNetworkRequiredInfo;
}
/**
* 拦截
* @return
*/
@Override
public Response intercept(Chain chain) throws IOException {
String nowDateTime = DateUtil.getDateTime();
//构建器
Request.Builder builder = chain.request().newBuilder();
//添加使用环境
builder.addHeader("os","android");
//添加版本号
builder.addHeader("appVersionCode",this.iNetworkRequiredInfo.getAppVersionCode());
//添加版本名
builder.addHeader("appVersionName",this.iNetworkRequiredInfo.getAppVersionName());
//添加日期时间
builder.addHeader("datetime",nowDateTime);
//返回
return chain.proceed(builder.build());
}
}
package com.mkyuan.aset.mall.android.network.interceptor;
import com.mkyuan.aset.mall.android.network.util.KLog;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Response;
/**
* 返回拦截器(响应拦截器)
*
* @author mk.yuan
*/
public class ResponseInterceptor implements Interceptor {
private static final String TAG = "DemoOkRetroRx";
/**
* 拦截
*/
@Override
public Response intercept(Chain chain) throws IOException {
long requestTime = System.currentTimeMillis();
Response response = chain.proceed(chain.request());
KLog.i(TAG, ">>>>>>requestSpendTime=" + (System.currentTimeMillis() - requestTime) + "ms");
return response;
}
}
上面协议层可以自己把有需要的http error继续加入进去。上述两个类的作用就是减少了你项目里在restful api调用时那些出错啦、错误码啦怎么处理的一个统一封装,类似我们在spring boot书写时有一个统一的controller层处理抛出exception的AOP机制一样的原理。
这个类属于辅助类,因为当有了retrofit2+okhttp3+rxjava后,你在你的代码里是看不到类似request过去、get/put/post、response回来这些细节了,那么retrofit2只是对okhttp3的一层包装,我们还是希望看到封装过的restful http请求过时是什么报文、返回时是什么报文这些原始数据以便于调试,因此我们就需要做一个日志工具类来打印和记录这些信息,它好比restful client调试http restful请求时当一条请求发送后,工具的右下角会出现一条:curl语句是一样的作用和目的。
package com.mkyuan.aset.mall.android.network.util;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* 自定义日志类
*/
public final class KLog {
private static boolean IS_SHOW_LOG = true;
private static final String DEFAULT_MESSAGE = "execute";
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final int JSON_INDENT = 4;
private static final int V = 0x1;
private static final int D = 0x2;
private static final int I = 0x3;
private static final int W = 0x4;
private static final int E = 0x5;
private static final int A = 0x6;
private static final int JSON = 0x7;
public static void init(boolean isShowLog) {
IS_SHOW_LOG = isShowLog;
}
public static void v() {
printLog(V, null, DEFAULT_MESSAGE);
}
public static void v(String msg) {
printLog(V, null, msg);
}
public static void v(String tag, String msg) {
printLog(V, tag, msg);
}
public static void d() {
printLog(D, null, DEFAULT_MESSAGE);
}
public static void d(String msg) {
printLog(D, null, msg);
}
public static void d(String tag, String msg) {
printLog(D, tag, msg);
}
public static void i() {
printLog(I, null, DEFAULT_MESSAGE);
}
public static void i(String msg) {
printLog(I, null, msg);
}
public static void i(String tag, String msg) {
printLog(I, tag, msg);
}
public static void w() {
printLog(W, null, DEFAULT_MESSAGE);
}
public static void w(String msg) {
printLog(W, null, msg);
}
public static void w(String tag, String msg) {
printLog(W, tag, msg);
}
public static void e() {
printLog(E, null, DEFAULT_MESSAGE);
}
public static void e(String msg) {
printLog(E, null, msg);
}
public static void e(String tag, String msg) {
printLog(E, tag, msg);
}
public static void a() {
printLog(A, null, DEFAULT_MESSAGE);
}
public static void a(String msg) {
printLog(A, null, msg);
}
public static void a(String tag, String msg) {
printLog(A, tag, msg);
}
public static void json(String jsonFormat) {
printLog(JSON, null, jsonFormat);
}
public static void json(String tag, String jsonFormat) {
printLog(JSON, tag, jsonFormat);
}
private static void printLog(int type, String tagStr, String msg) {
if (!IS_SHOW_LOG) {
return;
}
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
int index = 4;
String className = stackTrace[index].getFileName();
String methodName = stackTrace[index].getMethodName();
int lineNumber = stackTrace[index].getLineNumber();
String tag = (tagStr == null ? className : tagStr);
methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[ (").append(className).append(":").append(lineNumber).append(")#").append(methodName).append(" ] ");
if (msg != null && type != JSON) {
stringBuilder.append(msg);
}
String logStr = stringBuilder.toString();
switch (type) {
case V:
Log.v(tag, logStr);
break;
case D:
Log.d(tag, logStr);
break;
case I:
Log.i(tag, logStr);
break;
case W:
Log.w(tag, logStr);
break;
case E:
Log.e(tag, logStr);
break;
case A:
Log.wtf(tag, logStr);
break;
case JSON: {
if (TextUtils.isEmpty(msg)) {
Log.d(tag, "Empty or Null json content");
return;
}
String message = null;
try {
if (msg.startsWith("{")) {
JSONObject jsonObject = new JSONObject(msg);
message = jsonObject.toString(JSON_INDENT);
} else if (msg.startsWith("[")) {
JSONArray jsonArray = new JSONArray(msg);
message = jsonArray.toString(JSON_INDENT);
}
} catch (JSONException e) {
e(tag, e.getCause().getMessage() + "\n" + msg);
return;
}
printLine(tag, true);
message = logStr + LINE_SEPARATOR + message;
String[] lines = message.split(LINE_SEPARATOR);
StringBuilder jsonContent = new StringBuilder();
for (String line : lines) {
jsonContent.append("║ ").append(line).append(LINE_SEPARATOR);
}
Log.d(tag, jsonContent.toString());
printLine(tag, false);
}
break;
default:
break;
}
}
private static void printLine(String tag, boolean isTop) {
if (isTop) {
Log.d(tag, "╔═══════════════════════════════════════════════════════════════════════════════════════");
} else {
Log.d(tag, "╚═══════════════════════════════════════════════════════════════════════════════════════");
}
}
}
它里面还用到了一个辅助类,DataUtil类,代码也附上。
package com.mkyuan.aset.mall.android.network.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Objects;
public class DateUtil {
public static final String STANDARD_TIME = "yyyy-MM-dd HH:mm:ss";
public static final String FULL_TIME = "yyyy-MM-dd HH:mm:ss.SSS";
public static final String YEAR_MONTH_DAY = "yyyy-MM-dd";
public static final String YEAR_MONTH_DAY_CN = "yyyy年MM月dd号";
public static final String HOUR_MINUTE_SECOND = "HH:mm:ss";
public static final String HOUR_MINUTE_SECOND_CN = "HH时mm分ss秒";
public static final String YEAR = "yyyy";
public static final String MONTH = "MM";
public static final String DAY = "dd";
public static final String HOUR = "HH";
public static final String MINUTE = "mm";
public static final String SECOND = "ss";
public static final String MILLISECOND = "SSS";
public static final String YESTERDAY = "昨天";
public static final String TODAY = "今天";
public static final String TOMORROW = "明天";
public static final String SUNDAY = "星期日";
public static final String MONDAY = "星期一";
public static final String TUESDAY = "星期二";
public static final String WEDNESDAY = "星期三";
public static final String THURSDAY = "星期四";
public static final String FRIDAY = "星期五";
public static final String SATURDAY = "星期六";
public static final String[] weekDays = {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY};
/**
* 获取标准时间
*
* @return 例如 2021-07-01 10:35:53
*/
public static String getDateTime() {
return new SimpleDateFormat(STANDARD_TIME, Locale.CHINESE).format(new Date());
}
/**
* 获取完整时间
*
* @return 例如 2021-07-01 10:37:00.748
*/
public static String getFullDateTime() {
return new SimpleDateFormat(FULL_TIME, Locale.CHINESE).format(new Date());
}
/**
* 获取年月日(今天)
*
* @return 例如 2021-07-01
*/
public static String getTheYearMonthAndDay() {
return new SimpleDateFormat(YEAR_MONTH_DAY, Locale.CHINESE).format(new Date());
}
/**
* 获取年月日
*
* @return 例如 2021年07月01号
*/
public static String getTheYearMonthAndDayCn() {
return new SimpleDateFormat(YEAR_MONTH_DAY_CN, Locale.CHINESE).format(new Date());
}
/**
* 获取年月日
* @param delimiter 分隔符
* @return 例如 2021年07月01号
*/
public static String getTheYearMonthAndDayDelimiter(CharSequence delimiter) {
return new SimpleDateFormat(YEAR + delimiter + MONTH + delimiter + DAY, Locale.CHINESE).format(new Date());
}
/**
* 获取时分秒
*
* @return 例如 10:38:25
*/
public static String getHoursMinutesAndSeconds() {
return new SimpleDateFormat(HOUR_MINUTE_SECOND, Locale.CHINESE).format(new Date());
}
/**
* 获取时分秒
*
* @return 例如 10时38分50秒
*/
public static String getHoursMinutesAndSecondsCn() {
return new SimpleDateFormat(HOUR_MINUTE_SECOND_CN, Locale.CHINESE).format(new Date());
}
/**
* 获取时分秒
* @param delimiter 分隔符
* @return 例如 2021/07/01
*/
public static String getHoursMinutesAndSecondsDelimiter(CharSequence delimiter) {
return new SimpleDateFormat(HOUR + delimiter + MINUTE + delimiter + SECOND, Locale.CHINESE).format(new Date());
}
/**
* 获取年
*
* @return 例如 2021
*/
public static String getYear() {
return new SimpleDateFormat(YEAR, Locale.CHINESE).format(new Date());
}
/**
* 获取月
*
* @return 例如 07
*/
public static String getMonth() {
return new SimpleDateFormat(MONTH, Locale.CHINESE).format(new Date());
}
/**
* 获取天
*
* @return 例如 01
*/
public static String getDay() {
return new SimpleDateFormat(DAY, Locale.CHINESE).format(new Date());
}
/**
* 获取小时
*
* @return 例如 10
*/
public static String getHour() {
return new SimpleDateFormat(HOUR, Locale.CHINESE).format(new Date());
}
/**
* 获取分钟
*
* @return 例如 40
*/
public static String getMinute() {
return new SimpleDateFormat(MINUTE, Locale.CHINESE).format(new Date());
}
/**
* 获取秒
*
* @return 例如 58
*/
public static String getSecond() {
return new SimpleDateFormat(SECOND, Locale.CHINESE).format(new Date());
}
/**
* 获取毫秒
*
* @return 例如 666
*/
public static String getMilliSecond() {
return new SimpleDateFormat(MILLISECOND, Locale.CHINESE).format(new Date());
}
/**
* 获取时间戳
*
* @return 例如 1625107306051
*/
public static long getTimestamp() {
return System.currentTimeMillis();
}
/**
* 将时间转换为时间戳
*
* @param time 例如 2021-07-01 10:44:11
* @return 1625107451000
*/
public static long dateToStamp(String time) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(STANDARD_TIME, Locale.CHINESE);
Date date = null;
try {
date = simpleDateFormat.parse(time);
} catch (ParseException e) {
e.printStackTrace();
}
return Objects.requireNonNull(date).getTime();
}
/**
* 将时间戳转换为时间
*
* @param timeMillis 例如 1625107637084
* @return 例如 2021-07-01 10:47:17
*/
public static String stampToDate(long timeMillis) {
return new SimpleDateFormat(STANDARD_TIME, Locale.CHINESE).format(new Date(timeMillis));
}
/**
* 获取今天是星期几
*
* @return 例如 星期四
*/
public static String getTodayOfWeek() {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int index = cal.get(Calendar.DAY_OF_WEEK) - 1;
if (index < 0) {
index = 0;
}
return weekDays[index];
}
/**
* 根据输入的日期时间计算是星期几
*
* @param dateTime 例如 2021-06-20
* @return 例如 星期日
*/
public static String getWeek(String dateTime) {
Calendar cal = Calendar.getInstance();
if ("".equals(dateTime)) {
cal.setTime(new Date(System.currentTimeMillis()));
} else {
SimpleDateFormat sdf = new SimpleDateFormat(YEAR_MONTH_DAY, Locale.getDefault());
Date date;
try {
date = sdf.parse(dateTime);
} catch (ParseException e) {
date = null;
e.printStackTrace();
}
if (date != null) {
cal.setTime(new Date(date.getTime()));
}
}
return weekDays[cal.get(Calendar.DAY_OF_WEEK) - 1];
}
/**
* 获取输入日期的昨天
*
* @param date 例如 2021-07-01
* @return 例如 2021-06-30
*/
public static String getYesterday(Date date) {
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.add(Calendar.DATE, -1);
date = calendar.getTime();
return new SimpleDateFormat(YEAR_MONTH_DAY, Locale.getDefault()).format(date);
}
/**
* 获取输入日期的明天
*
* @param date 例如 2021-07-01
* @return 例如 2021-07-02
*/
public static String getTomorrow(Date date) {
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.add(Calendar.DATE, +1);
date = calendar.getTime();
return new SimpleDateFormat(YEAR_MONTH_DAY, Locale.getDefault()).format(date);
}
/**
* 根据年月日计算是星期几并与当前日期判断 非昨天、今天、明天 则以星期显示
*
* @param dateTime 例如 2021-07-03
* @return 例如 星期六
*/
public static String getDayInfo(String dateTime) {
String dayInfo;
String yesterday = getYesterday(new Date());
String today = getTheYearMonthAndDay();
String tomorrow = getTomorrow(new Date());
if (dateTime.equals(yesterday)) {
dayInfo = YESTERDAY;
} else if (dateTime.equals(today)) {
dayInfo = TODAY;
} else if (dateTime.equals(tomorrow)) {
dayInfo = TOMORROW;
} else {
dayInfo = getWeek(dateTime);
}
return dayInfo;
}
/**
* 获取本月天数
*
* @return 例如 31
*/
public static int getCurrentMonthDays() {
Calendar calendar = Calendar.getInstance();
//把日期设置为当月第一天
calendar.set(Calendar.DATE, 1);
//日期回滚一天,也就是最后一天
calendar.roll(Calendar.DATE, -1);
return calendar.get(Calendar.DATE);
}
/**
* 获得指定月的天数
*
* @param year 例如 2021
* @param month 例如 7
* @return 例如 31
*/
public static int getMonthDays(int year, int month) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
//把日期设置为当月第一天
calendar.set(Calendar.DATE, 1);
//日期回滚一天,也就是最后一天
calendar.roll(Calendar.DATE, -1);
return calendar.get(Calendar.DATE);
}
}
rxjava采用的是Observer模式,它可以让你的http请求体在返回时采用异步懒加载的方式,从而不影响到Android的主进程而引起相应的Android崩溃、闪退等问题。
在retrofit+okhttp+rxjava框架里,三者的分工其实是这样的:
Retrofit做上层网络请求接口的封装,同时将需要的数据解析成实体;
OkHttp负责请求的过程;
RxJava负责异步和线程切换;
package com.mkyuan.aset.mall.android.network;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* 基础返回类
* @author mk.yuan
*/
public class BaseResponse {
//返回码
@SerializedName("res_code")
@Expose
public Integer responseCode;
//返回的错误信息
@SerializedName("res_error")
@Expose
public String responseError;
}
package com.mkyuan.aset.mall.android.network;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
/**
* 自定义Observer
*
* @author mk.yuan
*/
public abstract class BaseObserver implements Observer {
//开始
@Override
public void onSubscribe(Disposable disposable) {
}
//继续
@Override
public void onNext(T t) {
onSuccess(t);
}
//异常
@Override
public void onError(Throwable e) {
onFailure(e);
}
//完成
@Override
public void onComplete() {
}
//成功
public abstract void onSuccess(T t);
//失败
public abstract void onFailure(Throwable e);
}
此处请一定记得这个Observer要使用io.reactivex下的Observer。
package com.mkyuan.aset.mall.android.network;
import android.app.Application;
public interface INetworkRequiredInfo {
/**
* 获取App版本名
*/
String getAppVersionName();
/**
* 获取App版本号
*/
String getAppVersionCode();
/**
* 判断是否为Debug模式
*/
boolean isDebug();
/**
* 获取全局上下文参数
*/
Application getApplicationContext();
}
它是INetworkRequiredInfo的实现类。
package com.mkyuan.aset.mall.android.network;
import android.app.Application;
import com.mkyuan.aset.mall.android.BuildConfig;
/**
* 网络访问信息
* @author mk.yuan
*/
public class NetworkRequiredInfo implements INetworkRequiredInfo {
private final Application application;
public NetworkRequiredInfo(Application application){
this.application = application;
}
/**
* 版本名
*/
@Override
public String getAppVersionName() {
return BuildConfig.VERSION_NAME;
}
/**
* 版本号
*/
@Override
public String getAppVersionCode() {
return String.valueOf(BuildConfig.VERSION_CODE);
}
/**
* 是否为debug
*/
@Override
public boolean isDebug() {
return BuildConfig.DEBUG;
}
/**
* 应用全局上下文
*/
@Override
public Application getApplicationContext() {
return application;
}
}
好了,此处进入主题了。
介个类,是我们在发起http restful请求的核心类,一般我们在代码里会通过以下这样的调用来完成okhttp3请求的发送:
LoginAPI loginAPI = NetworkApi.createService(LoginAPI.class);
下面来看NetworkAPI的代码
package com.mkyuan.aset.mall.android.network;
import com.mkyuan.aset.mall.android.network.errorhandler.ExceptionHandle;
import com.mkyuan.aset.mall.android.network.errorhandler.HttpErrorHandler;
import com.mkyuan.aset.mall.android.network.interceptor.RequestInterceptor;
import com.mkyuan.aset.mall.android.network.interceptor.ResponseInterceptor;
import com.mkyuan.aset.mall.android.util.AsetMallConstants;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.ObservableTransformer;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* 网络Api
* @author mk.yuan
* @description NetworkApi
*/
public class NetworkApi {
/**
* 获取APP运行状态及版本信息,用于日志打印
*/
private static INetworkRequiredInfo iNetworkRequiredInfo;
/**
* API访问地址
*/
//private static final String BASE_URL = "http://192.168.0.114:9080";
private static OkHttpClient okHttpClient;
private static final HashMap retrofitHashMap = new HashMap<>();
/**
* 初始化
*/
public static void init(INetworkRequiredInfo networkRequiredInfo) {
iNetworkRequiredInfo = networkRequiredInfo;
}
/**
* 创建serviceClass的实例
*/
public static T createService(Class serviceClass) {
return getRetrofit(serviceClass).create(serviceClass);
}
/**
* 配置OkHttp
*
* @return OkHttpClient
*/
private static OkHttpClient getOkHttpClient() {
//不为空则说明已经配置过了,直接返回即可。
if (okHttpClient == null) {
//OkHttp构建器
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//设置缓存大小
int cacheSize = 100 * 1024 * 1024;
//设置网络请求超时时长,这里设置为6s
builder.connectTimeout(6, TimeUnit.SECONDS);
//添加请求拦截器,如果接口有请求头的话,可以放在这个拦截器里面
builder.addInterceptor(new RequestInterceptor(iNetworkRequiredInfo));
//添加返回拦截器,可用于查看接口的请求耗时,对于网络优化有帮助
builder.addInterceptor(new ResponseInterceptor());
//当程序在debug过程中则打印数据日志,方便调试用。
if (iNetworkRequiredInfo != null && iNetworkRequiredInfo.isDebug()) {
//iNetworkRequiredInfo不为空且处于debug状态下则初始化日志拦截器
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
//设置要打印日志的内容等级,BODY为主要内容,还有BASIC、HEADERS、NONE。
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//将拦截器添加到OkHttp构建器中
builder.addInterceptor(httpLoggingInterceptor);
}
//OkHttp配置完成
okHttpClient = builder.build();
}
return okHttpClient;
}
/**
* 配置Retrofit
*
* @param serviceClass 服务类
* @return Retrofit
*/
private static Retrofit getRetrofit(Class serviceClass) {
if (retrofitHashMap.get(AsetMallConstants.ASET_MALL_ADMIN_BASE_URL + serviceClass.getName()) != null) {
//刚才上面定义的Map中键是String,值是Retrofit,当键不为空时,必然有值,有值则直接返回。
return retrofitHashMap.get(AsetMallConstants.ASET_MALL_ADMIN_BASE_URL + serviceClass.getName());
}
//初始化Retrofit Retrofit是对OKHttp的封装,通常是对网络请求做处理,也可以处理返回数据。
//Retrofit构建器
Retrofit.Builder builder = new Retrofit.Builder();
//设置访问地址
builder.baseUrl(AsetMallConstants.ASET_MALL_ADMIN_BASE_URL);
//设置OkHttp客户端,传入上面写好的方法即可获得配置后的OkHttp客户端。
builder.client(getOkHttpClient());
//设置数据解析器 会自动把请求返回的结果(json字符串)通过Gson转化工厂自动转化成与其结构相符的实体Bean
builder.addConverterFactory(GsonConverterFactory.create());
//设置请求回调,使用RxJava 对网络返回进行处理
builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
//retrofit配置完成
Retrofit retrofit = builder.build();
//放入Map中
retrofitHashMap.put(AsetMallConstants.ASET_MALL_ADMIN_BASE_URL + serviceClass.getName(), retrofit);
//最后返回即可
return retrofit;
}
/**
* 配置RxJava 完成线程的切换
*
* @param observer 这个observer要注意不要使用lifecycle中的Observer
* @param 泛型
* @return Observable
*/
public static ObservableTransformer applySchedulers(final Observer observer) {
return upstream -> {
Observable observable = upstream
.subscribeOn(Schedulers.io())//线程订阅
.observeOn(AndroidSchedulers.mainThread())//观察Android主线程
.map(NetworkApi.getAppErrorHandler())//判断有没有500的错误,有则进入getAppErrorHandler
.onErrorResumeNext(new HttpErrorHandler<>());//判断有没有400的错误
//订阅观察者
observable.subscribe(observer);
return observable;
};
}
/**
* 错误码处理
*/
protected static Function getAppErrorHandler() {
return response -> {
//当response返回出现500之类的错误时
if (response instanceof BaseResponse && ((BaseResponse) response).responseCode >= 500) {
//通过这个异常处理,得到用户可以知道的原因
ExceptionHandle.ServerException exception = new ExceptionHandle.ServerException();
exception.code = ((BaseResponse) response).responseCode;
exception.message = ((BaseResponse) response).responseError != null ? ((BaseResponse) response).responseError : "";
throw exception;
}
return response;
};
}
}
各位可以看到,到这个类为止,retrofit、okhttp3、rxjava全部窜起来用了。
下面这一句可以明显看到retrofit对okhttp3是如何封装的
//设置OkHttp客户端,传入上面写好的方法即可获得配置后的OkHttp客户端。
builder.client(getOkHttpClient());
下面我们就要来讲一下,这套封装在实际项目中应该怎么使用了。
说,有这么一个需求:我要在每个“需要是否登录校验的界面打开时校验用户是否登录,用户是否登录是通过用户的http请求头-header是否带有ut字段,且这个ut字段在后台是否存在且有效来判断的”。
因此,后台会提供一个post请求给到前台,如下:
@POST("/api/account/checkLoginUT")
Observable checkLoginUT(@Header("ut") String ut);
所以,我们在我们的业务模块里就定义一个API,如:LoginAPI,代码如下:
package com.mkyuan.aset.mall.android.login.api;
import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;
import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;
public interface LoginAPI {
@POST("/api/account/login")
Observable login(@Body RequestBody body);
@POST("/api/account/getSmsVerifiedCode")
Observable getSmsVerifiedCode(@Body RequestBody body);
@POST("/api/account/loginBySMSVerifiedCode")
Observable loginBySMSVerifiedCode(@Body RequestBody body);
@POST("/api/account/logout")
Observable logoutByUt(@Body RequestBody body);
@POST("/api/account/checkLoginUT")
Observable checkLoginUT(@Header("ut") String ut);
}
我这边的例子里有post、有get、有header,对于retrofit来说一切只是“方法中的参数”,唯一的区别是annotation的不同。方便吧!
然后我们来看如何使用retrofit+okhttp3+rxjava来调用和处理一个http请求的返回吧。
由于这是一个异步的过程,因此我们不能直接在onSuccess和onFailure里做Android里的界面操作,因为是异步请求和返回的,所以你用代码语句的顺序不代表实际网络请求值到达的时序,因此,我们这边使用了一个小技巧。
先定义了一个LoginCheckCallBack的接口
LoginCheckCallBack.java
package com.mkyuan.aset.mall.android.util.aop.login;
public interface LoginCheckCallBack {
public void changeValue(boolean loginResult);
}
把它跟着调用的上游,传入调用retrofit+okhttp3+rxjava
//判断当前是否已经登录
LoginManager.isLogin(new LoginCheckCallBack() {
@Override
public void changeValue(boolean loginResult) {
if(loginResult) {
Log.i(TAG, ">>>>>>用户己登录走入下一步");
try {
//因为用户被判定登录,因此继续打开相应的只有登录用户才能看到的界面
} catch (Throwable e) {
Log.e(TAG,e.getMessage(),e);
}
}else{
//如果未登录,去登录页面
Log.i(TAG, ">>>>>>用户未登录去登录页面");
LoginManager.gotoLoginPage();
}
}
});
好,接着我们来看isLogin方法
public static void isLogin(LoginCheckCallBack loginCheckCallBack) {
Context ctx = null;
try {
ctx = ContextUtils.getCurApplicationContext();
SharedPreferenceHelper spHelper = new SharedPreferenceHelper(ctx);
Map data = spHelper.read();
if (data.get("ut") != null && data.get("ut").trim().length() > 0) {
String utValue = data.get("ut");
//开始调用okhttp3, retrofit, rxjava框架
LoginAPI loginAPI = NetworkApi.createService(LoginAPI.class);
//loginAPI.checkLoginUT().
loginAPI.checkLoginUT(utValue).compose(NetworkApi.applySchedulers(new BaseObserver() {
@Override
public void onSuccess(LoginResponseBean loginResponseBean) {
Log.i(TAG, ">>>>>>" + new Gson().toJson(loginResponseBean));
int returnCode = loginResponseBean.getCode();
String returnMsg = loginResponseBean.getMessage();
if (returnCode == 0) {
//result = true;
loginCheckCallBack.changeValue(true);
Log.i(TAG,
">>>>>>get verifiedCode->" + loginResponseBean.getData());
//startActivity(new Intent(SmsLoginActivity.this, MainActivity
// .class));
} else {
loginCheckCallBack.changeValue(false);
}
}
@Override
public void onFailure(Throwable e) {
Log.e(TAG, ">>>>>>Network Error: " + e.toString(), e);
loginCheckCallBack.changeValue(false);
}
}));
} else {
loginCheckCallBack.changeValue(false);
}
} catch (Exception e) {
Log.e(TAG, ">>>>>>isLogin error: " + e.getMessage());
loginCheckCallBack.changeValue(false);
}
}
这个案例运行后在控制台会产生以下日志,通过日志我们可以清晰的看到okhttp3请求报文和响应的全过程。
把报文请求post出去
得到刚才请求的返回
好了,到此结束今天的教程。
这篇教程就是为下一篇做的前置铺垫,因为在下一篇里我们要使用这个isLogin方法来演示Android里如何使用AOP来对那些需要判定是否登录的界面做统一拦截。
最后还是那句话,自己不妨动动手试一下吧。