美文网首页Android开发Android科普知识库Android开发经验谈
从OkHttp的使用谈谈App网络层搭建的思维过程

从OkHttp的使用谈谈App网络层搭建的思维过程

作者: 蚍蜉一生 | 来源:发表于2019-03-09 18:53 被阅读136次

前言

    在Android开发中,网络请求是每个开发者的必备技能。当前也有很多优秀、开源的网络请求库。例如:

其中Retrofit是对OkHttp的封装,Android-async-http是对HttpClient的封装,利用这些网络库开发者可以极大提升编码效率。即便这些优秀的网络库可以很方便进行网络请求,但大多数团队依旧要搭建App网络层,甚至把网络层单独封装成库使用,为什么呢?所以本文首先就要讨论:

  1 为什么要搭建App网络层呢?

    当知道搭建网络层的必要性之后,便摩拳擦掌准备去大干一番,但很快便面对一个问题:

  2 如何一步步搭建App网络层呢?

    终于搭建好了网络层,但使用时肯定能发现不少bug和可以优化的地方,那么:

  3 应该用什么样的思想来指导改进网络层呢?

在回答上述问题之前,先了解下网络请求的基本流程:

网络请求基本流程

    网络请求的实质是去查看、修改远程计算机(包括服务器)上的信息,仅从客户端来看基本流程如下:


网络请求流程.png

    如图示,网络请求的基本流程就是如此简单,和把大象放入冰箱一样,都是三步。接下来我们在代码级别来看看:

如何进行网络请求

  以使用OkHttp框架访问百度首页为例子

       //构造一个HttpClient 相当于设置个人邮箱。
       OkHttpClient client=new OkHttpClient();
       //创建Request 对象,相当于写信。
       Request request = new Request.Builder()
               .url("http://www.baidu.com")
               .build()
       //将Request封装为call,相当于把信放进邮箱,成为设置后待发送的信件
       Call call = client.newCall(request);
       // 放置到请求队列、开始发送并等待回复,相当于邮箱开始发动信件,并等待对方回复。
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
              // 当请求被取消、连接中断、找不到服务器等问题会调用这个接口
           }

           @Override
           public void onResponse(Call call, final Response response) throws IOException {
               // 远程服务器成功返回调用
               final String res = response.body().string();
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       Log.e("TAG"," "+res);
                   }
               });
           }
       });

   }

    由上可以看到用OkHttp框架进行网络请求逻辑清晰简单,跟我们用邮件跟朋友交流差不多。首先是设置邮箱(如果没有什么特殊需求就用默认设置、如上文)+写邮件内容,然后把写好邮件后放到设置好的邮箱里面,最后点击发送,等待朋友的回复,当朋友回复了就去查看处理。

为什么要搭建网络层

    知道如何用OkHttp后很兴奋,于是用这一套开始了网络请求之旅,so easy!!复制-粘贴-修改,复制-粘贴-修改,复制-粘贴-修改...终于做了七八个网络请求,一看任务量完成了六分之一,啊--累死宝宝了!!
    不行了不行了,要喝杯luckin coffee鼓舞下士气,说走就走,喝着咖啡想着回来就一口气加班搞定它。突然想起OOP重要原则-代码复用原则,一拍脑子我TM真是个憨货,把这些请求的共同部分提取出来,对外提供更简单的接口、这样用着方便不容易出错、以后有问题修改工作量也大大减少了,这就是我们搭建网络层第一个原因:

    一、近似业务模型的代码复用--方便使用和修改;

    说干就干,撸起袖子正准备上场,再次灵光一闪,不对不对,这次要好好思考下整个完美的,半途而废啥的最浪费时间了!这里要说明下,App网络层是我们抽离出来的一个单独模块,所以网络层搭建跟设计实现一个单独的网络请求库一样,首先看看网络上优秀的开源库都有哪些功能可以借鉴。
    去github上看了star比较多的一些OkHttp库封装,整理了下功能列表:

  • 一般的get请求
  • 一般的post请求
  • 基于Http Post的文件上传(类似表单)
  • 文件下载/加载图片
  • 上传下载的进度回调
  • 支持取消某个请求
  • 支持自定义Callback
  • 支持HEAD、DELETE、PATCH、PUT
  • 支持session的保持
  • 支持自签名网站https的访问,提供方法设置下证书就行
  • 支持RxJava
  • 支持自定义缓存策略

    这些网络库是很好参考借鉴,但是,它们跟我们的业务结合不紧密、直接用还是会造成:

    1 每个网络请求都要加上业务逻辑;
    2 有不少多余功能,导致网络层很大,可能影响效率 ;
    3 团队特殊要求达不到,比如利用三方实现DNS防劫持。

    所以需要仔细去分析业务、梳理网络请求类型。很快我们发现需要四种缓存策略:立即请求、缓存10s、缓存1h、缓存24h,所以搭建网络层第二个原因也是功能点:

    二、 全局,全团队统一的缓存策略;

    接下来需要去沉下心,去谷歌搜索下网络请求问题,针对业务场景去思考下一旦上线会遇到什么样的问题,很快确定了第二个问题,DNS劫持问题,所以我们搭建网路层第二个原因也是重要功能点:

    三、 全局、团队统一的 DNS反劫持;

    为了防止遗漏,又去找团队成员、上级老大聊天请教,看有什么特殊要求。这时候运营部门提了个需求,统计dns劫持率,老大说出现网络问题要能够快速定位。所以搭建App网络层第四个重点:

    四、 全局、团队统一的网络请求统计和关键log

    明确了什么要搭建App的网络层,以及必须具备哪些功能,接下便开始正面遭遇问题:

如何一步步搭建App网络层呢

    人类做事习惯上是顺序进行的,这就决定了人类的可靠性思维-逻辑思维是线性的,进而决定了人类的可靠性表达也是线性的!越顺滑的思路,越顺滑的表达,越容易被人理解与接受。
    所以当遭遇事件类相关任务,又不知道怎么做的时候,从事件整体业务流程进行分析是一个很好的切入点。
    从整体业务流程出发、使我们不至于迷失,但到了每一个环节该如何做,就需要一些指导与规范,那必须就是:
    SDK设计原则: A 简洁易用 B 功能完备 C 扩展性好。
    当然 “简单吗?优美吗?” 也是每个软件工程师须时时反问的。

一 简洁易用-从用户使用与理解角度考虑对外接口与模块划分

    现在假设网络层已经搭建好了,用户网络层发起网络请求,所以遇到第一个问题节点就是网络层对外接口设计。接口设计原则是简单!简单!简单!不仅仅是代码看着的简单,而是在于用户易于理解和使用的简单!
    上文提到通过OkHttp网络请求大概分为四步:
    1 构建、设置OkHttpClient--相当于设置电子邮箱;
    2 构建Request请求内容--相当于写信;
    3 用OkHttpClient把Request转换成为待发送的Request-Call--相当于把信件放入邮箱,变为邮件;
    4 发送请求,等待回复,并处理。
    现在就设想最简单的网络请求是怎么样的:额、大概是这样的吧----客户端添加一个请求(由Url构建出来)- 发送出去-等待回复处理,比如如下

   new HttpClient.HttpClientBuilder().build()                        // 设置邮箱
                .addRequest(new GetRequest("http://www.baidu.com"))  //写信并添加到邮箱或者叫写邮件
                .sendRequest(new DefaultCallback(){          //发送邮件等待回复
                    @Override
                    public void onResponse(Call call, Response response) {
                        super.onResponse(call, response);
                        Log.e("JG","response="+response.toString());  //服务器成功返回
                    }
                    @Override
                    public void onFailure(Call call, IOException e) {
                        super.onFailure(call, e);
                    }
                });
 

    从这个简单流程出发、App网络层可抽象出如下模块:
    1 HttpClient 客户端模块 ;
    2 Request 请求模块 ;
    3 Callback 回复处理模。
    据此我们可以把App网络层划分为这三个大的模块。同理,在每一个大模块内部也要根据流程划分为更细的模块
    这一小节主要探讨模块设计与划分,核心思想是跳出代码逻辑,从整体业务流程出发,找到关键的处理节点,从而对网路层进行模块设计与划分。但到底这种设计是否行得通,还要从每一个具体业务实现进行重新审核。

功能完备-从业务需求实现出发审查模块划分的合理性

    接下来我们开始分析每一个业务需求、验证刚才的模块划分是否合理。

1 get/post请求

    最初我们从get/post请求开始思考业务逻辑,所以这个可以跟模块完美结合,get与post的区别在我们这里就是不同的Request封装。

2 DNS防劫持

    DNS-Domin Name System域名解析系统,将域名(例如:“http://www.baidu.com”)转换为IP地址(例如:220.181.112.244),这个解析过程涉及到本地缓存,运营商缓存,各级别域名服务器等,它是Http协议的一部分。
    DNS劫持劫持又称域名劫持,本质就是通过攻破DNS解析过程中某些环节与节点,来给用户返回假网址IP。
    既然DNS劫持结果是返回错误的IP,那是否直接用Ip来访问就可以防止DNS劫持了?这就是DNS反劫持的主要思想:拿到域名->来通过Http请求->访问权威三方(比如阿里的HTTPDNS)提供的DNS解析服务器->三方告诉你IP,便可通过该IP来进行网络请求了。
    OkHttp实现DNS反劫持由上文可知看到DNS反劫持关键在于,用三方的DNS解析系统代替系统默认的DNS解析系统!所以OkHttp提供了一个抽象的DNS类,用户只用继承这个类,便可以方便的接入自定义的DNS解析系统。

public class HttpDns implements Dns {
    private static final String TAG = HttpDns.class.getSimpleName();

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        Log.v(TAG, "lookup:" + hostname);
        //只需要在lookup方法中调用HttpDns的SDK去获取IP
        // 如果获取到了就返回一个List<InetAddress>的值
        // 如果购买了阿里的HttpDns服务就可以用
        //默认又返回系统的DNS解析,这就叫DNS降级
        return SYSTEM.lookup(hostname);
    }
}

然后通过OkHttClient设置此DNS,HttpClient模块主要就是封装OkHttClient,所以审核通过!可行再次+1。

3 缓存设置

    缓存设置指缓存的位置、大小、时间,OkHttp通过两种方式可以实现缓存设置:

//1  通过库cache接口
new OkHttpClient.Builder()
                .cache(new Cache(file, cacheSize)) // 配置缓存
// 2 通过拦截器
new OkHttpClient.Builder()
                .cache(new CacheInterceptor(){
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);
                return response;
            }
        }); 

具体做法请参照:okhttp 缓存实践
它依旧可以通过OkHttpClient完成,我们依旧只需要把这部分封装在HttpClient模块就好。

4 全局log统计

    全局log统计依旧是使用拦截器完成,上代码

public class LogInterceptor implements Interceptor {
    private static final String TAG=LogInterceptor.class.getSimpleName();
    @Override
    public Response intercept(Chain chain) throws IOException {

       // 把请求request拦截下来
        Request request = chain.request();
       //可以打印请求内容
        Log.v(TAG,"request method="+request.method()+",request url="+request.url());
        //继续向下一个传递处理,并拦截到处理结果response
        Response response = chain.proceed(request);
        // 可以打印返回内容
        Log.v(TAG,"response="+response.toString());
        return response;
    }
}

依旧是封装在HttpClient模块。全部审核通过,不过从最初设计也可以知道,我们从OkHttp用法借鉴思想,肯定是可行的。

三、 扩展性好-从开闭原则进行模块间解耦操作

    现在各个模块分工明确、业务功能基本全部实现了,但随着业务的发展、我们可能会有新类型的请求、新种类的返回处理等,所以一开始我们就要考虑整个网路层的扩展性。
    扩展性好的关键在于模块间耦合度低,解耦的关键在于依赖抽象,也就是实体模块(比如一个实体类)之间没有直接调用关系,实体模块之间数据传递要通过中间层(抽象类或者接口)。
再次回顾下我们最初设计的调用接口:

   new HttpClient.HttpClientBuilder().build()                        // 设置邮箱
                .addRequest(new GetRequest("http://www.baidu.com"))  //写信并添加到邮箱或者叫写邮件
                .sendRequest(new DefaultCallback(){          //发送邮件等待回复
                    @Override
                    public void onResponse(Call call, Response response) {
                        super.onResponse(call, response);
                        Log.e("JG","response="+response.toString());  //服务器成功返回
                    }
                    @Override
                    public void onFailure(Call call, IOException e) {
                        super.onFailure(call, e);
                    }
                });
 

其中 addRequest

 public ReadyRequest addRequest(BaseRequest baseRequest){

        return new ReadyRequest(this,baseRequest);


    }

其中GetRequest是继承自BaseRequest,这样HttpClient这个实体类就没有直接和实体类GetRequest相关,这就是通过依赖抽象进行了解耦合。当我们需要一种新的Request,只需继承BaseRequest就可以方便的扩展使用。

如何不断优化App网络层

    现在我们做好了一个基本可以使用、并且具备一定扩展性的网络层,但是使用过程中肯定可以发现bug和可以优化地方,那么如何一步步把我们的网络层从普通变为卓越呢?
    那首先我还是会问一个问题,对一个具体APP来说怎么样才是一个顶级的网络层呢?
    1 安全性高;
    2 网络访问速度快、性能优越;
    3 用户使用方便。
看了一些优秀的网络层改进过程,暂时总结出来点如下:
1 统计网络请求常见bug与风险,增加预防处理;
2 统计业务流程想关性,进行预加载或者缓存;
3 统计用户使用习惯和思维、统一智能设置或者修改接口;
4 不断探讨学习其他优秀的软件设计思维与思维,进行部分重构。
这些都是大数据与AI思维的延伸,这部分还在继续思考中,如果各位有什么想法、欢迎在下面交流评论!!

总结

    本文从简单的网络请求开始,谈到了为了业务需求方便使用来搭建自己的App网络层,进而探讨了如何一步步实现一个App网络层,并不断优化、完善。其中重点是想重现了下架构,优化功能层或者说SDK的一个思维过程:

    1 跳出代码,从整体业务流程进行初步模块划分;

    2 从方便(用户)理解使用的角度设计调用接口;

    3 从业务具体实现出发,重新审视模块划分;

    4 用软件设计思维与模式再次审视当前结构设计,增加扩展性、方便用户灵活扩展。

    5 用大数据与AI进化思维进行不停优化;

    同时,也花了一两天时间,手动搭建一个App网络层来验证思维过程的可行性。gitHub地址:https://github.com/kingkong-li/networklib
欢迎各位大神前来交流、共同开发学习~~

相关文章

网友评论

    本文标题:从OkHttp的使用谈谈App网络层搭建的思维过程

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