目前已经有不少Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具有网络访问效率高(基于OkHttp)、内存占用少、代码量小以及数据传输安全性高等特点。

Retrofit源码更是经典的设计模式教程,笔者已在之前的文章中分享过自己的一些体会,有兴趣的话可点击以下链接了解:《Retrofit源码设计模式解析(上)》、《Retrofit源码设计模式解析(下)

但在具体业务场景下,比如涉及到多种网络请求(GET/PUT/POST/DELETE等),多种请求方式(异步/同步)时,按照Retrofit官方文档实现网络请求仍然会显得比较繁琐,本文主要介绍笔者基于Retrofit+RxJava封装的Android分层网络请求框架,适用于下图所示的业务场景:Android移动端通过移动网关调用接口平台发布的业务服务

上述业务架构可能是目前移动应用中使用的比较广的,其具有以下优点:

  • 由于移动网关系统和统一服务发布平台的存在,移动端不需要直接调用业务系统的服务,避免了移动端同时对接多个业务系统,降低移动端系统的复杂性;
  • 移动网关会对移动端的请求进行鉴权,屏蔽外部恶意访问,有效提高内部业务系统的安全性;
  • 统一服务发布平台集成所有的业务接口,对外提供格式统一的接口服务,这对于内部系统的可维护性和可扩展性是至关重要的。
  • 业务系统只需要按照格式将其服务在接口平台上发布即可,无需关心具体的调用者。

因此,本文分享的分层网络请求框架的前提是:Android移动端直接对接移动网关。主要有以下内容:

  1. 网关请求封装。移动网关的请求格式(参数、字段、通信方式等)应该是固定的,并且对业务是透明的,不触碰具体业务数据。负责直接对接客户端的请求,包括请求的鉴权,客户端与后台的数据格式的转换等。
  2. 基础业务请求。基础业务请求涉及到正式/测试环境的切换,网关返回业务数据的统一解析,以及添加业务相关的网关默认字段等;
  3. 业务Module统一网络请求管理。业务Module负责统一管理一个业务模块中所有的网络请求,接收鉴别请求对应的字段,包含服务名、服务分组名、请求方法以及请求参数等;
  4. Model层网络请求。Model层的网络请求是按服务划分的,一个应用Module通常会对应多个服务,并且接收Activity的参数,组装请求bean;
  5. Activity层的网络访问。Activity直接调用Model层的方法,传入界面相关的参数,回调响应结果。
  6. 文件上下传及其它网络访问。通过Retrofit+RxJava还可以实现文件上下传以及软件更新等其它网络访问,本文也会一并简要介绍。

一、网关请求封装

通过Retrofit注解定义移动网关接口,比如请求方式,参数格式,字段等。以POST请求为例,参数格式为表单数据,字段包含服务名、服务分组名、方法名、参数、请求头Map以及其他参数等。

@FormUrlEncoded
@POST("./")
Observable<WGResponseBean> postRequest (
        @FieldMap("param") String param,
        @HeaderMap Map<String, String> headMap);

Retrofit的FieldMap不支持字段值为null,如参数中有null值,需要使用Field。

如上所述,@POST表示该请求是一个POST方法,常用的POST提交数据的方式有:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • application/json
  • text/xml

application/x-www-form-urlencoded对应表单数据,在Retrofit中,通过@FormUrlEncoded标注的参数将以表单形式进行提交。multipart/form-data一般用于文件上传的时候,这个在后面会提到。application/json通过JSON方式与服务端进行数据交换,text/xml使用XML数据格式。

定义了网关请求之后,需要创建对应的Service,而Service的使用方式并不确定,这里通过一个抽象类对其进行封装。

public abstract class WgReqService<T> {

    // 网关网络请求
    protected WGApi wgApi;

    // 省略代码
    public WgReqService(String baseUrl) {
        wgApi = new NetWork.Builder(baseUrl).build().getApi(WGApi.class);
    }

    public abstract T wgReq(WGRequestBean wgRequest, Map<String, String> headMap);
}

以同步/异步网络请求为例,分别继承自WgReqService,实现对应的wgReq方法即可。

public class WgReqAsync<T> extends WgReqService<Observable<T>> {
    // 省略代码
    @Override
    public Observable<T> wgReq(WGRequestBean wgRequest, Map<String, String> headMap) {
        // 省略代码
    }
}
public class WgReqSync extends WgReqService<WGResponseBean> {
    // 省略代码
    @Override
    public WGResponseBean wgReq(WGRequestBean wgRequest, Map<String, String> headMap) {
        // 省略代码
    }
}

由于采用了RxJava,因此在异步实现中,泛型参数为Observable<T>,而同步请求时直接返回网关的出参Bean。另外,需要说明的是WgReqAsync包含域Func1<WGResponseBean, T>,Func1为RxJava支持的接口,这里表示将网关返回的业务数据进行统一解析的方法。

二、基础业务请求

通过上述的分析可知,业务请求可以有同步/异步等多种实现方式,同时涉及到正式/测试环境的切换,网关返回业务数据的统一解析,以及添加业务相关的网关默认字段等,这里以异步请求为例:

public class BaseWgRequest implements Func1<WGResponseBean, BusinessBean> {

    // 网关请求Helper类
    private WgReqAsync<BusinessBean> wgReqAsync;

    // 服务名
    protected String service;
    // 服务组名
    protected String alias;
    // 解析类
    protected Class<? extends BusinessBean> rClazz;

    // 省略代码
}

BaseWgRequest持有WgReqAsync<BusinessBean>引用,并通过其完成网关访问,service、alias等域指定相应的服务,Class<? extends BusinessBean>表示对业务返回值进行解析的类。

return JSON.parseObject(wgResponse.getData(), rClazz != null ? rClazz : BusinessBean.class);

异步请求中,通过上述域及业务相关的网关默认字段封装请求体,同时获取请求head。

// 请求
return wgReqAsync.wgReq(ParamUtil.getWGRequestBean(service, alias, method, param),
            BaseConstants.getHeaderMap());

三、业务Module

首先申明,对整个项目进行多工程划分(业务工程和库工程独立,便于库工程独立维护),同时业务工程中分为多个功能Module(便于功能模块插件化、热加载),这种方式在比较大型的项目中应用效果可能比较好,在小型项目中并不推荐。这里的业务Module是以功能模块进行划分的,对一个功能模块中的所有网络请求进行统一管理,能有效的单元测试,提高整体开发效率。

如上所述,业务Module的主要职责是接收鉴别请求对应的字段,包含服务名、服务分组名、请求方法以及请求参数等,并继承自上述 BaseWgRequest实现。

public class WelNetwork extends BaseWgRequest {}

业务Module包含了一个功能模块中的所有网络请求方法,以登录为例:

public Observable<BusinessBean> userLoginWork(SysUsersReqDto sysUsersReqDto) {
    return wgRequest(service, alias, BusinessConstants.userLoginWork, ParamUtil.getJsonParam(sysUsersReqDto));
}

这里重点说明下登录方法的入参,BaseWgRequest关注的是与网关接口相关的参数,由于业务Module继承自BaseWgRequest,这一层的方法不再关注网关相关内容,重点是业务相关的请求入参。换句话说,业务Module的入参直接对应业务接口的入参,具有访问形式的无关性。考虑清楚每一个层次的关注重点,是搭建软件架构的基础。

四、Model层网络请求

在本系统中,按照服务名对Model进行了划分,需要申明的是,由于每个公司的具体情况不一样,这种划分方式不一定适用于你的系统。不过这种分层方式仍有借鉴之处。

由于Model中方法的访问可能不止一处,因此对外(Activity)提供单实例对象。这里提供一种最简单饿汉模式的单实例:

private static LoginModel loginModel = new LoginModel();

public static LoginModel getInstance() {
    return loginModel;
}

同时,在其构造器中初始化业务Module访问类。

private LoginModel() {
        super();
        welNetwork = new WelNetwork.Builder().service(BusinessConstants.SysLogin).alias(BaseConstants.getALIAS())
                .rClazz(SysUsersResDto.class).build();
}

上面提到,业务Module关注的是业务接口的入参,那么这个入参就是有Model提供的。一个功能模块可能对应多个服务,那么这些服务需要持有业务Module的引用,并通过业务Module的方法实现自身的方法。还是以登录为例:

public Observable<BusinessBean> userLoginWork(String username, String password) {
        return welNetwork.userLoginWork(new SysUsersReqDto.Builder(username).userPwd(password)
                .devType("1").devIp(DeviceUtils.getClientIpAddress()).build());
}

Model负责连接Activity和业务Module,对上直接对接Activity,Activity关注的是用户输入的用户名和密码,并不知道业务接口需要的数据格式,而业务Module关注的是业务接口的入参格式。因此,Model层对这两种数据进行适配,常见的就是对请求bean的组装,比如上述登录方法接收用户名和密码,组装成业务Module所需的SysUsersReqDto。

五、Activity层的网络访问

通过上述分层封装,在Activity中的网络访问就非常简单了。直接上示例代码:

LoginModel.getInstance().userLoginWork(usernameStr, passwordStr)
    .subscribe(new RxObserver<BusinessBean>(this) {
    @Override
    public void onSuccess(BusinessBean businessBean) {
          handleLoginResult(businessBean);
    }
});

需要说明的是RxObserver,RxObserver<T>继承自Subscriber<T>,Subscriber是RxJava的回调类,RxObserver包含抽象方法onSuccess,并在onNext实现中进行调用。

public abstract class RxObserver<T> extends Subscriber<T> {
    // 省略代码
    @Override
    public void onNext(T t) {
        onSuccess(t);
    }

    public abstract void onSuccess(T t);
}

从Activity的角度来讲,其负责用户交互,因此只关注用户输入和接口返回具体数据,并对数据进行处理。而至于网关的实现,业务接口的入参格式,网络请求的方式等底层实现,则对Activity完全闭合。

上述简要介绍了题目所讲到的基于Retrofit+RxJava的Android分层网络请求框架,由于涉及具体业务,只能开放部分代码样例。至于对架构的观点,可参考《什么是架构?》

  1. 根据要解决的问题,对目标系统的边界进行界定。
  2. 并对目标系统按某个原则的进行切分。切分的原则,要便于不同的角色,对切分出来的部分,并行或串行开展工作,一般并行才能减少时间。
  3. 并对这些切分出来的部分,设立沟通机制。
  4. 根据3,使得这些部分之间能够进行有机的联系,合并组装成为一个整体,完成目标系统的所有工作。

界定-切分-沟通-系统,是架构设计的基本步骤。

本系统界定为基于Retrofit+RxJava实现Android分层网络请求,然后将整个系统进行切分五个层次,每个层次的关注点相异,但又相互联系,这五个层次通过抽象(抽象类或接口)、继承、复合等方法进行沟通,形成一个统一系统,完成Android中的网络请求。

六、文件上下传及其它网络访问

除上述网关请求外,Android中还经常涉及文件上下传、软件更新等与网络相关的操作,这里也对其进行简要的介绍。

如上所述,文件上传需要采用multipart/form-data数据提交方式,因此在Retrofit中定义方法时,需要采用@Multipart注解。

@Multipart
@POST("./")
Observable<UploadFileResponseBean> uploadFile(@Part MultipartBody.Part file,
                                                   @PartMap Map<String, RequestBody> params,
                                                   @HeaderMap Map<String, String> headMap);

同时,其入参类型为@Part,或@PartMap。需要注意的是,传入该方法的参数为MultipartBody.Part。

// 根据文件路径生成文件
File file = new File(requestBean.getFilePath());
// 根据文件创建请求体
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
// 创建实际请求用的MultipartBody
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);

其他封装形式与上述网关请求类似,这里不再赘述。

对于文件的下载,笔者尝试了《Retrofit 2 — How to Download Files from Server》的方法,但由于其涉及下载进度的监听以及下载完成的操作等,对后续系统的封装并不好,这里就不详细介绍了。

针对文件下载这种场景,如果自定义实现,需要处理OOM、多线程等问题。DownloadManager是Android2.3以后引入的系统自带类库,通过getSystemService(Context.DOWNLOAD_SERVICE)就能获取并使用,系统服务已经完成网络访问控制、文件读写控制、通知栏进度显示、大文件续传等一系列文件下载可能遇到的问题。因此,推荐系统自带实现,这个列出简要参考代码,详细情况请参考《DownloadManager官方文档》

DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
// 设置目标文件路径
request.setDestinationInExternalPublicDir(dir, fileName);
// 仅在WIFI网络下载
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 设置标题及描述
request.setTitle(getString(R.string.app_name));
// 发送请求
downloadManager.enqueue(request);

最后,举个GET请求的栗子,查询软件是否有更新一般会采用GET请求,比如请求参数包括系统、包名、版本号等入参的请求格式为:

@GET("./")
Observable<ApkUpdateResponseBean> apkUpdate(
        @Query("os") String os,
        @Query("packageName") String packageName,
        @Query("version") String version);

@Query表示请求字段。

基于Retrofit+RxJava的Android分层网络请求框架的更多相关文章

  1. Android okHttp网络请求之Retrofit+Okhttp+RxJava组合

    前言: 通过上面的学习,我们不难发现单纯使用okHttp来作为网络库还是多多少少有那么一点点不太方便,而且还需自己来管理接口,对于接口的使用的是哪种请求方式也不能一目了然,出于这个目的接下来学习一下R ...

  2. 一步步搭建Retrofit+RxJava+MVP网络请求框架(二),个人认为这次封装比较强大了

    在前面已经初步封装了一个MVP的网络请求框架,那只是个雏形,还有很多功能不完善,现在进一步进行封装.添加了网络请求时的等待框,retrofit中添加了日志打印拦截器,添加了token拦截器,并且对Da ...

  3. Android okHttp网络请求之Json解析

    前言: 前面两篇文章介绍了基于okHttp的post.get请求,以及文件的上传下载,今天主要介绍一下如何和Json解析一起使用?如何才能提高开发效率? okHttp相关文章地址: Android o ...

  4. Android okHttp网络请求之Get/Post请求

    前言: 之前项目中一直使用的Xutils开源框架,从xutils 2.1.5版本使用到最近的xutils 3.0,使用起来也是蛮方便的,只不过最近想着完善一下app中使用的开源框架,由于Xutils里 ...

  5. Android okHttp网络请求之文件上传下载

    前言: 前面介绍了基于okHttp的get.post基本使用(http://www.cnblogs.com/whoislcj/p/5526431.html),今天来实现一下基于okHttp的文件上传. ...

  6. Android okHttp网络请求之缓存控制Cache-Control

    前言: 前面的学习基本上已经可以完成开发需求了,但是在项目中有时会遇到对请求做个缓存,当没网络的时候优先加载本地缓存,基于这个需求我们来学习一直okHttp的Cache-Control. okHttp ...

  7. Android网络请求框架AsyncHttpClient实例详解(配合JSON解析调用接口)

    最近做项目要求使用到网络,想来想去选择了AsyncHttpClient框架开进行APP开发.在这里把我工作期间遇到的问题以及对AsyncHttpClient的使用经验做出相应总结,希望能对您的学习有所 ...

  8. Android网络请求框架

    本篇主要介绍一下Android中经常用到的网络请求框架: 客户端网络请求,就是客户端发起网络请求,经过网络框架的特殊处理,让后将请求发送的服务器,服务器根据 请求的参数,返回客户端需要的数据,经过网络 ...

  9. Android 网络请求框架android-async-http问题

    今天通过接口请求服务器的一些app数据,发现一个很奇怪的问题,请求一个链接的时候,通常在第一次请求发起的时候没有什么问题,能很快的拿到数据,但是 往后再去请求的时候就会等待很久,而且最后会请求失败,一 ...

随机推荐

  1. linux内存管理

    一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...

  2. 用于基于 RPM 的 Linux 平台的 Java

    成为超级用户,方法是运行 su 并输入超级用户口令. 卸载任何之前安装的 Java 程序包. rpm -e package_name 切换到所需的安装目录.键入:cd directory_path_n ...

  3. 重构第5天:提升字段(Pull Up Field)

    理解:提升字段和前面讲解的方法提公很类似,可以说方式都是一样的.就是把继承类中经常用到的字段,提出来 放到基类中,达到通用的目的.提高代码重用性和可维护性. 详解:如下重构前的代码: using Sy ...

  4. 如何对ZBrush中面部进行快速布线

    面部布线的最重要目的是为了表情动画.人物内心的各种不同的心里活动,主要是通过面部表情反映出来.而面部变化最丰富的地方是眼部(眉毛)和口 部,其他部位则相应的会受这两部分的影响而变化.对于面部表情,必须 ...

  5. ndk编译protobuf库

    ndk_r9编译通过,里面带了自动生成代码的脚本(tool/createPBFile.bat). 下载地址

  6. Qt调用dll中的功能函数

    声明: 事先我已经自己动手写了一个简单的dll文件(myDLL.dll),C版接口的.并且用我前两篇有关DLL文章里面的方法,从dll中导出了导入库(.lib)文件,dll中有两个函数,原型如下:   ...

  7. 你可能不知道的SQL问题

    1.  如下是sql语句和结果, SELECT count(*) AS total FROM orders; +-------+ | total | +-------+ | 150 | +------ ...

  8. Centos7 安装mysql数据库

    第一步:下载数据库文件 # wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm 看到下载成功 第二步: # r ...

  9. 笔记整理——Linux下C语言正则表达式

    Linux下C语言正则表达式使用详解 - Google Chrome (2013/5/2 16:40:37) Linux下C语言正则表达式使用详解 2012年6月6日Neal627 views发表评论 ...

  10. 别以为真懂Openstack: 虚拟机创建的50个步骤和100个知识点(1)

    还是先上图吧,无图无真相 别以为真懂Openstack!先别着急骂我,我也没有说我真懂Openstack 我其实很想弄懂Openstack,然而从哪里下手呢?作为程序员,第一个想法当然是代码,Code ...