目前已经有很多Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具备网络访问效率高(基于OkHttp)、内存占用少、代码量小以及数据传输安全性高等特色。html
Retrofit源码更是经典的设计模式教程,笔者已在以前的文章中分享过本身的一些体会,有兴趣的话可点击如下连接了解:《Retrofit源码设计模式解析(上)》、《Retrofit源码设计模式解析(下)》android
但在具体业务场景下,好比涉及到多种网络请求(GET/PUT/POST/DELETE等),多种请求方式(异步/同步)时,按照Retrofit官方文档实现网络请求仍然会显得比较繁琐,本文主要介绍笔者基于Retrofit+RxJava封装的Android分层网络请求框架,适用于下图所示的业务场景:Android移动端经过移动网关调用接口平台发布的业务服务。git
上述业务架构多是目前移动应用中使用的比较广的,其具备如下优势:github
所以,本文分享的分层网络请求框架的前提是:Android移动端直接对接移动网关。主要有如下内容:json
经过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对应表单数据,在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的主要职责是接收鉴别请求对应的字段,包含服务名、服务分组名、请求方法以及请求参数等,并继承自上述 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中方法的访问可能不止一处,所以对外(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中的网络访问就很是简单了。直接上示例代码:
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分层网络请求框架,因为涉及具体业务,只能开放部分代码样例。至于对架构的观点,可参考《什么是架构?》。
- 根据要解决的问题,对目标系统的边界进行界定。
- 并对目标系统按某个原则的进行切分。切分的原则,要便于不一样的角色,对切分出来的部分,并行或串行开展工做,通常并行才能减小时间。
- 并对这些切分出来的部分,设立沟通机制。
- 根据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);