Android OkHttp+Retrofit+RxJava搭建网络访问框架(含源码) 您所在的位置:网站首页 安卓网络框架 Android OkHttp+Retrofit+RxJava搭建网络访问框架(含源码)

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

2023-09-16 07:15| 来源: 网络整理| 查看: 265

OkHttp+Retrofit+RxJava搭建网络访问框架前言正文一、添加依赖二、配置OkHttp三、配置Retrofit四、配置RxJava五、增加拦截器六、自定义Observer七、配置网络环境八、使用网络框架1. 添加网络模块依赖① 当前项目添加② 其他项目或新项目添加2. 使用网络模块3. 切换网络环境九、源码总结前言

  在实际开发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 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 implements Function { /** * 处理以下两类网络错误: * 1、http请求相关的错误,例如:404,403,socket timeout等等; * 2、应用数据的错误会抛RuntimeException,最后也会走到这个函数来统一处理; */ @Override public Observable 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 Function getAppErrorHandler() { return new Function() { @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 泛型 * @return Observable */ public static ObservableTransformer applySchedulers(final Observer observer) { return new ObservableTransformer() { @Override public ObservableSource apply(Observable upstream) { Observable observable = upstream .subscribeOn(Schedulers.io())//线程订阅 .observeOn(AndroidSchedulers.mainThread())//观察Android主线程 .map(NetworkApi.getAppErrorHandler())//判断有没有500的错误,有则进入getAppErrorHandler .onErrorResumeNext(new HttpErrorHandler());//判断有没有400的错误 //这里还少了对异常 //订阅观察者 observable.subscribe(observer); return observable; } }; }五、增加拦截器

拦截器中需要打印日志和时间转换,对此需要几个工具类,所以在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 implements Observer { //开始 @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文件,里面的代码如下:

在这里插入图片描述在这里插入图片描述

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

正式 测试 1 2

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

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

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

网络环境设置 您已经更改了网络环境,在您退出当前页面的时候APP将会重启切换环境!在这里插入图片描述在这里插入图片描述

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

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

这里默认的值为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中配置

但是网络配置这一步还没有结束,之前在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 createService(Class 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

在这里插入图片描述在这里插入图片描述

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

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

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

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

在这里插入图片描述在这里插入图片描述

点击OK。

在这里插入图片描述在这里插入图片描述

点击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。

下面就要来显示数据了,

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

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

在这里插入图片描述在这里插入图片描述

利用这些数据可以生成一个实体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 vertical; public List getVertical() { return vertical; } public void setVertical(List 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 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 getCid() { return cid; } public void setCid(List 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 getWallPaper(); }

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

里面也就一个图片控件而已,然后回到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() { @Override public void onSuccess(WallPaperResponse wallPaperResponse) { List 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(); } })); }

然后运行一下。

在这里插入图片描述在这里插入图片描述

图片就加载出来了。 可以查看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


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有