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

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


前言

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


正文

  创建一个名为NetworkFrameWorkDemo的项目。

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

选择Android Library,点击Next

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

创建好之后如下图所示:

下面可以先不管这个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

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

六、自定义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>

然后在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>

下面对网络进行一些配置,在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 = "https://gank.io";
  • } else {
  • //测试环境
  • mBaseUrl = "https://cn.bing.com";
  • }
  • }

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

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

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

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

八、使用网络框架

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

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

1. 添加网络模块依赖

① 当前项目添加

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

点击之后,等待即可

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

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

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

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

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

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

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

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

第三种:

通过File→Project Structure…

或者点击这个图标

都会进入如下页面

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

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

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

点击OK即可。

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

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

② 其他项目或新项目添加

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

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

点击后出现

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

然后点击Finish

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

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

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

2. 使用网络模块

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

你可以看到现在你就可以导包,然后使用这个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中进行配置

在这里配置自定义的Application。

下面就要来显示数据了,

  • https://gank.io/api/v2/data/category/Girl/type/Girl/page/1/count/10

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

  • /*
  • * 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
  • * {"data":[{"_id":"5e959250808d6d2fe6b56eda","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-25 08:00:00","desc":"\\u4e0e\\u5176\\u5b89\\u6170\\u81ea\\u5df1\\u5e73\\u51e1\\u53ef\\u8d35\\uff0c\\n\\u4e0d\\u5982\\u62fc\\u5c3d\\u5168\\u529b\\u6d3b\\u5f97\\u6f02\\u4eae\\u3002 \\u200b \\u200b\\u200b\\u200b\\u200b","images":["http://gank.io/images/f4f6d68bf30147e1bdd4ddbc6ad7c2a2"],"likeCounts":6,"publishedAt":"2020-05-25 08:00:00","stars":1,"title":"\\u7b2c96\\u671f","type":"Girl","url":"http://gank.io/images/f4f6d68bf30147e1bdd4ddbc6ad7c2a2","views":11898},{"_id":"5e95923f808d6d2fe6b56ed8","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-24 08:00:00","desc":"\\u8fd9\\u4e16\\u754c\\u603b\\u6709\\u4eba\\u5728\\u7b28\\u62d9\\u5730\\u7231\\u7740\\u4f60\\uff0c\\u60f3\\u628a\\u5168\\u90e8\\u7684\\u6e29\\u67d4\\u90fd\\u7ed9\\u4f60\\u3002 \\u200b\\u200b\\u200b\\u200b","images":["http://gank.io/images/dc75cbde1d98448183e2f9514b4d1320"],"likeCounts":3,"publishedAt":"2020-05-24 08:00:00","stars":1,"title":"\\u7b2c95\\u671f","type":"Girl","url":"http://gank.io/images/dc75cbde1d98448183e2f9514b4d1320","views":5085},{"_id":"5e95922e808d6d2fe6b56ed6","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-23 08:00:00","desc":"\\u966a\\u4f34\\u672c\\u6765\\u5c31\\u662f\\u8fd9\\u4e16\\u754c\\u4e0a\\u6700\\u4e86\\u4e0d\\u8d77\\u7684\\u5b89\\u6170\\u200b\\u3002","images":["http://gank.io/images/6b2efa591564475fb8bc32291fb0007c"],"likeCounts":1,"publishedAt":"2020-05-23 08:00:00","stars":1,"title":"\\u7b2c94\\u671f","type":"Girl","url":"http://gank.io/images/6b2efa591564475fb8bc32291fb0007c","views":4480},{"_id":"5e959200ee6ba981da2af34d","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-22 08:00:00","desc":"\\u957f\\u4e0d\\u8fc7\\u6267\\u5ff5\\uff0c\\u77ed\\u4e0d\\u8fc7\\u5584\\u53d8\\u3002","images":["http://gank.io/images/d6bba8cf5b8c40f9ad229844475e9149"],"likeCounts":2,"publishedAt":"2020-05-22 08:00:00","stars":1,"title":"\\u7b2c93\\u671f","type":"Girl","url":"http://gank.io/images/d6bba8cf5b8c40f9ad229844475e9149","views":3589},{"_id":"5e9591dcee6ba981da2af34b","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-21 08:00:00","desc":"\\u65e0\\u8bba\\u591a\\u4e48\\u8270\\u96be\\u7684\\u73b0\\u5728\\uff0c\\u7ec8\\u4f1a\\u7ffb\\u7bc7\\u3002\\n\\u671d\\u672a\\u6765\\u5927\\u6b65\\u5411\\u524d\\u5427\\uff0c\\u522b\\u4e27\\uff0c\\u522b\\u6b62\\u6b65\\u3002","images":["http://gank.io/images/9fa43020cf724c69842eec3e13f6d21c"],"likeCounts":4,"publishedAt":"2020-05-21 08:00:00","stars":1,"title":"\\u7b2c92\\u671f","type":"Girl","url":"http://gank.io/images/9fa43020cf724c69842eec3e13f6d21c","views":2434},{"_id":"5e9591c60bd5529b54e712af","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-20 08:00:00","desc":"\\u5e0c\\u671b\\u4e0b\\u4e00\\u6b21\\uff0c\\u80fd\\u559c\\u6b22\\u4e0a\\u4e00\\u4e2a\\u4e5f\\u559c\\u6b22\\u81ea\\u5df1\\u7684\\u4eba \\u200b\\u200b\\u200b\\u200b\\u3002","images":["http://gank.io/images/d237f507bf1946d2b0976e581f8aab9b"],"likeCounts":0,"publishedAt":"2020-05-20 08:00:00","stars":1,"title":"\\u7b2c91\\u671f","type":"Girl","url":"http://gank.io/images/d237f507bf1946d2b0976e581f8aab9b","views":2024},{"_id":"5e9591b6808d6d2fe6b56ed5","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-19 08:00:00","desc":"\\u8fd9\\u4e2a\\u4e16\\u754c\\u4e0a\\uff0c\\n\\u6709\\u4e9b\\u4eba\\u6709\\u591a\\u51b7\\u6f20\\uff0c\\n\\u6709\\u4e9b\\u4eba\\u5c31\\u6709\\u591a\\u6e29\\u6696\\uff0c\\n\\u5e0c\\u671b\\u4f60\\u603b\\u4f1a\\u9047\\u5230\\u90a3\\u4e9b\\u6e29\\u6696\\u5bf9\\u4f60\\u7684\\u4eba\\u3002","images":["http://gank.io/images/25d3e3db2c1248bb917c09dc4f50a46f"],"likeCounts":1,"publishedAt":"2020-05-19 08:00:00","stars":1,"title":"\\u7b2c90\\u671f","type":"Girl","url":"http://gank.io/images/25d3e3db2c1248bb917c09dc4f50a46f","views":2843},{"_id":"5e9591a2ee6ba981da2af34a","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-18 08:00:00","desc":"\\u4ee5\\u524d\\u5bf9\\u4f60\\u7684\\u559c\\u6b22\\uff0c\\n\\u662f\\u89c1\\u4f60\\uff0c\\u5ff5\\u4f60\\uff0c\\u966a\\u4f34\\u4f60\\u3002\\n\\u73b0\\u5728\\u5bf9\\u4f60\\u7684\\u559c\\u6b22\\uff0c\\n\\u662f\\u4e0d\\u95ee\\uff0c\\u4e0d\\u770b\\uff0c\\u4e0d\\u53e8\\u6270\\u3002","images":["http://gank.io/images/19c99c447e0a40a6b3ff89951957cfb1"],"likeCounts":0,"publishedAt":"2020-05-18 08:00:00","stars":1,"title":"\\u7b2c89\\u671f","type":"Girl","url":"http://gank.io/images/19c99c447e0a40a6b3ff89951957cfb1","views":1926},{"_id":"5e959197808d6d2fe6b56ed4","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-17 08:00:00","desc":"\\u53ea\\u8981\\u7ed3\\u5c40\\u662f\\u559c\\u5267\\uff0c\\u8fc7\\u7a0b\\u4f60\\u8981\\u6211\\u600e\\u4e48\\u54ed\\u90fd\\u884c\\uff0c\\u5e78\\u798f\\u53ef\\u4ee5\\u6765\\u7684\\u6162\\u4e00\\u4e9b\\uff0c\\n\\u53ea\\u8981\\u5b83\\u662f\\u771f\\u7684\\uff0c\\u5982\\u679c\\u6700\\u540e\\u80fd\\u5728\\u4e00\\u8d77\\uff0c\\u665a\\u70b9\\u6211\\u771f\\u7684\\u65e0\\u6240\\u8c13\\u7684\\u3002","images":["http://gank.io/images/f0c192e3e335400db8a709a07a891b2e"],"likeCounts":0,"publishedAt":"2020-05-17 08:00:00","stars":1,"title":"\\u7b2c88\\u671f","type":"Girl","url":"http://gank.io/images/f0c192e3e335400db8a709a07a891b2e","views":2746},{"_id":"5e95915f808d6d2fe6b56ed3","author":"\\u9e22\\u5a9b","category":"Girl","createdAt":"2020-05-16 08:00:00","desc":"\\u82e5\\u4e0d\\u662f\\u60c5\\u6df1\\u4f3c\\u6d77\\uff0c\\u601d\\u5ff5\\u53c8\\u600e\\u4f1a\\u6cdb\\u6ee5\\u6210\\u707e\\u3002","images":["http://gank.io/images/bdb35e4b3c0045c799cc7a494a3db3e0"],"likeCounts":3,"publishedAt":"2020-05-16 08:00:00","stars":1,"title":"\\u7b2c87\\u671f","type":"Girl","url":"http://gank.io/images/bdb35e4b3c0045c799cc7a494a3db3e0","views":3984}],"page":1,"page_count":10,"status":100,"total_counts":96}
  • */

利用这些数据可以生成一个实体Bean。
在app的com.llw.network下新建一个bean包,里面新建一个GankResponse类,里面的代码如下:

  • package com.llw.network.bean;
  • import java.util.List;
  • /**
  • * Gank返回数据
  • * @author llw
  • */
  • public class GankResponse {
  • private int page;
  • private int page_count;
  • private int status;
  • private int total_counts;
  • private List<DataBean> data;
  • public int getPage() {
  • return page;
  • }
  • public void setPage(int page) {
  • this.page = page;
  • }
  • public int getPage_count() {
  • return page_count;
  • }
  • public void setPage_count(int page_count) {
  • this.page_count = page_count;
  • }
  • public int getStatus() {
  • return status;
  • }
  • public void setStatus(int status) {
  • this.status = status;
  • }
  • public int getTotal_counts() {
  • return total_counts;
  • }
  • public void setTotal_counts(int total_counts) {
  • this.total_counts = total_counts;
  • }
  • public List<DataBean> getData() {
  • return data;
  • }
  • public void setData(List<DataBean> data) {
  • this.data = data;
  • }
  • public static class DataBean {
  • /**
  • * _id : 5e959250808d6d2fe6b56eda
  • * author : 鸢媛
  • * category : Girl
  • * createdAt : 2020-05-25 08:00:00
  • * desc : 与其安慰自己平凡可贵,
  • 不如拼尽全力活得漂亮。 ​ ​​​​
  • * images : ["http://gank.io/images/f4f6d68bf30147e1bdd4ddbc6ad7c2a2"]
  • * likeCounts : 6
  • * publishedAt : 2020-05-25 08:00:00
  • * stars : 1
  • * title : 第96期
  • * type : Girl
  • * url : http://gank.io/images/f4f6d68bf30147e1bdd4ddbc6ad7c2a2
  • * views : 11898
  • */
  • private String _id;
  • private String author;
  • private String category;
  • private String createdAt;
  • private String desc;
  • private int likeCounts;
  • private String publishedAt;
  • private int stars;
  • private String title;
  • private String type;
  • private String url;
  • private int views;
  • private List<String> images;
  • public String get_id() {
  • return _id;
  • }
  • public void set_id(String _id) {
  • this._id = _id;
  • }
  • public String getAuthor() {
  • return author;
  • }
  • public void setAuthor(String author) {
  • this.author = author;
  • }
  • public String getCategory() {
  • return category;
  • }
  • public void setCategory(String category) {
  • this.category = category;
  • }
  • public String getCreatedAt() {
  • return createdAt;
  • }
  • public void setCreatedAt(String createdAt) {
  • this.createdAt = createdAt;
  • }
  • public String getDesc() {
  • return desc;
  • }
  • public void setDesc(String desc) {
  • this.desc = desc;
  • }
  • public int getLikeCounts() {
  • return likeCounts;
  • }
  • public void setLikeCounts(int likeCounts) {
  • this.likeCounts = likeCounts;
  • }
  • public String getPublishedAt() {
  • return publishedAt;
  • }
  • public void setPublishedAt(String publishedAt) {
  • this.publishedAt = publishedAt;
  • }
  • public int getStars() {
  • return stars;
  • }
  • public void setStars(int stars) {
  • this.stars = stars;
  • }
  • public String getTitle() {
  • return title;
  • }
  • public void setTitle(String title) {
  • this.title = title;
  • }
  • public String getType() {
  • return type;
  • }
  • public void setType(String type) {
  • this.type = type;
  • }
  • public String getUrl() {
  • return url;
  • }
  • public void setUrl(String url) {
  • this.url = url;
  • }
  • public int getViews() {
  • return views;
  • }
  • public void setViews(int views) {
  • this.views = views;
  • }
  • public List<String> getImages() {
  • return images;
  • }
  • public void setImages(List<String> images) {
  • this.images = images;
  • }
  • }
  • }
展开

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

  • package com.llw.network.api;
  • import com.llw.network.bean.GankResponse;
  • import io.reactivex.Observable;
  • import retrofit2.http.GET;
  • /**
  • * ApiService接口 统一管理应用所有的接口
  • * @author llw
  • */
  • public interface ApiService {
  • /**
  • * 获取数据列表
  • * @return GankResponse
  • */
  • @GET("/api/v2/data/category/Girl/type/Girl/page/1/count/10")
  • Observable<GankResponse> getList();
  • }

现在基本上就配置完毕了,下面就来简单使用一下,修改一个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中。

  • package com.llw.network;
  • import androidx.appcompat.app.AppCompatActivity;
  • import android.annotation.SuppressLint;
  • import android.os.Bundle;
  • import android.widget.ImageView;
  • import android.widget.Toast;
  • import com.bumptech.glide.Glide;
  • import com.llw.network.api.ApiService;
  • import com.llw.network.bean.GankResponse;
  • import com.llw.network.observer.BaseObserver;
  • import java.util.List;
  • /**
  • * @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)
  • .getList()
  • .compose(NetworkApi.applySchedulers(new BaseObserver<GankResponse>() {
  • /**
  • * 成功
  • * @param gankResponse
  • */
  • @Override
  • public void onSuccess(GankResponse gankResponse) {
  • List<GankResponse.DataBean> data = gankResponse.getData();
  • if (data != null && data.size() > 0) {
  • String imgUrl = data.get(1).getImages().get(0);
  • Glide.with(MainActivity.this).load(imgUrl).into(imageView);
  • }else {
  • Toast.makeText(MainActivity.this, "数据为空", Toast.LENGTH_SHORT).show();
  • }
  • }
  • /**
  • * 失败
  • * @param e
  • */
  • @Override
  • public void onFailure(Throwable e) {
  • KLog.e("MainActivity",e.toString());
  • Toast.makeText(MainActivity.this, "访问失败", Toast.LENGTH_SHORT).show();
  • }
  • }));
  • }
  • }
展开

然后运行一下。

图片就加载出来了。
可以查看run里面的日志

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

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

还记得配置OkHttp这里吗?

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

现在是不是还差了一步呢?那就是设置网络了,还记得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()方法。

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

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

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

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

九、源码

源码地址:NetworkFrameWorkDemo


总结

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

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>