您的位置:首页 > 理论基础 > 计算机网络

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

2017-07-04 16:36 896 查看
如今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基本方法的解析,做好铺垫之后,下一篇文章开始解析多线程功能的设计和实现。

期待下篇文章出炉 ~

希望对你们有帮助 :)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐