Android 網(wǎng)絡(luò)層Library設(shè)計(jì)

網(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)載請注明作者及出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容