網(wǎng)絡(luò)層Library是App最常用的庫,需要考慮穩(wěn)定性,后期的擴(kuò)展性,更換核心網(wǎng)絡(luò)庫后對項(xiàng)目的影響,ZZNet采用okhttp作為核心網(wǎng)絡(luò)庫。
需求描述:
- 支持HTTP/HTTPS;
- 請求支持取消;
- 支持校驗(yàn)器,可用于統(tǒng)一的JSON校驗(yàn);
- 支持?jǐn)r截器,可用于緩存的處理;
- 支持重試次數(shù)和自定義重試規(guī)則;
- 支持文件上傳,上傳進(jìn)度,多文件上傳,取消上傳;
- 支持文件下載,下載進(jìn)度,斷點(diǎn)續(xù)傳,取消下載;
- 自動(dòng)處理錯(cuò)誤描述和詳細(xì)的錯(cuò)誤類型;
架構(gòu)設(shè)計(jì):

架構(gòu)實(shí)現(xiàn):
ZZNetValidator,請求及響應(yīng)校驗(yàn)器
可以針對單個(gè)請求配置校驗(yàn)器或所有請求配置統(tǒng)一的校驗(yàn)器,校驗(yàn)請求參數(shù)或響應(yīng)數(shù)據(jù)的合法性等。例如:可以在請求前校驗(yàn)登錄狀態(tài),在響應(yīng)后校驗(yàn)服務(wù)端返回的token是否合法,是否需要重新登錄等,可以在此處做統(tǒng)一的處理。
/**
* 請求及響應(yīng)校驗(yàn)器
*/
public interface ZZNetValidator {
/**
* 校驗(yàn)參數(shù)合法性,運(yùn)行在主線程
* @param zzNet
* @param paramsSource
* @return
*/
ZZNetResponse isCorrectWithParamsDataRunOnMainThread(@NonNull ZZNet zzNet
, @NonNull Map<String, String> paramsSource);
/**
* 校驗(yàn)API響應(yīng)合法性,運(yùn)行在工作線程
* @param zzNet
* @param response
*/
ZZNetResponse isCorrectWithCallBackDataRunOnWorkThread(@NonNull ZZNet zzNet
, @NonNull ZZNetResponse response);
}
ZZNetInterceptor,請求及響應(yīng)攔截器
針對請求生命周期的各個(gè)階段進(jìn)行攔截,增強(qiáng)網(wǎng)絡(luò)庫的擴(kuò)展性。例如:隨著業(yè)務(wù)的發(fā)展,API如果需要引入緩存機(jī)制,可以在攔截器中處理,隔離或降低對業(yè)務(wù)層的侵入性。
/**
* 請求及響應(yīng)攔截器
*/
public interface ZZNetInterceptor {
/**
* 通過所有校驗(yàn),發(fā)起網(wǎng)絡(luò)請求前執(zhí)行,運(yùn)行在工作線程。
* @param zzNet
* @return
*/
@WorkerThread
boolean beforeSendNetRequestRunOnWorkThread(@NonNull ZZNet zzNet);
/**
* 發(fā)送網(wǎng)絡(luò)請求后執(zhí)行,運(yùn)行在工作線程。
* @param zzNet
*/
@WorkerThread
void afterSendNetRequestRunOnWorkThread(@NonNull ZZNet zzNet);
/**
* 在callback的onDidCompleted方法前執(zhí)行,運(yùn)行在主線程
* @param zzNet
* @param response
*/
@MainThread
void beforeDidCompletedRunOnMainThread(@NonNull ZZNet zzNet, @NonNull ZZNetResponse response);
}
ZZNetProcessor,網(wǎng)絡(luò)請求及響應(yīng)核心處理器
網(wǎng)絡(luò)請求處理的抽象接口,把網(wǎng)絡(luò)請求過程拆分為處理請求和處理響應(yīng),可以根據(jù)業(yè)務(wù)需要添加實(shí)現(xiàn);目前有3個(gè)實(shí)現(xiàn)類,ZZNetStringProcessor(處理字符串類型的響應(yīng)數(shù)據(jù))、 ZZNetUploadProcessor(處理文件上傳)、 ZZNetDownloadProcessor(處理文件下載)。
@WorkerThread
public interface ZZNetProcessor {
/**
* 處理請求參數(shù)
* @param requestBuilder
*/
@WorkerThread
void handleRequestParams(@NonNull Request.Builder requestBuilder);
/**
* 處理請求響應(yīng)
* @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類型的響應(yīng)
* @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校驗(yàn)響應(yīng)結(jié)果
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中錯(cuò)誤碼及錯(cuò)誤描述,決定json是否可解析
*
* @param responseEntity 響應(yīng)實(shí)體數(shù)據(jù),需先設(shè)置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;
}
// 解析響應(yīng)數(shù)據(jù)
try {
netResponse.rawString = response.body().string();
netResponse.errorType = ZZNetErrorType.Success;
}catch (Exception e){
e.printStackTrace();
}
// validator校驗(yàn)響應(yīng)
ZZNetValidator appValidator = zzNet.getBuilder().getAppValidator();
if (appValidator != null){
appValidator.isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "appValidator校驗(yàn)響應(yīng)不通過,終止");
return;
}
}
if (zzNet.getValidator() != null){
zzNet.getValidator().isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "apiValidator校驗(yàn)響應(yīng)不通過,終止");
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);
}
/**
* 處理下載請求參數(shù)
* @param requestBuilder
*/
@WorkerThread
private void handleDownloadRequestParams(Request.Builder requestBuilder){
if (canAddRangeHeader()){
// 斷點(diǎn)續(xù)傳下載
requestBuilder.header("RANGE", "bytes=" + zzNet.getSaveFile().length() + "-");
}
}
/**
* 能否拼接斷點(diǎn)續(xù)傳文件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;
}
/**
* 處理文件下載響應(yīng)
* @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(), "文件已經(jīng)下載完畢,不需要再次下載,終止本次請求");
netResponse.errorType = ZZNetErrorType.Success;
return;
}
if (!response.isSuccessful()){
return;
}
// validator校驗(yàn)響應(yīng)
ZZNetValidator appValidator = zzNet.getBuilder().getAppValidator();
if (appValidator != null){
appValidator.isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "appValidator校驗(yàn)響應(yīng)不通過,終止");
return;
}
}
if (zzNet.getValidator() != null){
zzNet.getValidator().isCorrectWithCallBackDataRunOnWorkThread(zzNet, netResponse);
if (netResponse.errorType != ZZNetErrorType.Success){
LogUtils.d(zzNet.getUrl(), "apiValidator校驗(yàn)響應(yīng)不通過,終止");
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("當(dāng)前是否斷點(diǎn)續(xù)傳%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;
}
}
/**
* 是否支持?jǐn)帱c(diǎn)上傳
* @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;
}
/**
* 寫數(shù)據(jù)到文件
* @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;
}
}
自定義重試規(guī)則
允許請求失敗后的重試操作,可以自定義重試規(guī)則,重試次數(shù)等。例如:客戶端支付成功后需要查詢支付狀態(tài),服務(wù)端的支付狀態(tài)依賴第三方支付平臺的通知,為降低通知延遲對客戶端的影響,可以自定義重試規(guī)則,當(dāng)支付失敗時(shí)多重試幾次。
重試機(jī)制規(guī)則抽象接口
/**
* 重試機(jī)制規(guī)則
*/
public interface ZZNetRetryRule {
/**
* 是否需要重試,可在此定義重試規(guī)則,注意此函數(shù)是運(yùn)行在工作線程中
* @param zzNetResponse
* @return
*/
@WorkerThread
boolean needRetry(ZZNetResponse zzNetResponse);
}
重試機(jī)制具體使用方法
/**
* 自定義重試規(guī)則,App端支付成功后查詢支付狀態(tài)
* ,如果查詢結(jié)果為支付失敗,等待500ms后繼續(xù)查詢(最多3次)。
*/
public static class QueryPayStatusRetryRule implements ZZNetRetryRule {
// 該函數(shù)運(yùn)行在工作線程中
@Override
public boolean needRetry(ZZNetResponse response) {
// 可根據(jù)zzNetResponse定義重試規(guī)則
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/
版權(quán)聲明: 本博客所有文章均為原創(chuàng),轉(zhuǎn)載請注明作者及出處