目前已经有不少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 postRequest (        @FieldMap("param") String param,        @HeaderMap Map 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 {    // 网关网络请求    protected WGApi wgApi;    // 省略代码    public WgReqService(String baseUrl) {        wgApi = new NetWork.Builder(baseUrl).build().getApi(WGApi.class);    }    public abstract T wgReq(WGRequestBean wgRequest, Map headMap);}

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

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

public class WgReqSync extends WgReqService {    // 省略代码    @Override    public WGResponseBean wgReq(WGRequestBean wgRequest, Map headMap) {        // 省略代码    }}

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

二、基础业务请求

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

public class BaseWgRequest implements Func1 {    // 网关请求Helper类    private WgReqAsync wgReqAsync;    // 服务名    protected String service;    // 服务组名    protected String alias;    // 解析类    protected Class<? extends BusinessBean> rClazz;    // 省略代码}

BaseWgRequest持有WgReqAsync引用,并通过其完成网关访问,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 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 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(this) {    @Override    public void onSuccess(BusinessBean businessBean) {          handleLoginResult(businessBean);    }});

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

public abstract class RxObserver extends Subscriber {    // 省略代码    @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 uploadFile(@Part MultipartBody.Part file,                                                   @PartMap Map params,                                                   @HeaderMap Map headMap);

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

// 根据文件路径生成文件File file = new File(requestBean.getFilePath());// 根据文件创建请求体RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);// 创建实际请求用的MultipartBodyMultipartBody.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 apkUpdate(        @Query("os") String os,        @Query("packageName") String packageName,        @Query("version") String version);


更多相关文章

  1. Android(安卓)studio 通过以servlet搭建的服务器访问 PC端 mysql
  2. Android(安卓)TextView中显示图片的4种方式
  3. Android真机网络adb联机调试初探
  4. Android如何在局域网中发送网络广播
  5. Android(安卓)避免主线程执行网络请求之Activity/Fragment 结束
  6. android 网络请求+json解析 最优分析
  7. Android(安卓)网络图片加载
  8. Android(安卓)在TextView 中显示图片的4种方式
  9. Android(安卓)网络接口

随机推荐

  1. 详解MySQL的数据行和行溢出机制
  2. mysql 重要日志文件汇总
  3. MySQL的表空间是什么
  4. MySQL慢查询如何定位详解
  5. IDEA无法连接mysql数据库的6种解决方法大
  6. mysqldump你可能不知道的参数
  7. IDEA连接不上MySQL端口号占用的解决
  8. IDEA配置连接MYSQL数据库遇到Failed这个
  9. MySQL从库维护经验分享
  10. 关于mysql主备切换canal出现的问题解决