项目的集成
// JitPack 远程仓库:https://jitpack.io
maven { url 'https://jitpack.io' }
// 网络请求框架:https://github.com/getActivity/EasyHttp
implementation 'com.github.getActivity:EasyHttp:12.5'
// OkHttp 框架:https://github.com/square/okhttp
// noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
源码及介绍
网络请求,如斯优雅 - 简书
GitHub - getActivity/EasyHttp: Android 网络请求框架,简单易用,so easy
解决明文流量的网络请求报错
CLEARTEXT communication to 3g.163.com not permitted by network security policy
java.net.UnknownServiceException: CLEARTEXT communication to 3g.163.com not permitted by network security policy
添加res/xml/network_security_config.xml文件。文件名可以自拟,和后面的配置对应上即可。如果项目中,res文件夹中没有xml文件夹,我们需要在里面创建一个xml文件夹,然后在里面创建一个network_security_config.xml文件,network_security_config.xml文件中的内容如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 解决 Android P 禁用 http 请求的问题 --> <network-security-config> <!-- 允许明文通信 --> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
android:networkSecurityConfig="@xml/network_security_config"
初始化EasyHttp
由于直接复用轮子哥的配置代码,需要引入他调用的包,如果不引用的话修改他的配置代码
// Json 解析框架:https://github.com/google/gson implementation 'com.google.code.gson:gson:2.10.1' // Gson 解析容错:https://github.com/getActivity/GsonFactory implementation 'com.github.getActivity:GsonFactory:8.0' // 腾讯 MMKV:https://github.com/Tencent/MMKV MMKV初始化,MMKV是基于mmap内存映射的key-value组件 implementation 'com.tencent:mmkv-static:1.2.14'
/**
* 初始哈EasyHttp
*/
private void initEasyHttp() {
MMKV.initialize(this);//MMKV初始化,MMKV是基于mmap内存映射的key-value组件
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
// 网络请求框架初始化
IRequestServer server = new ReleaseServer();
EasyConfig.with(okHttpClient)
//.setLogEnabled(BuildConfig.DEBUG)// 是否打印日志
.setServer(server)// 设置服务器配置(必须设置)
.setHandler(new RequestHandler(this))// 设置请求处理策略(必须设置)
// 设置请求参数拦截器
.setInterceptor(new IRequestInterceptor() {
@Override
public void interceptArguments(@NonNull HttpRequest<?> httpRequest,
@NonNull HttpParams params,
@NonNull HttpHeaders headers) {
headers.put("timestamp", String.valueOf(System.currentTimeMillis()));
}
})
.setRetryCount(1) // 设置请求重试次数
.setRetryTime(2000)// 设置请求重试时间
// .addParam("token", "6666666")// 添加全局请求参数
// .addHeader("date", "20191030")// 添加全局请求头
.into();
}
RequestHandler 请求处理类
final public class RequestHandler implements IRequestHandler {
private final Application mApplication;
public RequestHandler(Application application) {
mApplication = application;
}
@NonNull
@Override
public Object requestSuccess(@NonNull HttpRequest<?> httpRequest, @NonNull Response response,
@NonNull Type type) throws Exception {
if (Response.class.equals(type)) {
return response;
}
if (!response.isSuccessful()) {
throw new ResponseException(String.format(mApplication.getString(R.string.http_response_error),
response.code(), response.message()), response);
}
if (Headers.class.equals(type)) {
return response.headers();
}
ResponseBody body = response.body();
if (body == null) {
throw new NullBodyException(mApplication.getString(R.string.http_response_null_body));
}
if (ResponseBody.class.equals(type)) {
return body;
}
// 如果是用数组接收,判断一下是不是用 byte[] 类型进行接收的
if(type instanceof GenericArrayType) {
Type genericComponentType = ((GenericArrayType) type).getGenericComponentType();
if (byte.class.equals(genericComponentType)) {
return body.bytes();
}
}
if (InputStream.class.equals(type)) {
return body.byteStream();
}
if (Bitmap.class.equals(type)) {
return BitmapFactory.decodeStream(body.byteStream());
}
String text;
try {
text = body.string();
} catch (IOException e) {
// 返回结果读取异常
throw new DataException(mApplication.getString(R.string.http_data_explain_error), e);
}
// 打印这个 Json 或者文本
EasyLog.printJson(httpRequest, text);
if (String.class.equals(type)) {
return text;
}
final Object result;
try {
result = GsonFactory.getSingletonGson().fromJson(text, type);
} catch (JsonSyntaxException e) {
// 返回结果读取异常
throw new DataException(mApplication.getString(R.string.http_data_explain_error), e);
}
if (result instanceof HttpData) {
HttpData<?> model = (HttpData<?>) result;
model.setResponseHeaders(response.headers());
if (model.isRequestSuccess()) {
// 代表执行成功
return result;
}
if (model.isTokenInvalidation()) {
// 代表登录失效,需要重新登录
throw new TokenException(mApplication.getString(R.string.http_token_error));
}
// 代表执行失败
throw new ResultException(model.getMessage(), model);
}
return result;
}
@NonNull
@Override
public Exception requestFail(@NonNull HttpRequest<?> httpRequest, @NonNull Exception e) {
if (e instanceof HttpException) {
if (e instanceof TokenException) {
// 登录信息失效,跳转到登录页
}
return e;
}
if (e instanceof SocketTimeoutException) {
return new TimeoutException(mApplication.getString(R.string.http_server_out_time), e);
}
if (e instanceof UnknownHostException) {
NetworkInfo info = ((ConnectivityManager) mApplication.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
// 判断网络是否连接
if (info != null && info.isConnected()) {
// 有连接就是服务器的问题
return new ServerException(mApplication.getString(R.string.http_server_error), e);
}
// 没有连接就是网络异常
return new NetworkException(mApplication.getString(R.string.http_network_error), e);
}
if (e instanceof IOException) {
// 出现该异常的两种情况
// 1. 调用 EasyHttp.cancel
// 2. 网络请求被中断
return new CancelException(mApplication.getString(R.string.http_request_cancel), e);
}
return new HttpException(e.getMessage(), e);
}
@NonNull
@Override
public Exception downloadFail(@NonNull HttpRequest<?> httpRequest, @NonNull Exception e) {
if (e instanceof ResponseException) {
ResponseException responseException = ((ResponseException) e);
Response response = responseException.getResponse();
responseException.setMessage(String.format(mApplication.getString(R.string.http_response_error),
response.code(), response.message()));
return responseException;
} else if (e instanceof NullBodyException) {
NullBodyException nullBodyException = ((NullBodyException) e);
nullBodyException.setMessage(mApplication.getString(R.string.http_response_null_body));
return nullBodyException;
} else if (e instanceof FileMd5Exception) {
FileMd5Exception fileMd5Exception = ((FileMd5Exception) e);
fileMd5Exception.setMessage(mApplication.getString(R.string.http_response_md5_error));
return fileMd5Exception;
}
return requestFail(httpRequest, e);
}
@Nullable
@Override
public Object readCache(@NonNull HttpRequest<?> httpRequest, @NonNull Type type, long cacheTime) {
String cacheKey = HttpCacheManager.generateCacheKey(httpRequest);
String cacheValue = HttpCacheManager.readHttpCache(cacheKey);
if (cacheValue == null || "".equals(cacheValue) || "{}".equals(cacheValue)) {
return null;
}
EasyLog.printLog(httpRequest, "----- read cache key -----");
EasyLog.printJson(httpRequest, cacheKey);
EasyLog.printLog(httpRequest, "----- read cache value -----");
EasyLog.printJson(httpRequest, cacheValue);
EasyLog.printLog(httpRequest, "cacheTime = " + cacheTime);
boolean cacheInvalidate = HttpCacheManager.isCacheInvalidate(cacheKey, cacheTime);
EasyLog.printLog(httpRequest, "cacheInvalidate = " + cacheInvalidate);
if (cacheInvalidate) {
// 表示缓存已经过期了,直接返回 null 给外层,表示缓存不可用
return null;
}
return GsonFactory.getSingletonGson().fromJson(cacheValue, type);
}
@Override
public boolean writeCache(@NonNull HttpRequest<?> httpRequest, @NonNull Response response, @NonNull Object result) {
String cacheKey = HttpCacheManager.generateCacheKey(httpRequest);
String cacheValue = GsonFactory.getSingletonGson().toJson(result);
if (cacheValue == null || "".equals(cacheValue) || "{}".equals(cacheValue)) {
return false;
}
EasyLog.printLog(httpRequest, "----- write cache key -----");
EasyLog.printJson(httpRequest, cacheKey);
EasyLog.printLog(httpRequest, "----- write cache value -----");
EasyLog.printJson(httpRequest, cacheValue);
boolean writeHttpCacheResult = HttpCacheManager.writeHttpCache(cacheKey, cacheValue);
EasyLog.printLog(httpRequest, "writeHttpCacheResult = " + writeHttpCacheResult);
boolean refreshHttpCacheTimeResult = HttpCacheManager.setHttpCacheTime(cacheKey, System.currentTimeMillis());
EasyLog.printLog(httpRequest, "refreshHttpCacheTimeResult = " + refreshHttpCacheTimeResult);
return writeHttpCacheResult && refreshHttpCacheTimeResult;
}
@Override
public void clearCache() {
HttpCacheManager.clearCache();
}
}
字符串
<!--EasyHttp--> <string name="http_response_error">服务器响应异常,请稍后再试,响应码:%d,响应信息:%s</string> <string name="http_response_null_body">服务器数据返回异常,请稍后再试</string> <string name="http_data_explain_error">数据解析异常,请稍后</string> <string name="http_token_error">登录失效,请重新登录</string> <string name="http_server_out_time">服务器请求超时,请稍后再试</string> <string name="http_server_error">服务器连接异常,请稍后再试</string> <string name="http_network_error">请求失败,请检查网络设置</string> <string name="http_request_cancel">请求被中断,请重试</string> <string name="http_response_md5_error">下载失败,文件 md5 校验失败</string>
HttpCacheManager Http缓存管理器
public final class HttpCacheManager {
private static final MMKV HTTP_CACHE_CONTENT = MMKV.mmkvWithID("http_cache_content");
private static final MMKV HTTP_CACHE_TIME = MMKV.mmkvWithID("http_cache_time");
/**
* 生成缓存的 key
*/
public static String generateCacheKey(@NonNull HttpRequest<?> httpRequest) {
IRequestApi requestApi = httpRequest.getRequestApi();
return "请替换成当前的用户 id" + "\n" + requestApi.getApi() + "\n" + GsonFactory.getSingletonGson().toJson(requestApi);
}
/**
* 读取缓存
*/
public static String readHttpCache(String cacheKey) {
String cacheValue = HTTP_CACHE_CONTENT.getString(cacheKey, null);
if ("".equals(cacheValue) || "{}".equals(cacheValue)) {
return null;
}
return cacheValue;
}
/**
* 写入缓存
*/
public static boolean writeHttpCache(String cacheKey, String cacheValue) {
return HTTP_CACHE_CONTENT.putString(cacheKey, cacheValue).commit();
}
/**
* 清理缓存
*/
public static void clearCache() {
HTTP_CACHE_CONTENT.clearMemoryCache();
HTTP_CACHE_CONTENT.clearAll();
HTTP_CACHE_TIME.clearMemoryCache();
HTTP_CACHE_TIME.clearAll();
}
/**
* 获取 Http 写入缓存的时间
*/
public static long getHttpCacheTime(String cacheKey) {
return HTTP_CACHE_TIME.getLong(cacheKey, 0);
}
/**
* 设置 Http 写入缓存的时间
*/
public static boolean setHttpCacheTime(String cacheKey, long cacheTime) {
return HTTP_CACHE_TIME.putLong(cacheKey, cacheTime).commit();
}
/**
* 判断缓存是否过期
*/
public static boolean isCacheInvalidate(String cacheKey, long maxCacheTime) {
if (maxCacheTime == Long.MAX_VALUE) {
// 表示缓存长期有效,永远不会过期
return false;
}
long httpCacheTime = getHttpCacheTime(cacheKey);
if (httpCacheTime == 0) {
// 表示不知道缓存的时间,这里默认当做已经过期了
return true;
}
return httpCacheTime + maxCacheTime < System.currentTimeMillis();
}
}
HttpData 统一接口数据结构
public class HttpData<T> {
/** 响应头 */
@Nullable
private Headers responseHeaders;
/** 返回码 */
private int errorCode;
/** 提示语 */
private String errorMsg;
/** 数据 */
@Nullable
private T data;
public void setResponseHeaders(@Nullable Headers responseHeaders) {
this.responseHeaders = responseHeaders;
}
@Nullable
public Headers getResponseHeaders() {
return responseHeaders;
}
public int getCode() {
return errorCode;
}
public String getMessage() {
return errorMsg;
}
@Nullable
public T getData() {
return data;
}
/**
* 是否请求成功
*/
public boolean isRequestSuccess() {
// 这里为了兼容 WanAndroid 接口才这样写,但是一般情况下不建议这么设计
// 因为 int 的默认值就是 0,这样就会导致,后台返回结果码为 0 和没有返回的效果是一样的
// 本质上其实不一样,没有返回结果码本身就是一种错误数据结构,理论上应该走失败的回调
// 因为这里会判断是否等于 0,所以就会导致原本走失败的回调,结果走了成功的回调
// 所以在定义错误码协议的时候,请不要将后台返回的某个成功码或者失败码的值设计成 0
// 如果你的项目已经出现了这种情况,可以尝试将结果码的数据类型从 int 修改成 Integer
// 这样就可以通过结果码是否等于 null 来判断后台是否返回了,当然这样也有一些弊端
// 后面外层在使用这个结果码的时候,要先对 Integer 对象进行一次判空,否则会出现空指针异常
return errorCode == 0;
}
/**
* 是否 Token 失效
*/
public boolean isTokenInvalidation() {
return errorCode == 1001;
}
}
ReleaseServer 请求服务配置
public class ReleaseServer implements IRequestServer { @NonNull @Override public String getHost() { return "https://www.wanandroid.com/"; } }
ResultException 返回结果异常
public final class ResultException extends HttpException {
private final HttpData<?> mData;
public ResultException(String message, HttpData<?> data) {
super(message);
mData = data;
}
public ResultException(String message, Throwable cause, HttpData<?> data) {
super(message, cause);
mData = data;
}
@NonNull
public HttpData<?> getHttpData() {
return mData;
}
}
TokenException Token失效异常
public final class TokenException extends HttpException {
public TokenException(String message) {
super(message);
}
public TokenException(String message, Throwable cause) {
super(message, cause);
}
}
模拟下载文件
// 如果是放到外部存储的应用专属目录则不需要适配分区存储特性
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "微信 8.0.15.apk");
String url = "http://3g.163.com/links/4636";
EasyHttp.download(this)
.method(HttpMethod.GET)
.file(file)
//.url("https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk")
// .url("https://dldir1.qq.com/weixin/android/weixin8015android2020_arm64.apk")
// .md5("b05b25d4738ea31091dd9f80f4416469")
.url(url)
.listener(new OnDownloadListener() {
@Override
public void onDownloadStart(File file) {
dialoading.setTv("开始下载");
}
@Override
public void onDownloadProgressChange(File file, int progress) {
dialoading.setTv("下载进度:" + progress);
}
@Override
public void onDownloadSuccess(File file) {
ToastUtils.success("下载成功:" + file.getPath());
AppUtils.installApp(file);
}
@Override
public void onDownloadFail(File file, Exception e) {
ToastUtils.error("下载失败:" + e.getMessage());
new WarningDialog.Builder(DownLoadActivity.this).
setTitle("下载失败").setText(e.toString()).show();
if (e instanceof FileMd5Exception) {
// 如果是文件 md5 校验失败,则删除文件
if (file.exists()) {
file.delete();
}
}
}
@Override
public void onDownloadEnd(File file) {
dialoading.close();
}
})
.start();