Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava

开篇

        一个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解决而要用3样东西去取待原来一样东西就可以实现的事

        为什么要从“简”变“难”、择“难”去“易”呢?

        当然,这是“码农”的眼光去看待原来一个okhttp3可以搞定的事的角度去思考这件事所能看到的局限。而一旦让我们从全局来看原来所谓“一个okhttp3”可以搞定的事到底是个什么样的情况呢?

        假设,我们有一个get请求,返回是一个{"code":0, "data":"4e23h100hm5ffg8l"}这样的一restful api,我们用okhttp3是怎么实现的呢?

  1. 先要定义http请求头;

  1. 设置一堆的请求变量;

  1. 声明okhttp builder为同步还是异步;

  1. 覆盖on success, on fail等方法;

  1. 判断http返回值是否200;

  1. 如果是200,取body;

  1. body取到后转成gson;

  1. 然后。。。

  1. 然后。。。

  1. 还有异步线程handler回调;

  1. 刷新界面;

        每一个不同的API请求都是上面这一陀。。。对不?然后有人说了。。。你封装呀,把一些重复步骤封装呀。。。哈哈哈。

        说到点了,retrofit+okhttp3+rxjava就是这个封装,当然它不可能封装到开发、程序员们只需要一句话就能实现上面所有步骤,这是不现实的。

        但是retrofit+okhttp3+rxjava可以把以上一堆步骤中的重复书写代码进行了最大程度的封装。。。so。。。已经有“大牛”给你封装了”,你自己也就不用去造轮子了。这就是我在另一个架构系列-高性能零售架构系列里提到的:当团队超过几十人且属于规模化基于一个产品进行协同作战模式下,根本不需要去做什么封装。因为你是对企业的稳定性、性能负责而不是快点交了可以收合同尾款这种乙方性质作法时,你会诞生一种这样的想法:希望这个东西应封装尽封装,不该封装的就不要封,在解决问题、共享信息、消除黑盒、解放生产力、质量、性能上达到最大的balance。这是一种“企业级架构思想”。

        retrofit+okhttp3+rxjava正是这么一种存在,这也是它存在的意义所在即:给予你充分自由又可最大限度消除一些重复代码,并且还掩盖了底层协议部分内容充分解放了程序员的生产力。

        看看似乎有三样东西一起做,其实使用上你是无感的。

        笔者基于这3个组合上,做了少许的小封装,使得它几乎可以适用于任何的开发团队,同时在笔者自己的mao-sir.com上也是使用得是这一套东西,框架代码完全公开给大家。

        下面就让我们一起进入retrofit+okhttp3+rxjava的框架搭建教程。

搭建retrofit2+okhttp3+rxjava

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第1张图片

        以上就是mao-sir的retrofit2+okhttp3+rxjava的框架的结构。

        任何一个业务模块里只要写一个xxx.xxx.api.业务名API就可以使用restful api的访问了。

        看看挺多,其实一点没什么难度,我们一块块把它们“掰开揉碎”了一点点来消化即可。

引入retrofit2+okhttp3+rxjava第三方包

        在模块的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'

        引入后它长这样

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第2张图片

http协议层相关类封装

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第3张图片

ExceptionHandle.java

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;
    }
}

HttpErrorHandler.java

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));
    }
}

RequestInterceptor.java

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());
    }
}

ResponseInterceptor.java

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机制一样的原理。

拦截http请求、返回报文并输出日志用工具类

        这个类属于辅助类,因为当有了retrofit2+okhttp3+rxjava后,你在你的代码里是看不到类似request过去、get/put/post、response回来这些细节了,那么retrofit2只是对okhttp3的一层包装,我们还是希望看到封装过的restful http请求过时是什么报文、返回时是什么报文这些原始数据以便于调试,因此我们就需要做一个日志工具类来打印和记录这些信息,它好比restful client调试http restful请求时当一条请求发送后,工具的右下角会出现一条:curl语句是一样的作用和目的。

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第4张图片

KLog.java

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类,代码也附上。

DataUtil.java

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封装response成object

        rxjava采用的是Observer模式,它可以让你的http请求体在返回时采用异步懒加载的方式,从而不影响到Android的主进程而引起相应的Android崩溃、闪退等问题。

        在retrofit+okhttp+rxjava框架里,三者的分工其实是这样的:

  • Retrofit做上层网络请求接口的封装,同时将需要的数据解析成实体;

  • OkHttp负责请求的过程;

  • RxJava负责异步和线程切换;

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第5张图片

BaseResponse.java

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;
}

BaseObserver.java

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。

使用retrofit对于okhttp3的网络请求的封装

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第6张图片

INetworkRequiredInfo.java

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();
}

NetworkRequiredInfo.java

        它是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;
    }
}

NetworkApi.java

        好了,此处进入主题了。

        介个类,是我们在发起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());

        下面我们就要来讲一下,这套封装在实际项目中应该怎么使用了。

在真实案例中使用retrofit2+okhttp3+rxjava

        说,有这么一个需求:我要在每个“需要是否登录校验的界面打开时校验用户是否登录,用户是否登录是通过用户的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出去

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第7张图片

得到刚才请求的返回

Android入门第65天-mvvm模式下的retrofit2+okhttp3+rxjava_第8张图片

好了,到此结束今天的教程。

        这篇教程就是为下一篇做的前置铺垫,因为在下一篇里我们要使用这个isLogin方法来演示Android里如何使用AOP来对那些需要判定是否登录的界面做统一拦截。

        最后还是那句话,自己不妨动动手试一下吧。

你可能感兴趣的:(Android从入门到精通,android,rxjava,retrofit,okhttp,android,rxjava,mvvm,retrofit)