网络层Library是App最常用的库,需要考虑稳定性,后期的扩展性,更换核心网络库后对项目的影响,ZZNet采用okhttp作为核心网络库。
需求描述:
- 支持HTTP/HTTPS;
- 请求支持取消;
- 支持校验器,可用于统一的JSON校验;
- 支持拦截器,可用于缓存的处理;
- 支持重试次数和自定义重试规则;
- 支持文件上传,上传进度,多文件上传,取消上传;
- 支持文件下载,下载进度,断点续传,取消下载;
- 自动处理错误描述和详细的错误类型;
架构设计:
架构实现:
ZZNetValidator,请求及响应校验器
可以针对单个请求配置校验器或所有请求配置统一的校验器,校验请求参数或响应数据的合法性等。例如:可以在请求前校验登录状态,在响应后校验服务端返回的token是否合法,是否需要重新登录等,可以在此处做统一的处理。
/**
* 请求及响应校验器
*/
public interface ZZNetValidator {
/**
* 校验参数合法性,运行在主线程
* @param zzNet
* @param paramsSource
* @return
*/
ZZNetResponse isCorrectWithParamsDataRunOnMainThread(@NonNull ZZNet zzNet
, @NonNull Map<String, String> paramsSource);
/**
* 校验API响应合法性,运行在工作线程
* @param zzNet
* @param response
*/
ZZNetResponse isCorrectWithCallBackDataRunOnWorkThread(@NonNull ZZNet zzNet
, @NonNull ZZNetResponse response);
}
ZZNetInterceptor,请求及响应拦截器
针对请求生命周期的各个阶段进行拦截,增强网络库的扩展性。例如:随着业务的发展,API如果需要引入缓存机制,可以在拦截器中处理,隔离或降低对业务层的侵入性。
/**
* 请求及响应拦截器
*/
public interface ZZNetInterceptor {
/**
* 通过所有校验,发起网络请求前执行,运行在工作线程。
* @param zzNet
* @return
*/
@WorkerThread
boolean beforeSendNetRequestRunOnWorkThread(@NonNull ZZNet zzNet);
/**
* 发送网络请求后执行,运行在工作线程。
* @param zzNet
*/
@WorkerThread
void afterSendNetRequestRunOnWorkThread(@NonNull ZZNet zzNet);
/**
* 在callback的onDidCompleted方法前执行,运行在主线程
* @param zzNet
* @param response
*/
@MainThread
void beforeDidCompletedRunOnMainThread(@NonNull ZZNet zzNet, @NonNull ZZNetResponse response);
}
ZZNetProcessor,网络请求及响应核心处理器
网络请求处理的抽象接口,把网络请求过程拆分为处理请求和处理响应,可以根据业务需要添加实现;目前有3个实现类,ZZNetStringProcessor(处理字符串类型的响应数据)、 ZZNetUploadProcessor(处理文件上传)、 ZZNetDownloadProcessor(处理文件下载)。
@WorkerThread
public interface ZZNetProcessor {
/**
* 处理请求参数
* @param requestBuilder
*/
@WorkerThread
void handleRequestParams(@NonNull Request.Builder requestBuilder);
/**
* 处理请求响应
* @param response
* @param responseEntity
*/
@WorkerThread
void handleResponse(@Nullable Response response, @NonNull ZZNetResponse responseEntity);
ZZNetStringProcessor,字符串处理器
/**
* 字符串处理器
*/
@WorkerThread
public final class ZZNetStringProcessor extends ZZNetDefaultProcessor {
public ZZNetStringProcessor(ZZNet zzNet){
super(zzNet);
}
/**
* 处理String类型的响应
* @param response
* @param netResponse
*/
@Override
@WorkerThread
public void handleResponse(Response response, ZZNetResponse netResponse) {
try{
if (response != null) {
if (response.isSuccessful()) {
netResponse.rawString = response.body().string();
if (checkJsonValid(netResponse)){
netResponse.errorType = ZZNetErrorType.Success;
// validator校验响应结果
ZZNetValidator appValidator = zzNet.getBuilder().getAppValidator();
if (appValidator != null){
appValidator.isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
}
if (zzNet.getValidator() != null){
zzNet.getValidator().isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
}
}
} else {
netResponse.errorType = ZZNetErrorType.ServerError;
netResponse.errorMsg = zzNet.getAppContext().getString(R.string.lib_network_net_msg4);
}
} else {
netResponse.errorType = ZZNetErrorType.NoResponse;
netResponse.errorMsg = zzNet.getAppContext().getString(R.string.lib_network_net_msg6);
}
}catch (Exception e){
e.printStackTrace();
netResponse.errorType = ZZNetErrorType.Timeout;
netResponse.errorMsg = zzNet.getAppContext().getString(R.string.lib_network_net_msg10);
}
}
/**
* 检查json合法性,并解析json中错误码及错误描述,决定json是否可解析
*
* @param responseEntity 响应实体数据,需先设置responseEntity.rawJson
* @return
*/
private boolean checkJsonValid(@NonNull ZZNetResponse responseEntity) {
if (TextUtils.isEmpty(responseEntity.rawString)) {
responseEntity.errorType = ZZNetErrorType.JSONInValid;
responseEntity.errorMsg = zzNet.getAppContext().getString(R.string.lib_network_net_msg7);
return false;
}
if (responseEntity.rawString.startsWith("\ufeff")) {
responseEntity.rawString = responseEntity.rawString.substring(1);
}
// parse error code
return true;
}
}
ZZNetUploadProcessor,文件上传处理器
/**
* 文件上传处理器
*/
@WorkerThread
public class ZZNetUploadProcessor extends ZZNetDefaultProcessor {
public ZZNetUploadProcessor(@NonNull ZZNet zzNet){
super(zzNet);
}
@Override
public void handleRequestParams(Request.Builder requestBuilder) {
super.handleRequestParams(requestBuilder);
handleUploadRequestParams(requestBuilder);
}
private void handleUploadRequestParams(Request.Builder requestBuilder){
if (zzNet.getUploadFile() != null && zzNet.getUploadFile().exists()){
if (zzNet.getMediaType() == null){
throw new NullPointerException("上传文件类型不能为空");
}
RequestBody rawRequestBody = RequestBody.create(MediaType.parse(zzNet.getMediaType())
, zzNet.getUploadFile());
if (zzNet.getMultipartFileKeyName() != null){
MultipartBody multipartBody = new MultipartBody.Builder()
.addFormDataPart(zzNet.getMultipartFileKeyName()
,zzNet.getUploadFile().getName(), rawRequestBody).build();
RequestBody requestBody = new ZZProgressRequest(multipartBody, zzNet.getFileCallback());
requestBuilder.post(requestBody);
}else{
RequestBody requestBody = new ZZProgressRequest(rawRequestBody, zzNet.getFileCallback());
requestBuilder.post(requestBody);
}
}
}
@Override
@WorkerThread
public void handleResponse(@NonNull Response response, @NonNull ZZNetResponse netResponse) {
if (response == null){
netResponse.errorType = ZZNetErrorType.NoResponse;
netResponse.errorMsg = zzNet.getAppContext().getString(R.string.lib_network_net_msg6);
return;
}
if (!response.isSuccessful()){
return;
}
// 解析响应数据
try {
netResponse.rawString = response.body().string();
netResponse.errorType = ZZNetErrorType.Success;
}catch (Exception e){
e.printStackTrace();
}
// validator校验响应
ZZNetValidator appValidator = zzNet.getBuilder().getAppValidator();
if (appValidator != null){
appValidator.isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "appValidator校验响应不通过,终止");
return;
}
}
if (zzNet.getValidator() != null){
zzNet.getValidator().isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "apiValidator校验响应不通过,终止");
return;
}
}
}
}
ZZNetDownloadProcessor,文件下载处理器
/**
* 文件下载处理器
*/
@WorkerThread
public final class ZZNetDownloadProcessor extends ZZNetDefaultProcessor {
private static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 KB
public ZZNetDownloadProcessor(ZZNet zzNet){
super(zzNet);
}
@Override
@WorkerThread
public void handleRequestParams(Request.Builder requestBuilder) {
super.handleRequestParams(requestBuilder);
handleDownloadRequestParams(requestBuilder);
}
/**
* 处理下载请求参数
* @param requestBuilder
*/
@WorkerThread
private void handleDownloadRequestParams(Request.Builder requestBuilder){
if (canAddRangeHeader()){
// 断点续传下载
requestBuilder.header("RANGE", "bytes=" + zzNet.getSaveFile().length() + "-");
}
}
/**
* 能否拼接断点续传文件Range
* @return
*/
@WorkerThread
private boolean canAddRangeHeader(){
File saveOrUploadFile = zzNet.getSaveFile();
if (zzNet.isSupportResumeDownload()
&& zzNet.getRequestModel() == ZZNetRequestModel.DownloadFile
&& saveOrUploadFile != null && saveOrUploadFile.exists()
&& saveOrUploadFile.length() > 0) {
return true;
}
return false;
}
/**
* 处理文件下载响应
* @param response
* @throws IOException
*/
@Override
@WorkerThread
public void handleResponse(@Nullable Response response, @NonNull ZZNetResponse netResponse){
if (response == null){
netResponse.errorType = ZZNetErrorType.NoResponse;
netResponse.errorMsg = zzNet.getAppContext().getString(R.string.lib_network_net_msg6);
return;
}
if (response.code() == 416){
LogUtils.w(zzNet.getUrl(), "文件已经下载完毕,不需要再次下载,终止本次请求");
netResponse.errorType = ZZNetErrorType.Success;
return;
}
if (!response.isSuccessful()){
return;
}
// validator校验响应
ZZNetValidator appValidator = zzNet.getBuilder().getAppValidator();
if (appValidator != null){
appValidator.isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "appValidator校验响应不通过,终止");
return;
}
}
if (zzNet.getValidator() != null){
zzNet.getValidator().isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "apiValidator校验响应不通过,终止");
return;
}
}
boolean isSupportRange = isSupportRange(response);
File saveOrUploadFile = zzNet.getSaveFile();
final boolean append = saveOrUploadFile.length() > 0 && isSupportRange;
if (LogUtils.DEBUG){
LogUtils.d(zzNet.getUrl(), "文件总大小", String.valueOf(response.body().contentLength())
, String.format("当前是否断点续传%s", Boolean.valueOf(append)));
}
OutputStream os = null;
try {
os = new FileOutputStream(saveOrUploadFile, append);
}catch (FileNotFoundException e){
e.printStackTrace();
try {
os.close();
}catch (IOException ex){
ex.printStackTrace();
}
return;
}
InputStream is = response.body().byteStream();
boolean downloadSuccess = true;
boolean isCanceled = false;
try{
isCanceled = !copyStream(response.body().contentLength(), is, os, isSupportRange);
}catch (Exception e){
e.printStackTrace();
downloadSuccess = false;
}finally {
try{
if (os != null){
os.close();
}
if (is != null) {
is.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
if (isCanceled){
// 被用户取消
netResponse.errorType = ZZNetErrorType.Canceled;
}else if (downloadSuccess){
netResponse.errorType = ZZNetErrorType.Success;
}
}
/**
* 是否支持断点上传
* @param response
* @return
*/
@WorkerThread
public boolean isSupportRange(Response response) {
if (response == null || !zzNet.isSupportResumeDownload()) return false;
if ("bytes".equals(response.header("Accept-Ranges"))) return true;
String contentRanges = response.header("Content-Range");
if (contentRanges != null && contentRanges.startsWith("bytes")) return true;
return false;
}
/**
* 写数据到文件
* @param total
* @param is
* @param os
* @return
* @throws IOException
*/
@WorkerThread
public boolean copyStream(long total, InputStream is, OutputStream os, boolean isSupportRange) throws IOException {
long current = 0;
if (total <= 0) {
total = is.available();
}
long oldFileCount = 0;
File saveOrUploadFile = zzNet.getSaveFile();
if (isSupportRange && saveOrUploadFile != null && saveOrUploadFile.exists() && saveOrUploadFile.length() > 0) {
oldFileCount = saveOrUploadFile.length();
total += oldFileCount;
}
final byte[] bytes = new byte[DEFAULT_BUFFER_SIZE];
int count;
while ((count = is.read(bytes, 0, DEFAULT_BUFFER_SIZE)) != -1) {
if (zzNet.isCanceled()){
return false;
}
os.write(bytes, 0, count);
current += count;
if (zzNet.getFileCallback() != null){
ZZNetFileCallback callback = zzNet.getFileCallback().get();
if (callback != null){
callback.onProgess(current + oldFileCount, total);
}
}
}
return true;
}
}
自定义重试规则
允许请求失败后的重试操作,可以自定义重试规则,重试次数等。例如:客户端支付成功后需要查询支付状态,服务端的支付状态依赖第三方支付平台的通知,为降低通知延迟对客户端的影响,可以自定义重试规则,当支付失败时多重试几次。
重试机制规则抽象接口
/**
* 重试机制规则
*/
public interface ZZNetRetryRule {
/**
* 是否需要重试,可在此定义重试规则,注意此函数是运行在工作线程中
* @param zzNetResponse
* @return
*/
@WorkerThread
boolean needRetry(ZZNetResponse zzNetResponse);
}
重试机制具体使用方法
/**
* 自定义重试规则,App端支付成功后查询支付状态
* ,如果查询结果为支付失败,等待500ms后继续查询(最多3次)。
*/
public static class QueryPayStatusRetryRule implements ZZNetRetryRule {
// 该函数运行在工作线程中
@Override
public boolean needRetry(ZZNetResponse response) {
// 可根据zzNetResponse定义重试规则
boolean needRetry = true;
if (!TextUtils.isEmpty(response.rawString)){
try {
final JSONObject jsonObject = JSON.parseObject(response.rawString);
if (jsonObject != null){
final boolean paySuccess = jsonObject.getBooleanValue("xxx");
if (!paySuccess){
if (LogUtils.DEBUG){
LogUtils.w("支付失败,延迟500ms重新查询");
}
SystemClock.sleep(500);
}else{
// 支付成功, 不需要重试
needRetry = false;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
return needRetry;
}
}
本文作者:gcoder.io
本文链接:http://gcoder-io.github.io/2017/03/26/android-network-framework/
版权声明: 本博客所有文章均为原创,转载请注明作者及出处