Android OkHttp+Retrofit+RxJava搭建网络访问框架(含源码)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Android OkHttp+Retrofit+RxJava搭建网络访问框架(含源码)

OkHttp+Retrofit+RxJava搭建网络访问框架


前言


在实际开发APP中,网络访问是必不可少的,最开始访问网络是使用HttpURLConnection、而后面有了一些框架比如Volley、OkHttp、Retrofit等。那么你可能看到最多的是OkHttp,因为它很出名,Google也推荐你使用此框架进行网络访问。你可能会说Retrofit,Retrofit其实就是对OkHttp的二次封装。还有RxJava,这个又是用来干嘛的呢?为什么要将三者组合起来,组合有什么优势吗?带着这些问题看下去。


正文


创建一个名为NetworkFrameWorkDemo的项目。


2020122818121899.png


点击Finish完成创建。


下面创建网络模块,点击导航栏 File→New→New Module…


20201228181423537.png


选择Android Library,点击Next。


20201228181459237.png


设置模块名称、模块包名等信息,点击Finish完成创建。


20201228181552898.png


创建好之后如下图所示:


20201228181643864.png


下面可以先不管这个app模块,把重点放到这个network模块中,等到要访问网络的时候再看app模块。


一、添加依赖


在network的build.gradle的dependencies{}闭包下添加如下依赖:


  //retrofit2
    api 'com.squareup.retrofit2:retrofit:2.4.0'
    //这里用api 是为了让其他模块也可以使用gson
    api 'com.squareup.retrofit2:converter-gson:2.4.0'
    //日志拦截器
    api 'com.squareup.okhttp3:logging-interceptor:3.9.0'
    api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    //rxjava
    api 'io.reactivex.rxjava2:rxandroid:2.1.1'
    api 'io.reactivex.rxjava2:rxjava:2.2.12'
    api 'androidx.preference:preference:1.0.0'
    //图片加载框架
    api 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'


点击Sync进行同步依赖一下,这里你如果看到api觉得很奇怪的话,我这里解释一下,它和implementation其实差不多,只不过在依赖模块中你可以使用这个api。


在com.llw.network包下创建一个接口INetworkRequiredInfo,在里面写一些回调的方法,用于获取App的版本名、版本号、运行状态、全局上下文参数,里面代码如下:


/**
 * App运行信息接口
 * @author llw
 */
public interface INetworkRequiredInfo {
    /**
     * 获取App版本名
     */
    String getAppVersionName();
    /**
     * 获取App版本号
     */
    String getAppVersionCode();
    /**
     * 判断是否为Debug模式
     */
    boolean isDebug();
    /**
     * 获取全局上下文参数
     */
    Application getApplicationContext();
}


二、配置OkHttp


然后在这个包下建一个NetworkApi类,用于配置网络请求,首先是对OkHttp进行一些配置。


package com.llw.network;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
/**
 * 网络API
 *
 * @author llw
 */
public class NetworkApi {
    //获取APP运行状态及版本信息,用于日志打印
    private static INetworkRequiredInfo iNetworkRequiredInfo;
    //OkHttp客户端
    private static OkHttpClient okHttpClient;
    /**
     * 配置OkHttp
     *
     * @return OkHttpClient
     */
    private static OkHttpClient getOkHttpClient() {
        //不为空则说明已经配置过了,直接返回即可。
        if (okHttpClient == null) {
            //OkHttp构建器
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            //设置缓存大小
            int cacheSize = 100 * 1024 * 1024;
            //设置OkHttp网络缓存
            builder.cache(new Cache(iNetworkRequiredInfo.getApplicationContext().getCacheDir(),cacheSize));
            //设置网络请求超时时长,这里设置为6s
            builder.connectTimeout(6, TimeUnit.SECONDS);
            //在这里添加拦截器,通过拦截器可以知道一些信息,这对于开发中是有所帮助的,后面给加上。
            // ...
            //当程序在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;
    }
}


在这里 getOkHttpClient() 方法中对OkHttp进行配置,注释已经写得很清楚了,我就也没有什么好说的。而这个里面其实还有两个日志拦截构造器没有配置上去,稍后写了之后,再添加上去。


三、配置Retrofit


在NetworkApi定义两个成员变量,分别用于状态API访问地址和Retrofit


  //retrofitHashMap
    private static HashMap<String, Retrofit> retrofitHashMap = new HashMap<>();
    //API访问地址
    private static String mBaseUrl;


下面写一个配置Retrofit的方法,里面的代码如下:


  /**
     * 配置Retrofit
     *
     * @param serviceClass 服务类
     * @return Retrofit
     */
    private static Retrofit getRetrofit(Class serviceClass) {
        if (retrofitHashMap.get(mBaseUrl + serviceClass.getName()) != null) {
            //刚才上面定义的Map中键是String,值是Retrofit,当键不为空时,必然有值,有值则直接返回。
            return retrofitHashMap.get(mBaseUrl + serviceClass.getName());
        }
        //初始化Retrofit  Retrofit是对OKHttp的封装,通常是对网络请求做处理,也可以处理返回数据。
        //Retrofit构建器
        Retrofit.Builder builder = new Retrofit.Builder();
        //设置访问地址
        builder.baseUrl(mBaseUrl);
        //设置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(mBaseUrl + serviceClass.getName(), retrofit);
        //最后返回即可
        return retrofit;
    }


四、配置RxJava


RxJava是对OkHttp的请求返回做处理,那么这个就涉及到线程的切换和异常的处理。因为在实际开发中很容易出现某一个接口请求返回500、400、404之类的异常,那么也可以在这个RxJava中做处理。


先在com.llw.network包下创建一个errorhandler包,该包下创建ExceptionHandle类,用来处理异常,里面的代码如下:


package com.llw.network.errorhandler;
import android.net.ParseException;
import com.google.gson.JsonParseException;
import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONException;
import java.net.ConnectException;
import retrofit2.HttpException;
/**
 * 异常处理
 * @author llw
 */
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:
                case FORBIDDEN:
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                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类,代码如下:


package com.llw.network.errorhandler;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
/**
 * 网络错误处理
 */
public class HttpErrorHandler<T> implements Function<Throwable, Observable<T>> {
    /**
     * 处理以下两类网络错误:
     * 1、http请求相关的错误,例如:404,403,socket timeout等等;
     * 2、应用数据的错误会抛RuntimeException,最后也会走到这个函数来统一处理;
     */
    @Override
    public Observable<T> apply(Throwable throwable) throws Exception {
        //通过这个异常处理,得到用户可以知道的原因
        return Observable.error(ExceptionHandle.handleException(throwable));
    }
}


还需要写一个基础返回类,在com.llw.network下新建一个BaseResponse,代码如下:


package com.llw.network;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
 * 基础返回类
 * @author llw
 */
public class BaseResponse {
    //返回码
    @SerializedName("res_code")
    @Expose
    public Integer responseCode;
    //返回的错误信息
    @SerializedName("res_error")
    @Expose
    public String responseError;
}


现在准备工作都做好了,下面就要写这个RxJava的配置了,不过还有一步就是,在NetworkApi中写一个错误码的处理方法,代码如下:


  /**
     * 错误码处理
     */
    protected static <T> Function<T, T> getAppErrorHandler() {
        return new Function<T, T>() {
            @Override
            public T apply(T response) throws Exception {
                //当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;
            }
        };
    }


下面终于到了这个RxJava的配置了


  /**
     * 配置RxJava 完成线程的切换,如果是Kotlin中完全可以直接使用协程
     *
     * @param observer 这个observer要注意不要使用lifecycle中的Observer
     * @param <T>      泛型
     * @return Observable
     */
    public static <T> ObservableTransformer<T, T> applySchedulers(final Observer<T> observer) {
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                Observable<T> observable = upstream
                        .subscribeOn(Schedulers.io())//线程订阅
                        .observeOn(AndroidSchedulers.mainThread())//观察Android主线程
                        .map(NetworkApi.<T>getAppErrorHandler())//判断有没有500的错误,有则进入getAppErrorHandler
                        .onErrorResumeNext(new HttpErrorHandler<T>());//判断有没有400的错误
                //这里还少了对异常
                //订阅观察者
                observable.subscribe(observer);
                return observable;
            }
        };
    }


五、增加拦截器


拦截器中需要打印日志和时间转换,对此需要几个工具类,所以在com.llw.network下新建一个utils包,下面新建一个DateUitl


package com.llw.network.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
public class DateUtil {
    //获取当前完整的日期和时间
    public static String getNowDateTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(new Date());
    }
    //获取当前日期
    public static String getNowDate() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date());
    }
    //前一天
    public static String getYesterday(Date date) {
        String tomorrow = "";
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, -1);
        date = calendar.getTime();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        tomorrow = formatter.format(date);
        return tomorrow;
    }
    //后一天
    public static String getTomorrow(Date date) {
        String tomorrow = "";
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, +1);
        date = calendar.getTime();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        tomorrow = formatter.format(date);
        return tomorrow;
    }
    //获取当前时间
    public static String getNowTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(new Date());
    }
    //获取当前日期(精确到毫秒)
    public static String getNowTimeDetail() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        return sdf.format(new Date());
    }
    //获取今天是星期几
    public static String getWeekOfDate(Date date) {
        String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
        if (w < 0) {
        }
        w = 0;
        return weekDays[w];
    }
    //计算星期几
    private static int getDayOfWeek(String dateTime) {
        Calendar cal = Calendar.getInstance();
        if (dateTime.equals("")) {
            cal.setTime(new Date(System.currentTimeMillis()));
        } else {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", 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 cal.get(Calendar.DAY_OF_WEEK);
    }
    //根据年月日计算是星期几并与当前日期判断  非昨天、今天、明天 则以星期显示
    public static String Week(String dateTime) {
        String week = "";
        String yesterday = "";
        String today = "";
        String tomorrow = "";
        yesterday = getYesterday(new Date());
        today = getNowDate();
        tomorrow = getTomorrow(new Date());
        if (dateTime.equals(yesterday)) {
            week = "昨天";
        } else if (dateTime.equals(today)) {
            week = "今天";
        } else if (dateTime.equals(tomorrow)) {
            week = "明天";
        } else {
            switch (getDayOfWeek(dateTime)) {
                case 1:
                    week = "星期日";
                    break;
                case 2:
                    week = "星期一";
                    break;
                case 3:
                    week = "星期二";
                    break;
                case 4:
                    week = "星期三";
                    break;
                case 5:
                    week = "星期四";
                    break;
                case 6:
                    week = "星期五";
                    break;
                case 7:
                    week = "星期六";
                    break;
            }
        }
        return week;
    }
    //将时间戳转化为对应的时间(10位或者13位都可以)
    public static String formatTime(long time) {
        String times = null;
        if (String.valueOf(time).length() > 10) {// 10位的秒级别的时间戳
            times = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time * 1000));
        } else {// 13位的秒级别的时间戳
            times = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
        }
        return times;
    }
    //将时间字符串转为时间戳字符串
    public static String getStringTimestamp(String time) {
        String timestamp = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Long longTime = sdf.parse(time).getTime() / 1000;
            timestamp = Long.toString(longTime);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return timestamp;
    }
}


同样再建一个KLog类,用于日志打印。


package com.llw.network.utils;
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, "╚═══════════════════════════════════════════════════════════════════════════════════════");
        }
    }
}


在com.llw.network下新建一个Interceptor包,包下新建一个RequestInterceptor类,这是一个请求拦截器,里面的代码如下:


package com.llw.network.interceptor;
import com.llw.network.INetworkRequiredInfo;
import com.llw.network.utils.DateUtil;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
 * 请求拦截器
 * @author llw
 */
public class RequestInterceptor implements Interceptor {
    /**
     * 网络请求信息
     */
    private INetworkRequiredInfo iNetworkRequiredInfo;
    public RequestInterceptor(INetworkRequiredInfo iNetworkRequiredInfo){
        this.iNetworkRequiredInfo = iNetworkRequiredInfo;
    }
    /**
     * 拦截
     */
    @Override
    public Response intercept(Chain chain) throws IOException {
        String nowDateTime = DateUtil.getNowDateTime();
        //构建器
        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.llw.network.interceptor;
import com.llw.network.utils.KLog;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Response;
/**
 * 返回拦截器(响应拦截器)
 *
 * @author llw
 */
public class ResponseInterceptor implements Interceptor {
    private static final String TAG = "ResponseInterceptor";
    /**
     * 拦截
     */
    @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;
    }
}


这里面也很简单就是可记录当前这个接口的请求耗费时长,这个时间在网速正常的情况下自然是越短越好,当然这个就是后期的网络方面的优化了。


那么这两个拦截器有了,下面就他们放到OkHttp中,打开NetworkApi


20210112101114947.png


现在这个拦截器就会在请求网络时生效了。


六、自定义Observer


 在上面的代码中完成了对OkHttp的优化,OkHttp负责网络访问,使用Retrofit发起网络请求,使用RxJava处理返回结果,在上面只是做了线程的切换和错误码的处理,所以还需要的返回做一个处理,下面在com.llw.network下新建一个observer包,该包下新建一个BaseObserver的抽象类,里面代码如下:


package com.llw.network.observer;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
/**
 * 自定义Observer
 * @author llw
 */
public abstract class BaseObserver<T> implements Observer<T> {
    //开始
    @Override
    public void onSubscribe(Disposable d) {
    }
    //继续
    @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的所有方法,只用了两个,onNext和onError。


七、配置网络环境


 在日常开发中,常常会有多个开发环境,比如测试环境、正式环境。他们的区别其实就是前面的地址不同而已,后面的参数都是一样的。举个例子,加入你是Android开发,你面对了两个后台开发,在项目初期后台的服务器都是在自己的电脑上,因此你需要配置他们电脑的ip地址才能去访问他们所写的接口API,普通做法就是对接A的接口时使用A的ip,对接B的接口时使用B的ip,你可能会觉得不就是修改一行代码的事情吗,不麻烦,那假如让你打包出来测试呢?因为一个APP的出现不能不去测试,开发的话要是能信,还要测试干什么?这是我一个测试朋友说的,一时间我竟无法反驳。因此为了避免不断需要我们去根据不同的网络环境打包测试,就体现出来这个网络环境的重要性了,说了这么多也是一个建议,当然你是否采纳取决于自己,起码我是这么做的。


在com.llw.network下新建一个environment包。包下新建一个NetworkEnvironmentActivity,然后先不管它,因为还需要配置一些东西才行。在res下创建一个layout文件下,在这个文件夹下创建一个activity_network_environment.xml文件,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<layout>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</layout>

20210112105422966.png


然后在values下新建一个network_array.xml文件,用于网络配置数组参数,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="environmentName">
        <item>正式</item>
        <item>测试</item>
    </string-array>
    <string-array name="environmentValues">
        <item>1</item>
        <item>2</item>
    </string-array>
</resources>


这里我配置了两个环境,一个正式一个测试,实际开发中可能会更多,可根据实际情况进行增减。


然后在drawable下新建一个ic_network_settings.xml,这是一个图标,用路径写的。


<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="56dp"
    android:height="56dp"
    android:viewportWidth="56"
    android:viewportHeight="56">
    <path
        android:fillColor="#FF000000"
        android:pathData="M8,41.08V2c0,-0.553 -0.448,-1 -1,-1S6,1.447 6,2v39.08C2.613,41.568 0,44.481 0,48c0,3.859 3.14,7 7,7s7,-3.141 7,-7C14,44.481 11.387,41.568 8,41.08zM7,53c-2.757,0 -5,-2.243 -5,-5s2.243,-5 5,-5s5,2.243 5,5S9.757,53 7,53z"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M29,20.695V2c0,-0.553 -0.448,-1 -1,-1s-1,0.447 -1,1v18.632c-3.602,0.396 -6.414,3.456 -6.414,7.161s2.812,6.765 6.414,7.161V54c0,0.553 0.448,1 1,1s1,-0.447 1,-1V34.891c3.4,-0.577 6,-3.536 6,-7.098S32.4,21.272 29,20.695zM27.793,33c-2.871,0 -5.207,-2.336 -5.207,-5.207s2.335,-5.207 5.207,-5.207S33,24.922 33,27.793S30.664,33 27.793,33z"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M56,8c0,-3.859 -3.14,-7 -7,-7s-7,3.141 -7,7c0,3.519 2.613,6.432 6,6.92V54c0,0.553 0.448,1 1,1s1,-0.447 1,-1V14.92C53.387,14.432 56,11.519 56,8zM49,13c-2.757,0 -5,-2.243 -5,-5s2.243,-5 5,-5s5,2.243 5,5S51.757,13 49,13z"/>
</vector>


然后在strings.xml中增加一个值。


  <string name="network_environment_setting">网络环境设置</string>
    <string name="network_change_tip">您已经更改了网络环境,在您退出当前页面的时候APP将会重启切换环境!</string>

20210112144910918.png


下面对网络进行一些配置,在Android9.0及以后版本,默认使用Https访问网络,这导致了不能使用Http,因此要配置允许使用Http,在res下新建一个xml文件夹,在这个文件夹下新建一个network_security_config.xml,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>


然后在这个xml文件夹下再建一个environment_preference.xml文件,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <ListPreference
        android:defaultValue="1"
        android:entries="@array/environmentName"
        android:entryValues="@array/environmentValues"
        android:icon="@drawable/ic_network_settings"
        android:key="network_environment"
        android:summary="请您选择您需要使用的网络环境,选择完后会重启APP生效"
        android:title="设置网络环境" />
</PreferenceScreen>


这里默认的值为1,也就是正式环境。现在关于xml就配置完了,该进入这个NetworkEnvironmentActivity里面去写代码了,首先继承AppCompatActivity,重写父类的onCreate方法,然后设置布局。现在看起来这个Activity就和常规的Activity差不多了。


package com.llw.network.environment;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.llw.network.R;
/**
 * 设置网络环境Activity
 * @author llw
 */
public class NetworkEnvironmentActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network_environment);
    }
}


当然这还是刚开始,下面一步一步完善这个Activity,首先增加两个成员变量。


  //网络环境
    public static final String NETWORK_ENVIRONMENT = "network_environment";
    //当前网络环境
    private static String mCurrentNetworkEnvironment = "";


下面会用到缓存,键就是NETWORK_ENVIRONMENT,常规这种键都是大写的。


先在NetworkEnvironmentActivity中创建一个内部类MyPreferenceFragment继承PreferenceFragmentCompat并实现Preference.OnPreferenceChangeListener。


  /**
     * 内部缓存变化监听类
     */
    public static class MyPreferenceFragment extends PreferenceFragmentCompat
            implements Preference.OnPreferenceChangeListener {
        //创建缓存
        @Override
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            //这个相当于Activity的setContentView,从资源文件中添Preferences ,选择的值将会自动保存到SharePreferences
            addPreferencesFromResource(R.xml.environment_preference);
            //设置缓存变化监听 , 通过键来设置监听
            findPreference(NETWORK_ENVIRONMENT).setOnPreferenceChangeListener(this);
        }
        //缓存变化
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            if (!mCurrentNetworkEnvironment.equalsIgnoreCase(String.valueOf(newValue))) {
                //当前值与缓存中不一致时,说明切换了网络,这时提醒一下
                Toast.makeText(getContext(), R.string.network_change_tip, Toast.LENGTH_SHORT).show();
            }
            return true;
        }
    }


通过这个类,定义xml文件中,的操作方式,ListPreferenc这个控件中,默认是正式环境,当你修改之后,会将你修改的值存到缓存中,然后会进入这个缓存变化的回调中,此时提醒一下开发者,当然此时只是更换了缓存信息而已,此时应该退出当前应用,再重启,重启时读取缓存中的值,根据这个值去使用不同的环境,那么为了让这个过程显得不那么突兀,可以在页面返回的监听中做判断。


  /**
     * 页面返回
     */
    @Override
    public void onBackPressed() {
        //获取缓存对象
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        //通过键获取缓存则,没有则使用默认值
        String value = preferences.getString(NETWORK_ENVIRONMENT, "1");
        if (!mCurrentNetworkEnvironment.equalsIgnoreCase(value)) {
            //不一致.说明有修改,从操作系统中结束掉当前程序的进程
            android.os.Process.killProcess(android.os.Process.myPid());
        } else {
            //一致  没有修改则关闭当前页面
            finish();
        }
    }


onBackPressed可以监听页面的返回按钮的点击事件,我在这里判断是否有修改网络环境,因为缓存值修改就意味着网络环境修改,如果已经修改过则在返回页面时结束当前程序的进程,如果没有修改只是关闭当前的Activity而已。


而假如要在启动App时判断当前环境是否为正式环境时,还是需要去缓存来对比的,因此可以再写一个方法来判断当前是否为正式环境,方法如下:


  /**
     * 是否为正式环境
     */
    public static boolean isFormalEnvironment(Application application) {
        //获取当前应用的缓存
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(application);
        String networkEnvironment = preferences.getString(NETWORK_ENVIRONMENT, "1");
        return "1".equalsIgnoreCase(networkEnvironment);
    }


因为当前只有正式和测试两种情况,因此可以用boolean就可以,多种情况你可以返回一个key的结果,每个key对应不同的网络,自己区分好就行。


最后在onCreate中配置Fragment的replace


  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network_environment);
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.content, new MyPreferenceFragment())
                .commit();
        //获取默认缓存
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        //如果没有值就默认为 “1”  在这里 1 表示正式环境
        mCurrentNetworkEnvironment = preferences.getString(NETWORK_ENVIRONMENT,"1");
    }


这样这个Activity就写完了,别忘了在AndroidManifest.xml中配置


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.llw.network">
    <uses-permission android:name="android.permission.INTERNET" />
    <application android:networkSecurityConfig="@xml/network_security_config">
        <activity
            android:name=".environment.NetworkEnvironmentActivity"
            android:label="@string/network_environment_setting"
            android:screenOrientation="portrait" />
    </application>
</manifest>


但是网络配置这一步还没有结束,之前在NetworkApi中配置了mBaseUrl这个成员变量,还记得吗?之前可是一直没有赋值的,我相信你已经猜到了,更改网络环境,实际上就是在更改mBaseUrl的值,只不过更改之前要根据缓存判断一下。


那么在NetworkApi中再增加一个成员变量


  //是否为正式环境
    private static boolean isFormal = true;


下面在NetworkApi中新增一个初始化的方法,代码如下:


  /**
     * 初始化
     */
  public static void init(INetworkRequiredInfo networkRequiredInfo) {
        iNetworkRequiredInfo = networkRequiredInfo;
        //当初始化这个NetworkApi时,会判断当前App的网络环境
        isFormal = NetworkEnvironmentActivity.isFormalEnvironment(networkRequiredInfo.getApplicationContext());
        if (isFormal) {
            //正式环境
            mBaseUrl = "http://service.picasso.adesk.com";
        } else {
            //测试环境
            mBaseUrl = "https://cn.bing.com";
        }
    }


同样还要创建一个Service的实例方法,代码如下:


  /**
     * 创建serviceClass的实例
     */
    public static <T> T createService(Class<T> serviceClass) {
        return getRetrofit(serviceClass).create(serviceClass);
    }


OK,到目前为止,NetworkApi终于是写完了。


下面这个该进入使用环节了,回到app模块。


八、使用网络框架


20210112154234519.png


目前app模块下只有这一个孤零零的MainActivity。首先在app下的com.llw.network下新建一个application包,(在实际开发中尽量要避免包名重复的情况),在这个包下创建一个NetworkRequiredInfo类,然后实现network模块下的INetworkRequiredInfo接口。


20210112154905806.png


你会发现,这个报红,这时因为你没有添加network模块的依赖,那么有三种方式可以添加,


1. 添加网络模块依赖


① 当前项目添加


第一种:

鼠标点击这个报红处,然后使用Alt+Enter,会出现如下弹窗,点击第一项Add dependency on module ‘network’,意思就是添加network模块的依赖。

20210112155147291.png


点击之后,等待即可


20210112155419132.png


然后发现报错了,这个报错是为什么呢?


20210112155525676.png


你打开app的build.gradle就知道了,如下图所示:


20210112155700153.png


我这里解释一下是为什么,随着Gradle版本的更新,以前的一些使用方式就弃用了,比如这个compile就过时了,因此在高版本中可以替换为implementation和api。那么将compile替换成为implementation之后点击右上角的Sync Now进行同步。


20210112160216709.png


这样就编译成功了,上面通过Alt + Enter的方式虽然不用我们改动,但是这个内部机制还是低版本的,它做的无非就是加一句话而已,那么我们也可以自己来加不是吗?


第二种:


打开app的build.gradle,在dependencies{}闭包下添加如下依赖:


  //依赖网络模块
    implementation project(path: ':network')


注意这个格式,所有的标点符号都是因为英文的,network对应你的模块的名字,它前面还有一个英文冒号。


20210112160646823.png


然后就点击Sync Now同步就可以了。


第三种:


通过File→Project Structure…


20210112161318392.png


或者点击这个图标

20210112161500829.png


都会进入如下页面


20210112161520343.png


然后通过下图这四步操作即可添加这个模块依赖。


20210112161644255.png


然后勾选上,下面的下拉框中可以选择类型。


20210112161817260.png


可以看到高版本AS中已经没有compile的选项了,


20210112161902962.png


点击OK即可。


20210112162012923.png


再点击OK,然后你打开app的build.gradle查看,里面一定多了一个依赖,如下图所示:


2021011216210064.png


这种方式可以把错误和修改的可能性降到最低,推荐使用。


② 其他项目或新项目添加


同样你假如要在一个新的项目中使用这个network模块也可以这么做。比如我打开我之前写的关于高德地图的项目Demo。


20210112162442921.png


里面没有网络模块,因此需要先导入模块才行,通过File→New→Import Module…


20210112162639590.png


点击后出现


20210112162811265.png


找到之前的模块所在路径。


20210112162956643.png


然后点击Finish


2021011216303619.png


然后你就可以通过上面的第三步进行添加依赖了。


新版的AS导入模块其实也差不多,下面的导入模块的图是AS4.2.1版本中导入


e738cd870a98473aabe1f38e85872ed5.png

0d8132b7360741999f6ad07a3e0d692b.png


之前很多人说没有找到导入模块的地方,其实是观察不够仔细,注意上图标注的地方。


171a809bfd54488192f8fbd6c0cdfd42.png


点击OK。


248985ca26ed4723962e4f5ff1da7adb.png


点击Finish完成导入。


这对于不熟悉的朋友来说还是不错的,因为有时候他们配置项目时会出现各种各样的问题,五花八门,最终就是表现为报错了,然后不知道为什么报错,因此详细一点也没有错。


OK下面进入当前项目的使用


2. 使用网络模块


上面由一个NetworkRequiredInfo引发出这么多内容,但是我觉得是有必要讲一下的,也是做一个笔记吧,那么回到这个NetworkRequiredInfo中,


20210112164052327.png


你可以看到现在你就可以导包,然后使用这个INetworkRequiredInfo,导包也是使用Alt+Enter快捷键,如果你这个接口是唯一的,则会直接导包,如果不是唯一的则会出现一个弹窗供你选择要导入的包,所属,还记得上面使用Observer的时候吗?它就是不唯一的,androidx.lifecyle下有,io.reactivex下也有,你要是导错了包,那么后面怎么搞都是有问题的,这都是细节,需要注意才行。


实现里面的方法,最终里面的代码如下:


package com.llw.network.application;
import android.app.Application;
import com.llw.network.BuildConfig;
import com.llw.network.INetworkRequiredInfo;
/**
 * 网络访问信息
 * @author llw
 */
public class NetworkRequiredInfo implements INetworkRequiredInfo {
    private 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;
    }
}



相信应该很好理解,然后在这个application包下再创建一个MyApplication类,继承Application。重写onCreate方法,在里面完成对NetworkApi和NetworkRequiredInfo的初始化配置,里面的代码如下:


package com.llw.network.application;
import android.app.Application;
import com.llw.network.NetworkApi;
/**
 * 自定义Application
 * @author llw
 */
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //初始化
        NetworkApi.init(new NetworkRequiredInfo(this));
    }
}


然后打开AndroidManifest.xml中进行配置


20210112205608678.png


在这里配置自定义的Application。


下面就要来显示数据了,


http://service.picasso.adesk.com/v1/vertical/vertical?limit=30&skip=180&adult=false&first=0&order=hot


可以在浏览器数据下面的这个地址,然后会返回如下所示JSON数据:


5be49941c894490aa38be967758d0e48.png


利用这些数据可以生成一个实体Bean。


在app的com.llw.network下新建一个bean包,里面新建一个GankResponse类,里面的代码如下:


public class WallPaperResponse {
    private String msg;
    private ResBean res;
    private int code;
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public ResBean getRes() {
        return res;
    }
    public void setRes(ResBean res) {
        this.res = res;
    }
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public static class ResBean {
        private List<VerticalBean> vertical;
        public List<VerticalBean> getVertical() {
            return vertical;
        }
        public void setVertical(List<VerticalBean> vertical) {
            this.vertical = vertical;
        }
        public static class VerticalBean {
            private String preview;
            private String thumb;
            private String img;
            private int views;
            private String rule;
            private int ncos;
            private int rank;
            private String source_type;
            private String wp;
            private boolean xr;
            private boolean cr;
            private int favs;
            private double atime;
            private String id;
            private String store;
            private String desc;
            private List<String> cid;
            private List<?> tag;
            private List<?> url;
            public String getPreview() {
                return preview;
            }
            public void setPreview(String preview) {
                this.preview = preview;
            }
            public String getThumb() {
                return thumb;
            }
            public void setThumb(String thumb) {
                this.thumb = thumb;
            }
            public String getImg() {
                return img;
            }
            public void setImg(String img) {
                this.img = img;
            }
            public int getViews() {
                return views;
            }
            public void setViews(int views) {
                this.views = views;
            }
            public String getRule() {
                return rule;
            }
            public void setRule(String rule) {
                this.rule = rule;
            }
            public int getNcos() {
                return ncos;
            }
            public void setNcos(int ncos) {
                this.ncos = ncos;
            }
            public int getRank() {
                return rank;
            }
            public void setRank(int rank) {
                this.rank = rank;
            }
            public String getSource_type() {
                return source_type;
            }
            public void setSource_type(String source_type) {
                this.source_type = source_type;
            }
            public String getWp() {
                return wp;
            }
            public void setWp(String wp) {
                this.wp = wp;
            }
            public boolean isXr() {
                return xr;
            }
            public void setXr(boolean xr) {
                this.xr = xr;
            }
            public boolean isCr() {
                return cr;
            }
            public void setCr(boolean cr) {
                this.cr = cr;
            }
            public int getFavs() {
                return favs;
            }
            public void setFavs(int favs) {
                this.favs = favs;
            }
            public double getAtime() {
                return atime;
            }
            public void setAtime(double atime) {
                this.atime = atime;
            }
            public String getId() {
                return id;
            }
            public void setId(String id) {
                this.id = id;
            }
            public String getStore() {
                return store;
            }
            public void setStore(String store) {
                this.store = store;
            }
            public String getDesc() {
                return desc;
            }
            public void setDesc(String desc) {
                this.desc = desc;
            }
            public List<String> getCid() {
                return cid;
            }
            public void setCid(List<String> cid) {
                this.cid = cid;
            }
            public List<?> getTag() {
                return tag;
            }
            public void setTag(List<?> tag) {
                this.tag = tag;
            }
            public List<?> getUrl() {
                return url;
            }
            public void setUrl(List<?> url) {
                this.url = url;
            }
        }
    }
}


下面在app的com.llw.network下新建一个api包,包下新建一个ApiService的接口,里面的代码如下:


/**
 * ApiService接口 统一管理应用所有的接口
 * @author llw
 */
public interface ApiService {
    /**
     * 获取热门壁纸列表
     */
    @GET("/v1/vertical/vertical?limit=30&skip=180&adult=false&first=0&order=hot")
    Observable<WallPaperResponse> getWallPaper();
}


现在基本上就配置完毕了,下面就来简单使用一下,修改一个activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />
</LinearLayout>


里面也就一个图片控件而已,然后回到MainActivity中。


/**
 * @author llw
 */
public class MainActivity extends AppCompatActivity {
    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
        //访问网络
        requestNetwork();
    }
    /**
     * 访问网络
     */
    @SuppressLint("CheckResult")
    private void requestNetwork() {
        NetworkApi.createService(ApiService.class)
                .getWallPaper()
                .compose(NetworkApi.applySchedulers(new BaseObserver<WallPaperResponse>() {
                    @Override
                    public void onSuccess(WallPaperResponse wallPaperResponse) {
                        List<WallPaperResponse.ResBean.VerticalBean> vertical = wallPaperResponse.getRes().getVertical();
                        if (vertical != null && vertical.size() > 0) {
                            String imgUrl = vertical.get(0).getImg();
                            Glide.with(MainActivity.this).load(imgUrl).into(imageView);
                        } else {
                            Toast.makeText(MainActivity.this, "数据为空", Toast.LENGTH_SHORT).show();
                        }
                    }
                    @Override
                    public void onFailure(Throwable e) {
                        KLog.e("MainActivity", e.toString());
                        Toast.makeText(MainActivity.this, "访问失败", Toast.LENGTH_SHORT).show();
                    }
                }));
    }


然后运行一下。


28a0e70177554df391a721eed02e4b1c.png


图片就加载出来了。


可以查看run里面的日志


cb9e2f7431b24f87972166e184daf441.png


还记得这是在那里添加的吗,没错就是请求拦截器里面。也别忘了在返回拦截器中打印了请求时间。下面来看一下:


4162ebcf34734f8e934b2af51b00ff97.png


现在我们知道这个接口从请求到返回耗时459毫秒,而且通过这个自定义的日志打印工具类,你还能知道是在那里打印的日志,可以让你追根溯源。现在我们很明显是在debug模式下的,怎么证明呢?


还记得配置OkHttp这里吗?


20210112175410531.png


那么BODY里面的信息也可以在Run下面找到,如下图所示:


8d587fb4324842dd87d8d46cf6eec1ec.png


现在是不是还差了一步呢?那就是设置网络了,还记得network模块中的NetworkEnvironmentActivity吗?该它出马了。


3. 切换网络环境

 切换网络通常是采用特殊的方式,否则岂不是谁都知道了,还记得Android手机开启开发者模式这个方法吗?下面这个操作也是类似的。


可以点击这个图片多下然后进入到NetworkEnvironmentActivity中,现在进入MainActivity中,添加如下成员变量


  //点击6次
    private final int CLICK_NUM = 6;
    //点击时间间隔5秒
    private final int CLICK_INTERVAL_TIME = 5000;
    //上一次的点击时间
    private long lastClickTime = 0;
    //记录点击次数
    private int clickNum = 0;


添加连续点击的方法


  /**
     * 连续6次点击
     */
    public void sixClick() {
        //点击的间隔时间不能超过5秒
        long currentClickTime = SystemClock.uptimeMillis();
        if (currentClickTime - lastClickTime <= CLICK_INTERVAL_TIME || lastClickTime == 0) {
            lastClickTime = currentClickTime;
            clickNum = clickNum + 1;
        } else {
            //超过5秒的间隔
            //重新计数 从1开始
            clickNum = 1;
            lastClickTime = 0;
            return;
        }
        if (clickNum == CLICK_NUM) {
            //重新计数
            clickNum = 0;
            lastClickTime = 0;
            /*实现点击多次后的事件*/
            Toast.makeText(MainActivity.this, "设置网络环境", Toast.LENGTH_SHORT).show();
            startActivity(new Intent(MainActivity.this, NetworkEnvironmentActivity.class));
        }
    }


然后在onCreate中通过点击imageView调用这个sixClick()方法。


20210112210342759.png


下面运行一下,这次我是在真机上运行的。


4a64281538a5475e968617dcf24ebd38.gif


运行的效果就证明网络切换成功了,因为实际上我使用了两个完全不同访问地址,因此当切换到测试的地址之后,出现访问失败的提示,这个错误就是404。怎么证明了,还记得我在错误返回的时候打印的日志吗?


20210112212056274.png


OK,再切换到之前的网络看看。


7f2a34b1f0e5487cafe7be343e60e530.gif


这样就可以了,那么这篇文章就到这里了。


九、源码


源码地址:NetworkFrameWorkDemo


总结


 上述的内容,并不全是我自己的想法,网络上学到过一些,再加上平时开发中的积累,才写出来的,希望能对您有所帮助,当然网络访问框架通常并不是独立使用的,而是与框架组合起来使用,后续我可能会写,也可能不会。山高水长,后会有期~


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
9天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
35 1
|
16天前
|
网络协议 Ubuntu 前端开发
好好的容器突然起不来,经定位是容器内无法访问外网了?测试又说没改网络配置,该如何定位网络问题
本文记录了一次解决前端应用集成到主应用后出现502错误的问题。通过与测试人员的沟通,最终发现是DNS配置问题导致的。文章详细描述了问题的背景、沟通过程、解决方案,并总结了相关知识点和经验教训,帮助读者学习如何分析和定位网络问题。
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
277 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
69 8
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
3月前
|
JSON 安全 网络协议
【Azure Policy】添加策略用于审计Azure 网络安全组(NSG)规则 -- 只能特定的IP地址允许3389/22端口访问
为了确保Azure虚拟机资源的安全管理,只有指定IP地址才能通过RDP/SSH远程访问。解决方案包括使用Azure Policy服务扫描所有网络安全组(NSG),检查入站规则中的3389和22端口,并验证源地址是否在允许的IP列表中。不符合条件的NSG规则将被标记为非合规。通过编写特定的Policy Rule并定义允许的IP地址参数,实现集中管控和合规性检查。
|
1天前
|
存储 安全 算法
网络安全与信息安全:漏洞、加密技术及安全意识的重要性
如今的网络环境中,网络安全威胁日益严峻,面对此类问题,除了提升相关硬件的安全性、树立法律法规及行业准则,增强网民的网络安全意识的重要性也逐渐凸显。本文梳理了2000年以来有关网络安全意识的研究,综述范围为中国知网中篇名为“网络安全意识”的期刊、硕博论文、会议论文、报纸。网络安全意识的内涵是在“网络安全”“网络安全风险”等相关概念的发展中逐渐明确并丰富起来的,但到目前为止并未出现清晰的概念界定。此领域内的实证研究主要针对网络安全意识现状与问题,其研究对象主要是青少年。网络安全意识教育方面,很多学者总结了国外的成熟经验,但在具体运用上仍缺乏考虑我国的实际状况。 内容目录: 1 网络安全意识的相关
|
2天前
|
SQL 安全 算法
网络安全与信息安全:漏洞、加密技术与安全意识的交织
【10月更文挑战第28天】在数字时代的浪潮中,网络安全与信息安全成为保护个人隐私和企业资产的重要盾牌。本文将深入探讨网络安全中的常见漏洞,介绍加密技术的基本概念及其在保护数据中的应用,并强调提高安全意识的重要性。通过分析具体案例和提供实用的防护措施,旨在为读者提供一个全面的网络安全知识框架,以应对日益复杂的网络威胁。
17 4
|
1天前
|
存储 安全 网络安全
云计算与网络安全:探索云服务中的信息安全技术
【10月更文挑战第29天】在数字化时代的浪潮中,云计算作为一种革命性的技术,正日益成为企业和个人数据存储与处理的首选方案。然而,随之而来的网络安全问题也愈发复杂和严峻。本文将深入探讨云计算服务中的网络安全挑战,并分析如何通过先进的信息安全技术来加强保护措施。我们将从云服务的基础知识出发,逐步深入到网络安全的各个方面,包括数据加密、身份验证、访问控制等关键技术的应用。通过理论与实践的结合,旨在为读者提供一套全面而实用的云计算安全指南。