作者:王鲁才


客户端开发中不可避免的需要接触到访问网络的需求,如何把访问网络模块设计的更具有扩展性是每一个移动开发者不得不面对的事情。现在有很多主流的网络请求处理框架,如Square公司的OkHttp,Google推出的Volley,还有在OkHttp基础上进行封装的Retrofit等,这些都是非常优秀的网络处理框架。利用现有网络处理框架,比从零开始设计、开发网络请求节省很多开发时间,同时也避免了一些意想不到的问题。如果把这些框架直接拿来使用,不进行任何二次封装,会使我们工程层次结构不清晰(数据存取、逻辑处理、UI展示),不利于扩展(替换成其他网络框架)。所以选择一个合适的网络框架并进行封装,这在开发过程中是非常必要的。

1.   概述

   通常情况下,客户端数据流遵循如下图所述方式流动,UI层负责展示数据,接收用户操作;逻辑处理层处理用户操作,并将处理结果反馈给UI层进行展示;数据层负责数据存取,这里可能会涉及多种方式存取数据:本地缓存中存取数据,向服务器请求存取数据;网络访问层负责从服务器存取数据,数据返回后进行解析,并将结果返回给数据存取层。

网易考拉Android客户端网络模块设计_Android客户端

2.   考拉Android客户端网络模块设计

下面简要介绍一下考拉Android客户端网络模块设计的变迁历程,在这个过程中我们也踩了不少坑,好在我们把这些坑都填平了,而且正在朝着更好的方向发展。

在考拉项目开始之初,我们对比了Volley、OKHttp等主流网络处理框架,最终选择了Volley作为考拉Android客户端的网络请求模块。Volley是在2013年IO大会上Google推出的异步网络请求框架(和图片加载框架),特别适合数据量小,通信频繁的网络操作。

2.1 存在的问题

1.      封装扩展性差(SPDY,HTTPS);

2.      存在内存泄漏(Volley自己的问题);

3.      数据解析在UI线程中进行,阻塞UI展示;

在这一版本的网络请求封装中存在一个比较严重的问题,数据解析在UI线程中进行,在最初的几个版本中,请求的数据量较小,UI比较简单,阻塞UI线程的这个问题还不是特别明显,但是随着UI越来越复杂,同时请求数据、解析数据的操作也越来越多,阻塞UI线程解析数据,存在丢帧问题。为了解决这个问题,引入了工作线程池,网络请求回来以后,先将数据返回给调用者(UI线程),调用者在解析数据的时候,开启工作线程,数据解析完成后再抛回UI线程,UI线程进行数据展示。在这个过程中,有多次线程切换,线程创建、切换、调度都是需要内存、时间开销的。更要命的是,这个过程对调用者不透明,调用者必须知道当前操作是在哪个线程中进行的。

网易考拉Android客户端网络模块设计_网络模块设计_02

扩展性差这一点相信很多开发者都有比较痛的感悟。为了兼容不同服务器返回的不同数据格式,在网络请求模块中定义了很多不同参数的回调方法。在请求数据时,每一种回调接口都要对应一个具体的方法(传入的回调接口不同),代码写的比较冗余,条理性、扩展性差,简直要不能直视了。

2.2 重构

网易考拉Android客户端网络模块设计_数据_03

为了解决上面两个问题,对网络请求模块进行了重构,引入了泛型数据,重新定义了网络数据返回结构,新增了数据解析器。根据用户传入的不同解析器,将网络返回数据解析成不同类型。在网络数据返回后,直接将数据解析操作从UI线程中抛到工作线程中,这个过程对调用者是透明的。调用者只需要知道网络请求操作和数据解析操作是在工作线程中进行的,回调接口在UI线程中执行就可以。

网易考拉Android客户端网络模块设计_数据_04

在这个网络响应回调接口中,采用了泛型,调用者可以返回任意数据结构,为了提高兼容性,在错误回调方法中,不仅传入了错误码和错误原因,而且还额外增加了一个Object类型的数据,供调用者返回其他数据信息。

网易考拉Android客户端网络模块设计_ui线程_05

网易考拉Android客户端网络模块设计_网易考拉_06

KoalaStringParser是通用数据解析接口,主要针对有特殊需求的数据解析;对于通用的、与服务器约定好的数据返回格式,可以继承KoalaSimpleStringParser抽象类,实现onSimpleParse抽象方法解析数据。通用数据解析接口和抽象数据解析类,既保证了扩展性,又对通用数据格式有了统一处理。

2.3 数据缓存

如果不考虑数据缓存的话,上面对网络请求的封装基本也能满足我们的要求了。但是如果没有数据缓存,每次都向服务器请求数据,启动新页面,会有时间长短不一的加载过程,没有网络或者网络不好时,会显示一个空白页面告诉用户网络不可用,这降低了用户体验。为了提升用户体验,减少启动页面的等待时间,增加数据缓存是一件很有必要的事情。缓存可以与服务器配合,使用HTTP的缓存机制,也可以在客户端单独使用。因为现在考拉服务器还没有支持缓存,现阶段只能在客户端添加缓存。

网易考拉Android客户端网络模块设计_数据_07

上面是在没有服务器支持的情况下,客户端支持的缓存。在启动页面请求数据时,先判断是否允许读取缓存数据。允许读取缓存数据,检查是否存在缓存数据,存在缓存数据,将数据从本地缓存中读取后直接返回给调用者;不存在缓存数据,不做任何处理。在检查缓存数据时,同时向服务器发送请求,获取数据,数据返回后更新缓存数据。这里需要注意的是,回调接口可能会被调用两次,一次是从缓存读取数据的回调,一次是从服务器读取数据的回调。出现两次回调的问题,需要调用者进行区分,在回调方法中添加了当前回调是从本地读取数据的回调还是从网络读取数据的回调标示。

         服务端不支持缓存,只在客户端对数据进行缓存处理,在一定程度上提升了用户体验,避免用户多次请求相同页面时出现多次重复加载、长时间等待的问题。这种缓存方式并不能节省用户流量。

2.4 切换到OkHttp

前一段时间项目组提到考拉可能会引入SPDY来优化性能,后面为了解决劫持问题,还可能会上HTTPS,考虑到OkHttp已经内置了对SPDY的支持,而且对HTTPS的封装比较好,未雨绸缪,我们决定从Volley转投OkHttp了,因为之前已经对网络请求进行了封装,所以切换到OkHttp还是比较容易的。

         OkHttp具有如下优点:

1.      支持SPDY,共享同一个Socket来处理同一个服务器的所有请求;

2.      如果SPDY不可用,则通过连接池来减少请求延时;

3.      无缝的支持GZIP来减少数据流量;

4.      缓存网络数据,减少重复网络请求;

5.      支持HTTP2;

使用OkHttp既可以在工作线程中请求数据,也可以在UI线程中请求数据,这与Volley只能在工作线程中请求数据不同。在工作线程中请求数据,数据返回后不需要重新开启线程解析数据,数据在请求线程中解析完后,直接抛到UI线程中进行展示。

3. 总结

在设计开发功能模块时,往往为了快速完成功能,忽略了模块的扩展性,当后续功能越来越复杂时,之前设计的功能、接口已经不能满足需求时,就要重新审视模块是否需要重构、优化,不能总在旧的代码上打补丁,造成代码难以阅读、维护。

4.   SPDY简介

SPDY是Google开发的基于传输控制协议(TCP)的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。SPDY协议在性能方面对HTTP做了很大的优化,语义方面并没有做太大的修改。具体来说是,SPDY使用了HTTP的方法和页眉,但是删除了一些报头并重写了HTTP中管理连接和数据转移格式的部分,基本上兼容HTTP。

网易考拉Android客户端网络模块设计_网易考拉_08