如今Android开发中Okhttp已成为主流网络框架,内置丰富、全面、强大的网络请求功能,也为开发者提供了api,但是在项目开发中的大量使用,会出现api的重复调用、代码冗杂等现象。应当封装一个适用于项目的网络框架,便于使用。
此系列文章旨于:基于okhttp3原始框架来设计封装一个满足业务需求、扩展性强、耦合度低的网络框架。具体框架功能为:
此系列文章将详细记录其封装过程,从底层框架的选择、基础知识、设计理念到架构分析、封装代码,自顶向下来完成网络框架的设计封装,最后附上源码,下面开始!
在Android网络框架并不成熟的时候,开发者在开发过程中往往会遇到几大难题:如何去访问网络数据?如何做本地存储?如何做图片缓存等类似问题。随着Android不断发展,继而推出了以下几大常用框架:
(1)Volley
谷歌研发的一款底层基础网络框架,Volley在Android整个发展中是出现较早的,早期的项目使用的便是Volley框架,但是以当今的角度去分析它还是有些不足的地方,例如不支持文档、文件的下载,更倾向于轻量级数据的请求。
优点:
缺点:
(2)Android-async-http
提供了Http请求的大部分功能,绝对满足网络请求的各种需求,支持智能重试、gzip压缩等功能,是一个功能全面的网络框架,而且在github上start星数是Volley的两倍。
优点:
缺点:
(3)Afinal 和 XUtils
国人研发的框架,不仅仅提供网络请求的功能,还支持数据库管理、图片下载缓存等功能。虽然功能丰富,但是没有只专于网络请求的框架功能细致、全面,后期维护成本很高。此框架较适用于小型快速开发。
四大模块功能:
(4)Okhttp
由square公司研发(开源界最著名的两个公司:Square、Facebook),OkHttp是一款优秀的HTTP框架,大致功能为:
就目前而言,Okhttp网络请求框架已被广泛应用成为主流框架,而且在Android6.0时已被集成到系统中默认底层协议,可以看出谷歌对Okhttp的满意度。
(5)Retrofit
本质上就是在Okhttp上做了相应的封装,网络底层交互还是Okhttp,封装如下:
参考标准:
选择
综合以上考虑,本次封装的网络框架是基于Okhttp3,第二点从架构设计的层面来分析本次封装的网络框架,此点较为重要!
在Android开发当中往往会涉及到设计模式的采用,例如MVC、MVP,MVVM等,但是在这个网络框架设计并未采用任何设计模式,主要是按照分层次的划分来实现解耦的目的,方便于此框架以后拓展与修改。
如上图所示,此框架可以分为三个层次:
第一层:便于框架扩展,第一层即最底层是Http Interface和Abstact,例如Http中的Headers、Request、Response等通用的原生接口。
第二层:有了第一层请求接口定义,便于第二层对接口的实现,此框架采用两种方式对接口进行实现,分别是Okhttp和原生的HttpURLConnection。通过这两个相关的API去实现整个Http请求和响应的过程,若还想要做相应的拓展,采用别的第三方http请求库,在此处可增加。(已经预先在第一层定义了足够多的接口实现网络请求的回调,第一层可无需修改)对于整个上层业务来说,无需直接接触到底层Okhttp、HttpURLConnection具体实现,所以提供二次封装的 HttpProvider ,暴露接口给上层调用。(具体底层是调用Http还是HttpURLConnection取决于配置,首先判断Okhttp依赖在项目中是否存在,若有则主要采用Okhttp来进行网络请求,否则采用HttpURLConnection)
第三层:即最上层由 Workstation 和Convert组成。Workstation 的中文意思是工作站,用来处理一些线程的调度分发和任务的队列,之所以将它设计在最上层,因为整个多线程、队列机制是与业务层紧密相关的。Convert是为上层开发者提供了更好的接口封装,用于接口返回类型转换、数据解析,例如json、xml等。
HTTP(Hypertext Transfer Protocol),即超文本传输协议。是WWW浏览器和WWW服务器之间的应用层通讯协议。HTTP协议是基于TCP/IP之上的协议,它不仅保证正确传输超文本文档,还确定传输文档中的哪一部分,以及哪一部分内容首先显示(如文本先于图形)。
方式名称 | 含义 |
---|---|
GET | 请求获取Request-URI所标识的资源 |
POST | 在Request-URI所标识的资源后附加新的数据 |
HEAD | 请求获取由Request-URI所标识的资源的响应信息报头 |
PUT | 请求服务器存储一个资源,并用Request-URI作为其标识 |
DELETE | 请求服务器删除Request-URI所标识的资源 |
TRACE | 请求服务器回送收到的请求信息,主要用于测试或诊断 |
CONNECT | 保留将来使用 |
OPTIONS | 请求查询服务器的性能,或者查询与资源相关的选项 |
请求头 | 说明 |
---|---|
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
应答头 | 说明 |
---|---|
Content-Encoding | 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。 |
Content-Type | 表示后面的文档属于什么MIME类型。 |
Date | 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。 |
Expires | 过期时间 |
Last-Modified | 档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。 |
HTTP状态码分类
分类 | 描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
较常用状态码
以上基础HTTP网络知识在后续的网络框架编码中会涉及到,所以需要稍作了解。在学习以上知识点后,下面举例介绍使用Okhttp3框架请求网络常用的方式,这里只介绍后续封装使用到的请求方式。(默认读者有基本使用Okhttp3基础,网上使用教程很多,可自行搜查)
大多数情况异步请求较于同步请求更为广泛,因为同步请求的过程中容易阻塞到线程,而异步请求通过内部队列维护完美避免此问题,并且在回调中处理请求结果更符合需求。典型的使用方法如下:
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");
}
}
在第二节中介绍了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();
}
}
}
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();
}
}
}
上一点介绍完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();
}
}
上一点传送给服务器的数据仅限于简单的字段,但是需求中常涉及到传输文件,所以需要使用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基本方法的解析,做好铺垫之后,下一篇文章开始解析多线程功能的设计和实现。
期待下篇文章出炉 ~
希望对你们有帮助 :)