美文网首页
Android 网络层Library设计

Android 网络层Library设计

作者: gcoder_io | 来源:发表于2017-03-28 23:37 被阅读180次

    网络层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/
    版权声明: 本博客所有文章均为原创,转载请注明作者及出处

    相关文章

      网友评论

          本文标题: Android 网络层Library设计

          本文链接:https://www.haomeiwen.com/subject/hfgsottx.html