优雅设计封装基于Okhttp3的网络框架(一):Http网络协议与Okhttp3解析

如今Android开发中Okhttp已成为主流网络框架,内置丰富、全面、强大的网络请求功能,也为开发者提供了api,但是在项目开发中的大量使用,会出现api的重复调用、代码冗杂等现象。应当封装一个适用于项目的网络框架,便于使用。

此系列文章旨于:基于okhttp3原始框架来设计封装一个满足业务需求、扩展性强、耦合度低的网络框架。具体框架功能为:

  • 封装基本的网络请求
  • 扩展其对数据库的支持
  • 对多文件上传、多线程文件下载的支持
  • 对Json数据解析等功能的支持

此系列文章将详细记录其封装过程,从底层框架的选择、基础知识、设计理念到架构分析、封装代码,自顶向下来完成网络框架的设计封装,最后附上源码,下面开始!


一. 主流网络框架分析与选择

1. 常用网络框架介绍

在Android网络框架并不成熟的时候,开发者在开发过程中往往会遇到几大难题:如何去访问网络数据?如何做本地存储?如何做图片缓存等类似问题。随着Android不断发展,继而推出了以下几大常用框架:

(1)Volley

谷歌研发的一款底层基础网络框架,Volley在Android整个发展中是出现较早的,早期的项目使用的便是Volley框架,但是以当今的角度去分析它还是有些不足的地方,例如不支持文档、文件的下载,更倾向于轻量级数据的请求。

优点:

  • 默认Android2.3及以上基于HttpURLConnection,2.3以下使用基于HttpClient。
  • 符合Http 缓存语义 的缓存机制(提供了默认的磁盘和内存等缓存)。
  • 请求队列的优先级排序(网络框架的基本必备)。
  • 提供多样的取消机制。
  • 提供简便的图片加载工具(其实图片的加载才是我们最为看重的功能)。

缺点:

  • 不能下载文件。

(2)Android-async-http

提供了Http请求的大部分功能,绝对满足网络请求的各种需求,支持智能重试、gzip压缩等功能,是一个功能全面的网络框架,而且在github上start星数是Volley的两倍。

优点:

  • 在匿名回调中处理请求结果
  • 在UI线程外进行http请求
  • 文件断点上传
  • 智能重试
  • 默认gzip压缩
  • 支持解析成Json格式
  • 可将Cookies持久化到SharedPreference

缺点:

  • 已停止维护更新,新功能需自己实现

(3)Afinal 和 XUtils

国人研发的框架,不仅仅提供网络请求的功能,还支持数据库管理、图片下载缓存等功能。虽然功能丰富,但是没有只专于网络请求的框架功能细致、全面,后期维护成本很高。此框架较适用于小型快速开发。

四大模块功能:

  • 数据库模块: android中的orm框架,使用了线程池对sqlite进行操作。
  • 注解模块: android中的ioc框架,完全注解方式就可以进行UI绑定和事件绑定。无需findViewById和setClickListener等。
  • 网络模块:通过httpclient进行封装http数据请求,支持ajax方式加载,支持下载、上传文件功能。
  • 图片缓存模块:通过FinalBitmap,imageview加载bitmap的时候无需考虑bitmap加载过程中出现的oom和android容器快速滑动时候出现的图片错位等现象。

(4)Okhttp

由square公司研发(开源界最著名的两个公司:Square、Facebook),OkHttp是一款优秀的HTTP框架,大致功能为:

  • 支持get请求和post请求,支持基于Http的文件上传和下载
  • 支持加载缓存图片
  • 支持下载文件透明的GZIP压缩
  • 支持响应缓存避免重复的网络请求
  • 支持使用连接池来降低响应延迟问题。

就目前而言,Okhttp网络请求框架已被广泛应用成为主流框架,而且在Android6.0时已被集成到系统中默认底层协议,可以看出谷歌对Okhttp的满意度。


(5)Retrofit
本质上就是在Okhttp上做了相应的封装,网络底层交互还是Okhttp,封装如下:

  • 支持okhttp
  • 注解处理,简化代码
  • 支持上传和下载文件
  • 支持自己更换解析方式
  • 支持多种http请求库


2. 网络框架选择标准

参考标准:

  • 学习成本
  • 文档是否齐全
  • github 星数量
  • 现在是否有人维护
  • 流行程度
  • 代码设计是否有借鉴性
  • 代码体积

选择

综合以上考虑,本次封装的网络框架是基于Okhttp3,第二点从架构设计的层面来分析本次封装的网络框架,此点较为重要!




二. 架构设计分析 ★ ★ ★ ★ ★

在Android开发当中往往会涉及到设计模式的采用,例如MVC、MVP,MVVM等,但是在这个网络框架设计并未采用任何设计模式,主要是按照分层次的划分来实现解耦的目的,方便于此框架以后拓展修改

这里写图片描述

如上图所示,此框架可以分为三个层次

  • 第一层:便于框架扩展,第一层即最底层是Http InterfaceAbstact,例如Http中的Headers、Request、Response等通用的原生接口。

  • 第二层:有了第一层请求接口定义,便于第二层对接口的实现,此框架采用两种方式对接口进行实现,分别是Okhttp和原生的HttpURLConnection。通过这两个相关的API去实现整个Http请求和响应的过程,若还想要做相应的拓展,采用别的第三方http请求库,在此处可增加。(已经预先在第一层定义了足够多的接口实现网络请求的回调,第一层可无需修改)对于整个上层业务来说,无需直接接触到底层Okhttp、HttpURLConnection具体实现,所以提供二次封装的 HttpProvider ,暴露接口给上层调用。(具体底层是调用Http还是HttpURLConnection取决于配置,首先判断Okhttp依赖在项目中是否存在,若有则主要采用Okhttp来进行网络请求,否则采用HttpURLConnection)

  • 第三层:即最上层由 WorkstationConvert组成。Workstation 的中文意思是工作站,用来处理一些线程的调度分发和任务的队列,之所以将它设计在最上层,因为整个多线程、队列机制是与业务层紧密相关的。Convert是为上层开发者提供了更好的接口封装,用于接口返回类型转换、数据解析,例如json、xml等。





三. Http协议基础内容

1. 什么是Http协议?

HTTP(Hypertext Transfer Protocol),即超文本传输协议。是WWW浏览器和WWW服务器之间的应用层通讯协议。HTTP协议是基于TCP/IP之上的协议,它不仅保证正确传输超文本文档,还确定传输文档中的哪一部分,以及哪一部分内容首先显示(如文本先于图形)。


2. Http 版本区别

这里写图片描述


3. Http的请求方式总结

方式名称 含义
GET 请求获取Request-URI所标识的资源
POST 在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应信息报头
PUT 请求服务器存储一个资源,并用Request-URI作为其标识
DELETE 请求服务器删除Request-URI所标识的资源
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项

4. Http协议的特点

  • 支持客户/服务器模式。
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、 HEAD、POST。每种方法规定了客户与服务器联系的类型不同。 由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活: HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  • 无连接: 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求, 并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态: HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。 缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每 次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

5 . 请求头信息

请求头 说明
User-Agent 中文名为用户代理,是Http协议中的一部分,它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识
Referer 先前网页的地址,当前请求网页紧随其后,即来路
Cache-Control 指定请求和响应遵循的缓存机制
Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
If-Match 只有请求内容与实体相匹配才有效
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变

这里只介绍举例部分重要的请求响应头,关于更详细信息可看:
http://tools.jb51.net/table/http_header


6 . 响应头信息

应答头 说明
Content-Encoding 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。
Content-Type 表示后面的文档属于什么MIME类型。
Date 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
Expires 过期时间
Last-Modified 档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。

7 . 状态码信息

HTTP状态码分类

分类 描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

较常用状态码

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误




四. Okhttp使用解析

以上基础HTTP网络知识在后续的网络框架编码中会涉及到,所以需要稍作了解。在学习以上知识点后,下面举例介绍使用Okhttp3框架请求网络常用的方式,这里只介绍后续封装使用到的请求方式。(默认读者有基本使用Okhttp3基础,网上使用教程很多,可自行搜查)

1. 同步请求和异步请求

大多数情况异步请求较于同步请求更为广泛,因为同步请求的过程中容易阻塞到线程,而异步请求通过内部队列维护完美避免此问题,并且在回调中处理请求结果更符合需求。典型的使用方法如下:

public class AsyncHttp {

    /*
    *   同步请求(会阻塞到线程)
    * */
    public static void sendRequest(String url) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                System.out.println(response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /*
    *   异步请求
    * */
    public static void sendAsyncRequest(String url) {
        System.out.println(Thread.currentThread().getId());
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    System.out.println(Thread.currentThread().getId());
                }

            }
        });
    }

    public static void main(String args[]) {
        System.out.println(0/100.0);

        sendRequest("http://www.baidu.com");
//        sendAsyncRequest("http://www.baidu.com");
    }
}

2. http请求头与响应头请求

在第二节中介绍了Http协议及请求头、响应头等相关知识,在使用Okhttp3框架请求网络时可以显式调用、设置、获取相关信息,典型使用如下:

public class HeadHttp {

    public static void main(String args[]) {
        String str = "1234";

        System.out.println(str.substring(0,str.length() - 3));


        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().
                url("http://www.imooc.com/static/sea-modules/seajs/seajs/2.1.1/sea.js").
                addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36").
                addHeader("Range", "bytes=2-").
                addHeader("Accept-Encoding", "identity").
                build();
        try {
            Response response = client.newCall(request).execute();


//            System.out.println(response.body().string());
            System.out.println("size=" + response.body().contentLength());
            System.out.println("type=" + response.body().contentType());
            if (response.isSuccessful()) {
                Headers headers = response.headers();
                for (int i = 0; i < headers.size(); i++) {
                    System.out.println(headers.name(i) + " : " + headers.value(i));
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. get请求之添加参数

get请求为Http的请求方式中最常用的一种,访问URL获取资源,大多数情况url通过参数组成而来,以下代码则介绍通过添加参数的方式来获取最终url进行get请求,典型使用如下:

public class QueryHttp {

    public static void main(String args[]) {

        OkHttpClient client = new OkHttpClient();
        HttpUrl httpUrl = HttpUrl.parse("https://api.heweather.com/x3/weather").
                newBuilder().
                addQueryParameter("city", "beijing").
                addQueryParameter("key", "d17ce22ec5404ed883e1cfcaca0ecaa7").
                build();
        String url = httpUrl.toString();
        System.out.println(httpUrl.toString());
        Request request = new Request.Builder().url(url).build();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                System.out.println(response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里写图片描述


4. post请求之添加参数

上一点介绍完get请求后,必然联想到post请求,get请求中的添加的参数信息全部显示在url上,根本上决定了get请求只适用于资源的资源获取显示,需要传送重要数据给服务器时需要使用post请求方式,不仅对传送参数大小限制少,而且更为安全,典型的应用有登录注册。使用方式如下:

public class PostHttp {
    public static void main(String args[]) {

        new Thread() {

            @Override
            public void run() {
                OkHttpClient client = new OkHttpClient();
                FormBody body = new FormBody.Builder().add("username", "nate")
                        .add("userage", "99").build();
                Request request = new Request.Builder().url("http://localhost:8080/web/HelloServlet").post(body).build();
                try {
                    Response response = client.newCall(request).execute();
                    if (response.isSuccessful()) {
                        System.out.println(response.body().string());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

5. martipart 上传文件(MP4)

上一点传送给服务器的数据仅限于简单的字段,但是需求中常涉及到传输文件,所以需要使用martipart 来使用类似需求,典型使用方式如下:

public class MultipartHttp {

    public static void main(String args[]) {
        RequestBody imageBody = RequestBody.create(MediaType.parse("image/jpeg"), new File("/Users/nate/girl.jpg"));
        MultipartBody body = new MultipartBody.Builder().
                setType(MultipartBody.FORM).
                addFormDataPart("name", "girl").
                addFormDataPart("filename", "girl.jpg", imageBody).build();
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().
                url("http://192.168.1.6:8080/web/UploadServlet").post(body).build();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                System.out.println(response.body().string());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

另外,有兴趣了解Okhttp3源码图解分析缓存机制的读者可看此篇博客,是由此系列分离出来的知识点,了解学习对后续封装框架颇有帮助:

以 Okhttp3源码 为例 —— 图解 缓存机制 的原理和实现(上)

以 Okhttp3源码 为例 —— 图解 缓存机制 的原理和实现(下)





四. 小结

此篇文章的第二大点,也就是架构设计分析 最为重要,这是将要设计的网络框架的精髓支撑。要设计一个适用、合理的框架,解耦与拓展是相当重要的,不同分层的功能的实现相辅相成,应当被注重!

以上内容只是此系列的一个开始,Http网络协议的基础了解和Okhttp3基本方法的解析,做好铺垫之后,下一篇文章开始解析多线程功能的设计和实现。



期待下篇文章出炉 ~

希望对你们有帮助 :)

你可能感兴趣的:(Android,学习笔记)