Android 二次封装网络加载框架

作者: 程序员徐公 | 来源:发表于2017-11-24 09:30 被阅读440次

    Android 二次封装网络加载框架

    写在最前面

    开发当中,在请求网络的时候,大家或多或少都会使用一些第三方框架,Android-Async-Http、 Volley、XUtils、Okhttp、Retrofit 等。这些框架减少了我们的很多工作量,同时也对侵入了我们的项目。

    大家回顾一下手头上的项目代码,是不是或多或少存在这样那样的历史遗留问题,第三方框架调用混乱,没有封装,或者封装不测底。如果要替换框架,很有可能要对项目大动干戈。


    封装的必要性

    • 随着需求的变更或者时间的迁移,某些框架可能已经不能满足我们的需求,我们需要使用新的框架来代替。
    • 对第三方框架进行封装,是为了达到对模块项目的控制,已最小的代价替换框架,达到对项目的控制。

    或许你会认为对第三方框架再进行一次封装,没有必要。那是你没有尝试过一行行复制张贴代码,进行替换。

    有人可能会想, AS 不是有批量替换的功能吗,干嘛还要封装?

    • 首先,你能保证调用到的地方参数都一模一样吗?
    • 第二,能用更优雅的方式实现,为什么要用最粗暴的方式去替换。

    接下来我们先来看一下 okHttp 的使用


    Okhttp 的使用

    OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().
            connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(new LoggingInterceptor())
            .cache(new Cache(mContext.getExternalFilesDir("okhttp"),cacheSize));
    client=mBuilder.build();
    
    Request.Builder builder = new Request.Builder().url(url).tag(option.mTag);
    builder=configHeaders(builder,option);
    
    Request build = builder.build();
    
    client.newCall(build).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            handleError(e, iResponseListener);
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            handleResult(response, iResponseListener);
        }
    });
    
    
    

    如果不进行封装,okHttp 请求网络大概是这样的,想一下,如果我们在项目中都这样使用,要替换框架,那花费的工作量要多大。

    不过这种方法,在项目中大多数人不会这样使用,至少都会封装成为一个工具类。封装完成之后如下。

     public static void  doGet(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption,
                          final IResponseListener iResponseListener){
           OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().
                   connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS)
                   .readTimeout(30, TimeUnit.SECONDS)
                   .addInterceptor(new LoggingInterceptor())
                   .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize));
           OkHttpClient cilent = mBuilder.build();
    
           Request.Builder builder = new Request.Builder().url(url);
    
    
           Request build = builder.build();
    
           cilent.newCall(build).enqueue(new Callback() {
               @Override
               public void onFailure(Call call, IOException e) {
                   handleError(e, iResponseListener);
               }
    
               @Override
               public void onResponse(Call call, Response response) throws IOException {
                   handleResult(response, iResponseListener);
               }
           });
       }
    
        public static void doPost(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption,
                           final IResponseListener iResponseListener) {
            OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().
                    connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .addInterceptor(new LoggingInterceptor())
                    .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize));
            OkHttpClient cilent = mBuilder.build();
    
            url= NetUtils.checkUrl(url);
            final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url);
            FormBody.Builder builder = new FormBody.Builder();
            FormBody formBody = builder.build();
    
            Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag);
            Request request = requestBuilder.build();
            cilent.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    handleError(e,iResponseListener);
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    handleResult(response,iResponseListener);
                }
            });
        }
    

    这种封装成工具类的比完全没有封装的好了很多,但是还是存在一定的问题的。封装成工具类的话,别人完全有权限访问你这个工具类,他可以随时修改你工具类里面的实现,这给维护带来了一定的成本。那有没有更好的方法呢?

    大多数人都会想到的是封装统一网络接口,没错,确实是这样。于是,经过一番思考以后,我们可能写出以下的代码。

       void doGet(Context context,String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);
    

    如果我们需要动态配置请求头呢,请求 TAG 呢,这时候你会怎么写,继续增加参数吗?

    这时候接口可能如下:

    public interface NetRequest{
    
       void doGet(Context context,String url, final Map<String, String> paramsMap,final Map<String, String> headMap, String tag,final IResponseListener iResponseListener);
       
       
       ---
      }
    

    那以后如果要配置缓存路径呢,配置请求超时时间,读取超时时间呢,直接在方法中增加相应的参数?

    这样的做法是不太明智的,会导致接口越来越臃肿。

    既然这样,那有没有办法解决呢?

    首先,我们先回想一下,网络请求那些参数是必要的,那些是非必要的,即可有可无的。

    必要选项

    • url,请求网址
    • paramsMap ,请求参数
    • iResponseListener 请求结果的回调

    非必要选项

    • context 通常是用来配置配置一些缓存等一些信息
    • headMap 请求头
    • tag 请求 TAG,用来区分或者取消网络请求
    • connectTimeout 连接超时时间
    • readTimeout 读取超时时间
    • writeTimeout 写入超时时间

    了解完必要参数和非必要参数之后,我们的接口要怎样提取呢?

    不知道大家有没有注意到 okHttpClient 的构建,他将所有的网络配置都提取封装在 OkHttpClient,Request 中,在请求网络的时候减少了相应的参数,简洁灵活。

           OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().
                   connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS)
                   .readTimeout(30, TimeUnit.SECONDS)
                   .addInterceptor(new LoggingInterceptor())
                   .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize));
           OkHttpClient cilent = mBuilder.build();
    
           Request.Builder builder = new Request.Builder().url(url);
           client.newCall(builder.build()).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            handleError(e, iResponseListener);
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            handleResult(response, iResponseListener);
        }
    });
    
    

    看了 OKhttp 的代码,我们也可以依样画葫芦,我们可以将非必要参数封装在一个实体类 NetworkOption 当中,必要的参数作为方法参数,这样接口变成以下的形式。

    void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);
    
    void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener);
    

    对比直接在方法中增加相应的参数,是不是简洁很多。

    接着,我们一起来看一下 NetworkOption 的属性。基本上,只要 okhttp 可以配置的,我们都可以往里面配置。这里列举了一些常用的字段 ,baseUrl,请求标志 tag,请求头 mHeaders。-connectTimeout 连接超时时间,readTimeout 读取超时时间,writeTimeout 写入超时时间就不一一列举了。

    public class NetworkOption {
    
        /**
         * 网络请求的 TAG
         */
        public String mBaseUrl;
        public String mTag;
        public Map<String,String> mHeaders;
    
        public NetworkOption(String tag) {
            this.mTag = tag;
        }
    
        public static final  class Builder{
            public String tag;
            public Map<String,String> mHeaders;
            public String mBaseUrl;
    
            public Builder setTag(String tag){
                this.tag=tag;
                return this;
            }
    
            public Builder setHeaders(Map<String,String> headers){
                mHeaders=headers;
                return this;
    
            }
    
            public Builder setBaseUrl(String baseUrl) {
                mBaseUrl = baseUrl;
                return this;
            }
    
            public NetworkOption build(){
                NetworkOption networkOption = new NetworkOption(tag);
                networkOption.mHeaders=mHeaders;
                networkOption.mBaseUrl=mBaseUrl;
                return networkOption;
            }
        }
    }
    
    

    同时,考虑到 NetworkOption 对象的配置会比较复杂,这里我们采用了建造者模式来构建。有兴趣的话,可以参考我的这一篇博客。建造者模式(Builder)及其应用

    建造者模式的优点

    • 封装性很好,将产品本身与产品的创建过程解耦,对外屏蔽了对象的构建过程
    • 扩展性强,如果有新的需求,只需要增加新的具体建造者,无须修改原有类库的代码

    最后的封装实现

    NetRequest 接口的封装

    public interface NetRequest {
    
        void init(Context context);
    
         void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);
    
         void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener);
    
    
         void doPost(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);
    
         void doPost(String url, final Map<String, String> paramsMap, NetworkOption networkOption,
                     final IResponseListener iResponseListener);
        
    
         void cancel(Object tag);
    
    
    
    }
    

    可以看到,我们主要有几个方法

    • init 方法,主要用来配置一些初始化参数
    • doGet 有两个方法,其中一个方法是另外一个方法的重载,这样设计的目的是为了减少调用方法的时候减少方法参数的传递
    • doPost 跟 doGet 方法一样,就不说了
    • cancel 主要是用来取消网络请求的。在项目当中,在 Activity 或者 Fragment 销毁的时候,最好取消网络请求,不然可能导致内存泄露或者异常,如空指针异常等。

    OkHttpRequest 的实现

    OkHttp 的配置是非常灵活的,这样我们主要看一下怎么配置请求头,请求参数,以及怎样取消网络请求。

    public class OKHttpRequest implements NetRequest {
    
        //  -----  省略若干方法,有兴趣的话上 github 查阅
    
        @Override
        public void doGet(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) {
            doGet(url,paramsMap,null,iResponseListener);
        }
    
        @Override
        public void doGet(String url, Map<String, String> paramsMap, NetworkOption networkOption,
                       final   IResponseListener iResponseListener) {
            url= NetUtils.checkUrl(url);
            url=NetUtils.appendUrl(url,paramsMap);
            final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url);
            Request.Builder builder = new Request.Builder().url(url).tag(option.mTag);
            builder=configHeaders(builder,option);
    
            Request build = builder.build();
    
            getCilent().newCall(build).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    handleError(e, iResponseListener);
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    handleResult(response, iResponseListener);
                }
            });
        }
    
     
    
        private Request.Builder configHeaders(Request.Builder builder, NetworkOption option) {
            Map<String, String> headers = option.mHeaders;
            if(headers==null || headers.size()==0){
                return builder;
            }
            Set<Map.Entry<String, String>> entries = headers.entrySet();
            for(Map.Entry<String, String> entry: entries){
                String key = entry.getKey();
                String value = entry.getValue();
                // 添加请求头
                builder.addHeader(key,value);
            }
            return builder;
    
        }
    
        @Override
        public void doPost(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) {
            doPost(url,paramsMap,null,iResponseListener);
    
        }
    
        private FormBody.Builder configPushParam(FormBody.Builder builder, Map<String, String> paramsMap) {
            if(paramsMap!=null){
                Set<Map.Entry<String, String>> entries = paramsMap.entrySet();
                for(Map.Entry<String,String> entry:entries ){
                    String key = entry.getKey();
                    String value = entry.getValue();
                    builder.add(key,value);
                }
            }
            return builder;
        }
    
        @Override
        public void doPost(String url, Map<String, String> paramsMap, NetworkOption networkOption,
                           final IResponseListener iResponseListener) {
            url= NetUtils.checkUrl(url);
            final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url);
            // 以表单的形式提交
            FormBody.Builder builder = new FormBody.Builder();
            builder=configPushParam(builder,paramsMap);
            FormBody formBody = builder.build();
    
            Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag);
            requestBuilder=configHeaders(requestBuilder,option);
            Request request = requestBuilder.build();
            getCilent().newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    handleError(e,iResponseListener);
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                   handleResult(response,iResponseListener);
                }
            });
        }
    
        @Override
        public void cancel(Object tag) {
            if(client!=null){
                if(client != null) {
                // 在等待队列中查找是否有相应的请求
                    for(Call call : client.dispatcher().queuedCalls()) {
                        if(call.request().tag().equals(tag))
                            call.cancel();
                    }
                // 在正在请求的请求队列中查找是否有相应的请求
                    for(Call call : client.dispatcher().runningCalls()) {
                        if(call.request().tag().equals(tag))
                            call.cancel();
                    }
                }
            }
        }
    
    
    }
    

    OKHttpRequest 的实现其实很就简单,主要是根据 NetworkOption 做相应的配置,不熟悉 okhttpRequest 的用法的可以参考该博客。OkHttp使用完全教程

    VolleyRequest

    VolleyRequest 的实现也不说了,也是根据 NetworkOption 做相应的配置,有兴趣的话可以点击查看 Networklibrary

    NetworkManger

    考虑到项目当中有可能要切换框架,这里我们使用简单工厂模式来实现,方便我们框架的随时切换。

    UMl 类图如下

    image
    • NetRequest 统一的网络接口
    • VolleyRequest ,Volley 请求网络的具体实现
    • OkhttpRequest,Okhttp 请求网络的实现
    • NetManger ,根据参数的不同返回不同的网络实现

    最后考虑到网络加载在项目中是经常用到的,为了节省资源,提高速度,我们结合了单例模式,最终的实现如下:

    public class NetManger {
    
        private static NetRequest instance;
        private static Context mContext;
    
        public static NetRequest getRequest(){
            return instance;
        }
    
        static HashMap<String,NetRequest> mMap=new HashMap<>();
    
        public static void  init(Context context){
            instance = OKHttpRequest.getInstance();
            mContext = context.getApplicationContext();
            instance.init(NetManger.mContext);
        }
    
    
    // 采用反射的形式实现,这样有一个好处是,以后增加新的实现类的话,我们只需要传递相应 的 Class,
    //而不需要更改 NetManger 的代码
        public static <T extends NetRequest> NetRequest getRequest(Class<T> clz){
            String simpleName = clz.getSimpleName();
            NetRequest request = mMap.get(simpleName);
            if(request ==null){
                try {
                    Constructor<T> constructor = clz.getDeclaredConstructor();
                    constructor.setAccessible(true);
                    request = constructor.newInstance();
                    request.init(mContext);
                    mMap.put(simpleName,request);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
            instance=request;
            return request;
    
        }
    
    }
    

    你问我答

    1) NetManger 怎么使用

    首先你需要在 Application 中调用 NetManger 的 init 方法

    NetManger.init(application);
    

    默认的实现是使用 okhttp 实现,采用单例模式

    NetManger.getRequest().doGet(url, mMap, new IResponseListener() {
        @Override
        public void onResponse(String response) {
            LogUtil.i(TAG,"onResponse:  response ="+response);
        }
    
        @Override
        public void onFail(HttpException httpException) {
            Log.i(TAG, "onFail: httpException=" +httpException.toString());
        }
    });
    

    2) NetManger 怎么切换具体的实现

    加入我们想切换成 Volley,那么我们在传递参数的时候只需要传递VolleyRequest.class 即可

    NetManger.getRequest(VolleyRequest.class).doPost(url, mMap, new IResponseListener() {
        @Override
        public void onResponse(String response) {
            mTv.setText("post 请求\n"+response);
            LogUtil.i(TAG,"onResponse:  response ="+response);
        }
    
        @Override
        public void onFail(HttpException httpException) {
            Log.i(TAG, "onFail: httpException=" +httpException.toString());
        }
    });
    
    
    
    1. 如果我们不想使用 okhttp,Volley,而是想使用 XUtils或者 retrofit,有没有办法实现呢?

    答案是坑定的,我们只需要自己增加一个实现类 implement NetRequest 接口即可。然后在使用传递参数的时候传递相应的 Class 即可。

    NetManger.getRequest(XUtilsRequest.class).doPost(url, mMap, new IResponseListener() {
        @Override
        public void onResponse(String response) {
            mTv.setText("post 请求\n"+response);
            LogUtil.i(TAG,"onResponse:  response ="+response);
        }
    
        @Override
        public void onFail(HttpException httpException) {
            Log.i(TAG, "onFail: httpException=" +httpException.toString());
        }
    })
    

    题外话

    看了上面网络框架的二次封装,对图片框架,json 解析框架的封装,你是不是也想到了什么,懂得怎样封装了吧。


    Networklibrary

    二次封装图片第三方框架——简单工厂模式的运用

    建造者模式(Builder)及其应用

    Android 二次封装网络加载框架

    最后的最后,卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。


    image

    相关文章

      网友评论

      • 自由小子0918:提交的图片是file文件?
      • 自由小子0918:当要上传图片的时候,参数要怎么提交呀?一张或者多张?
      • 程序员徐公:这个方法是动态替换的,即项目中可以同时使用两种框架
      • android_cyw:不明白博主为什么要增加这么一个方法NetRequest getRequest(Class<T> clz)去替换请求库,项目中替换请求库一般都是全局替换的吧?全局替换的话在NetManger 的init方法中修改不就好了吗?

      本文标题:Android 二次封装网络加载框架

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