为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块。本篇文章就尝试为使用gradle的android项目设计实现MockApi。

需求概述

在app开发过程中,在和服务器人员协作时,一般会第一时间确定数据接口的请求参数和返回数据格式,然后服务器人员会尽快提供给客户端可调试的假数据接口。不过有时候就算是假数据接口也来不及提供,或者是接口数据格式来回变动——很可能是客户端展示的原因,这个是产品设计决定的,总之带来的问题就算服务器端的开发进度会影响客户端。

所以,如果可以在客户端的正常项目代码中,自然地(不影响最终apk)添加一种模拟服务器数据返回的功能,这样就可以很方便的在不依赖服务器的情况下展开客户端的开发。而且考虑一种情况,为了测试不同网络速度,网络异常以及服务器错误等各种“可能的真实数据请求的场景”对客户端UI交互的影响,我们往往需要做很多手动测试——千篇一律!如果本地有一种控制这种服务器响应行为的能力那真是太好了。

本文将介绍一种为客户端项目增加模拟数据接口功能的方式,希望能减少一些开发中的烦恼。

设计过程

下面从分层设计、可开关模拟模块、不同网络请求结果的制造这几个方面来阐述下模拟接口模块的设计。
为了表达方便,这里要实现的功能表示为“数据接口模拟模块”,对应英文为MockDataApi,或简写为MockApi,正常的数据接口模块定义为DataApi。

分层思想

说到分层设计,MVC、MVP等模式一定程度上就起到了对代码所属功能的一个划分。分层设计简单的目标就是让项目代码更加清晰,各层相互独立,好处不多说。

移动app的逻辑主要就是交互逻辑,然后需要和服务器沟通数据。所以最简单的情形下可以将一个功能(比如一个列表界面)的实现分UI层和数据访问层。

下面将数据访问层表述为DataApi模块,DataApi层会定义一系列的接口来描述不同类别的数据访问请求。UI层使用这些接口来获取数据,而具体的数据访问实现类就可以在不修改UI层代码的情况下进行替换。

例如,有一个ITaskApi定义了方法List<Task> getTasks(),UI层一个界面展示任务列表,那么它使用ITaskApi来获取数据,而具体ITaskApi的实现类可以由DataApi层的一个工厂类DataApiManager来统一提供。

有了上面的分层设计,就可以为UI层动态提供真实数据接口或模拟数据接口。

模拟接口的开关

可能大家都经历过在UI层代码里临时写一些假数据得情况。比如任务列表界面,开发初,可以写一个mockTaskData()方法来返回一个List<Task>。但这种代码只能是开发阶段有,最终apk不应该存在。

不能让“模拟数据”的代码到处散乱,在分层设计的方式下,可以将真实的数据接口DataApi和模拟数据接口MockDataApi分别作为两个数据接口的实现模块,这样就可以根据项目的构建类型来动态提供不同的数据接口实现。

实现MockDataApi的动态提供的方法也不止一种。
一般的java项目可以使用“工厂模式+反射”来动态提供不同的接口实现类,再专业点就是依赖注入——DI框架的使用了。
目前gradle是java的最先进的构建工具,它支持根据buildType来分别指定不同的代码资源,或不同的依赖。
可以在一个单独的类库module(就是maven中的项目)中来编写各种MockDataApi的实现类,然后主app module在debug构建时添加对它的依赖,此时数据接口的提供者DataApiManager可以向UI层返回这些mock类型的实例。

为了让“正常逻辑代码”和mock相关代码的关联尽量少,可以提供一个MockApiManager来唯一获取各个MockDataApi的实例。然后在debug构建下的MockApiManager会返回提供了mock实现的数据接口实例,而release构建时MockApiManager会一律返null。

不同请求结果的模拟

MockApi在多次请求时提供不同的网络请求结果,如服务器错误,网络错误,成功等,并模拟出一定的网络延迟,这样就很好的满足了UI层代码的各种测试需求。

为了达到上述目标,定义一个接口IMockApiStrategy来表示对数据请求的响应策略,它定义了方法onResponse(int callCount)。根据当前请求的次数callCount,onResponse()会得到不同的模拟响应结果。很明显,可以根据测试需要提供不同的请求响应策略,比如不断返回成功请求,或者不断返回错误请求,或轮流返回成功和错误等。

关键代码解析

下面就给出各个部分的关键代码,来说明以上所描述的MockDataApi模块的实现。

UI层代码

作为示例,界面MainActivity是一个“任务列表”的展示。任务由Task类表示:

public class Task {  public String name;
}

界面MainActivity使用一个TextView来显示“加载中、任务列表、网络错误”等效果,并提供一个Button来点击刷新数据。代码如下:

public class MainActivity extends Activity {    private TextView tv_data;    private boolean requesting = false;    @Overrideprotected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);tv_data = (TextView) findViewById(R.id.tv_data);        getData();}    private void getData() {        if (requesting) return;requesting = true;ITaskApi api = DataApiManager.ofTask();        if (api != null) {api.getTasks(new DataApiCallback<List<Task>>() {                @Overridepublic void onSuccess(List<Task> data) {                    // 显示数据StringBuilder sb = new StringBuilder("请求数据成功:\n");                    for (int i = 0; i < data.size(); i++) {sb.append(data.get(i).name).append("\n");}tv_data.setText(sb.toString());requesting = false;}                @Overridepublic void onError(Throwable e) {                    // 显示错误tv_data.setText("错误:\n" + e.getMessage());requesting = false;}                @Overridepublic void onStart() {                    // 显示loadingtv_data.setText("正在加载...");}});}}    public void onRefreshClick(View view) {        getData();}
}

在UI层代码中,使用DataApiManager.ofTask()获得数据访问接口的实例。
考虑到数据请求会是耗时的异步操作,这里每个数据接口方法接收一个DataApiCallback<T> 回调对象,T是将返回的数据类型。

public interface DataApiCallback<T>  {    void onSuccess(T data);    void onError(Throwable e);    void onStart();
}

接口DataApiCallback定义了数据接口请求数据开始和结束时的通知。

DataApiManager

根据分层设计,UI层和数据访问层之间的通信就是基于DataApi接口的,每个DataApi接口提供一组相关数据的获取方法。获取Task数据的接口就是ITaskApi:

public interface ITaskApi {    void getTasks(DataApiCallback<List<Task>> callback);
}

UI层通过DataApiManager来获得各个DataApi接口的实例。也就是在这里,会根据当前项目构建是debug还是release来选择性提供MockApi或最终的DataApi。

public class DataApiManager {    private static final boolean MOCK_ENABLE = BuildConfig.DEBUG;    public static ITaskApi ofTask() {        if (MOCK_ENABLE) {ITaskApi api = MockApiManager.getMockApi(ITaskApi.class);            if (api != null) return api;}        return new NetTaskApi();}
}

当MOCK_ENABLE为true时,会去MockApiManager检索一个所需接口的mock实例,如果没找到,会返回真实的数据接口的实现,上面的NetTaskApi就是。倘若现在服务器还无法进行联合调试,它的实现就简单的返回一个服务器错误:

public class NetTaskApi implements ITaskApi {    @Overridepublic void getTasks(DataApiCallback<List<Task>> callback) {        // 暂时没用实际的数据接口实现callback.onError(new Exception("数据接口未实现"));}
}

MockApiManager

DataApiManager利用MockApiManager来获取数据接口的mock实例。这样的好处是模拟数据接口的相关类型都被“封闭”起来,仅通过一个唯一类型来获取已知的DataApi的一种(这里就指mock)实例。这样为分离出mock相关代码打下了基础。

在DataApiManager中,获取数据接口实例时会根据开关变量MOCK_ENABLE判断是否可以返回mock实例。仅从功能上看是满足动态提供MockApi的要求了。不过,为了让最终release构建的apk中不包含多余的mock相关的代码,可以利用gradle提供的buildVariant。

  • buildVariant
    使用gradle来构建项目时,可以指定不同的buildType,默认会有debug和release两个“构建类型”。此外,还可以提供productFlavors来提供不同的“产品类型”,如demo版,专业版等。
    每一种productFlavor和一个buildType组成一个buildVariant(构建变种)。
    可以为每一个buildType,buildVariant,或productFlavor指定特定的代码资源。

这里利用buildType来为debug和release构建分别指定不同的MockApiManager类的实现。

默认的项目代码是在src/main/java/目录下,创建目录/src/debug/java/来放置只在debug构建时编译的代码。在/src/release/java/目录下放置只在release构建时编译的代码。

  • debug构建时的MockApiManager

public class MockApiManager {    private static final MockApiManager INSTANCE = new MockApiManager();    private HashMap<String, BaseMockApi> mockApis;    private MockApiManager() {}    public static <T> T getMockApi(Class<T> dataApiClass) {        if (dataApiClass == null) return null;String key = dataApiClass.getName();        try {T mock = (T) getInstance().mockApis.get(key);            return mock;} catch (Exception e) {            return null;}}    private void initApiTable() {mockApis = new HashMap<>();mockApis.put(ITaskApi.class.getName(), new MockTaskApi());}    private static MockApiManager getInstance() {        if (INSTANCE.mockApis == null) {            synchronized (MockApiManager.class) {                if (INSTANCE.mockApis == null) {INSTANCE.initApiTable();}}}        return INSTANCE;}
}

静态方法getMockApi()根据传递的接口类型信息从mockApis中获取可能的mock实例,mockApis中注册了需要mock的那些接口的实现类对象。

  • release构建时的MockApiManager

public class MockApiManager {    public static <T> T getMockApi(Class<T> dataApiClass) {        return null;}
}

因为最终release构建时是不需要任何mock接口的,所以此时getMockApi()一律返回null。也没有任何和提供mock接口相关的类型。

通过为debug和release构建提供不同的MockApiManager代码,就彻底实现了MockApi代码的动态添加和移除。

MockApi的实现

模拟数据接口的思路非常简单:根据请求的次数callCount,运行一定的策略来不断地返回不同的响应结果。
响应结果包括“网络错误、服务器错误、成功”三种状态,而且还提供一定的网络时间延迟的模拟。

IMockApiStrategy

接口IMockApiStrategy的作用就是抽象对请求返回不同响应结果的策略,响应结果由IMockApiStrategy.Response表示。

public interface IMockApiStrategy {    void onResponse(int callCount, Response out);    /**     * Mock响应返回结果,表示响应的状态     */class Response {        public static final int STATE_NETWORK_ERROR = 1;        public static final int STATE_SERVER_ERROR = 2;        public static final int STATE_SUCCESS = 3;        public int state = STATE_SUCCESS;        public int delayMillis = 600;}
}

Response表示的响应结果包含结果状态和延迟时间。

作为一个默认的实现,WheelApiStrategy类根据请求次数,不断返回上述的三种结果:

public class WheelApiStrategy implements IMockApiStrategy {    @Overridepublic void onResponse(int callCount, Response out) {        if (out == null) return;        int step = callCount % 10;        switch (step) {            case 0:            case 1:            case 2:            case 3:out.state = Response.STATE_SUCCESS;                break;            case 4:            case 5:out.state = Response.STATE_SERVER_ERROR;                break;            case 6:            case 7:out.state = Response.STATE_SUCCESS;                break;            case 8:            case 9:out.state = Response.STATE_NETWORK_ERROR;                break;}out.delayMillis = 700;}
}

方法onResponse()的参数out仅仅是为了避免多次创建小对象,对应debug构建,倒也没太大意义。

BaseMockApi

针对每一个数据访问接口,都可以提供一个mock实现。比如为接口ITaskApi提供MockTaskApi实现类。

为了简化代码,抽象基类BaseMockApi完成了大部分公共的逻辑。

public abstract class BaseMockApi {    protected int mCallCount;    private IMockApiStrategy mStrategy;    private Response mResponse = new Response();    public Response onResponse() {        if (mStrategy == null) {mStrategy = getMockApiStrategy();}        if (mStrategy != null) {mStrategy.onResponse(mCallCount, mResponse);mCallCount++;}        return mResponse;}    protected IMockApiStrategy getMockApiStrategy() {        return new WheelApiStrategy();}    protected void giveErrorResult(final DataApiCallback<?> callback, Response response) {Action1<Object> onNext = null;AndroidSchedulers.mainThread().createWorker().schedule(new Action0() {            @Overridepublic void call() {callback.onStart();}});        switch (response.state) {            case Response.STATE_NETWORK_ERROR:onNext = new Action1<Object>() {                    @Overridepublic void call(Object o) {callback.onError(new IOException("mock network error."));}};                break;            case Response.STATE_SERVER_ERROR:onNext = new Action1<Object>() {                    @Overridepublic void call(Object o) {callback.onError(new IOException("mock server error."));}};                break;}        if (onNext != null) {Observable.just(10086).delay(response.delayMillis, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(onNext);}}     public <T> void giveSucce***esult(final Func0<T> dataMethod, final DataApiCallback<T> callback, final Response response) {AndroidSchedulers.mainThread().createWorker().schedule(new Action0() {            @Overridepublic void call() {Observable.create(new Observable.OnSubscribe<T>() {                    @Overridepublic void call(Subscriber<? super T> subscriber) {Log.d("MOCK", "onNext Thread = " + Thread.currentThread().getName());subscriber.onNext(dataMethod.call());subscriber.onCompleted();}}).                delay(response.delayMillis, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new ApiSubcriber(callback));}});}    private static class ApiSubcriber<T> extends Subscriber<T> {        private DataApiCallback<T> callback;        public ApiSubcriber(DataApiCallback<T> callback) {            this.callback = callback;}        @Overridepublic void onStart() {callback.onStart();}        @Overridepublic void onCompleted() {}        @Overridepublic void onError(Throwable e) {callback.onError(e);}        @Overridepublic void onNext(T data) {callback.onSuccess(data);}}
}
  • onResponse()
    方法onResponse()根据“响应策略”来针对一次请求返回一个“响应结果”,默认的策略由方法getMockApiStrategy()提供,子类可以重写它提供其它策略。当然策略对象本身也可以作为参数传递(此时此方法本身也没多大意义了)。
    一个想法是,每一个MockApi类都只需要一个实例,这样它的callCount就可以在程序运行期间得到保持。此外,大多数情况下策略对象只需要一个就行了——它是无状态的,封装算法的一个“函数对象”,为了多态,没办法让它是静态方法。

  • giveErrorResult()
    此方法用来执行错误回调,此时是不需要数据的,只需要根据response来执行一定的延迟,然后返回网络错误或服务器错误。
    注意一定要在main线程上执行callback的各个方法,这里算是一个约定,方便UI层直接操作一些View对象。

  • giveSucce***esult()
    此方法用来执行成功回调,此时需要提供数据,并执行response中的delayMillis延迟。
    参数dataMethod用来提供需要的假数据,这里保证它的执行在非main线程中。
    同样,callback的方法都在main线程中执行。

上面BaseMockApi中的rxjava的一些代码都非常简单,完全可以使用Thread来实现。

提供MockTaskApi

作为示例,这里为ITaskApi提供了一个mock实现类:

public class MockTaskApi extends BaseMockApi implements ITaskApi {    @Overridepublic void getTasks(DataApiCallback<List<Task>> callback) {Response response = onResponse();        if (response.state == Response.STATE_SUCCESS) {Func0<List<Task>> mockTasks = new Func0<List<Task>>() {                @Overridepublic List<Task> call() {                    // here to give some mock data, you can get it from a json file —— if there is.ArrayList<Task> tasks = new ArrayList<>();                    int start = (mCallCount - 1) * 6;                    for (int i = start; i < start + 6; i++) {Task task = new Task();task.name = "Task - " + i;tasks.add(task);}                    return tasks;}};            giveSucce***esult(mockTasks, callback, response);} else {            giveErrorResult(callback, response);}}
}

它的代码几乎不用过多解释,使用代码提供需要的返回数据是非常简单的——就像直接在UI层的Activity中写一个方法来造假数据那样。

小结

无论如何,经过上面的一系列的努力,模拟数据接口的代码已经稍具模块性质了,它可以被动态的开关,不影响最终的release构建,可以为需要测试的数据接口灵活的提供想要的mock实现。

很值得一提的是,整个MockApi模块都是建立在纯java代码上的。这样从UI层请求到数据访问方法的执行,都最终是直接的java方法的调用,这样可以很容易获取调用传递的“请求参数”,这些参数都是java类。而如果mock是建立在网络框架之上的,那么额外的http报文的解析是必不可少的。
仅仅是为了测试的目的,分层设计,让数据访问层可以在真实接口和mock接口间切换,更简单直接些。

最后,造假数据当然也可以是直接读取json文件这样的方式来完成,如果服务器开发人员有提供这样的文件的话。

转载于:https://blog.51cto.com/kiujyhgt/1915501

关于App开发:模拟服务器数据接口 - MockApi相关推荐

  1. Android App开发基础篇—数据存储(SQLite数据库)

    Android App开发基础篇-数据存储(SQLite数据库) 前言:Android中提供了对SQLite数据库的支持.开发人员可以在应用中创建和操作自己的数据库来存储数据,并对数据进行操作. 一. ...

  2. 淘宝app商品详情原数据接口V1新版接口

    item_get_app-获得淘宝app商品详情原数据接口,包括:标题,价格,促销价,优惠券,库存,销量,详情图片,SKU属性,主图等看的到的信息均可以拿到,可应用于商品销售情况分析,电商选品分析,品 ...

  3. 一年白干!程序员赵某仿制老东家APP,获取服务器数据,被判4年6个月

    整理 | 王晓曼 出品 | 程序人生 (ID:coder _life) 近日,中国裁判文书网公布了一起非法获取计算机信息系统数据的案件.这则由北京市朝阳区人民法院发出的刑事判决书显示,被告人赵某某犯非 ...

  4. vue外卖十四:商家详情:用mockjs模拟api数据接口

    1)安装mockjs 官网:mockjs.com 文档:https://github.com/nuysoft/Mock/wiki/Getting-Started http://mockjs.com/e ...

  5. ant-design-pro使用服务器数据接口代理配置

    因为是新入门antd-pro这个的小白,所以在mock数据和服务器数据切换这里搞了将近2天才弄好,配置如下,供各位初学者参考,如有错误的地方,请大神指出~叩谢!! 下面开始干货: 1..roadhog ...

  6. 睡眠监测APP开发了解睡眠数据

    现在都市人的生活节奏和生活压力增加,人们的睡眠质量越来越差,改善睡眠问题有助于人们的身体健康,前提是我们要对于数据的了解.睡眠监测APP开发就是对于一些睡眠时间质量等数据监测,让人们对于自己睡眠质量的 ...

  7. APP开发实战10-APP数据同步方案

    3.3数据同步方案 3.3.1 文件的同步 通常图片都需要在APP端做缓存处理,所以从服务器端返回图片链接的时候,一定要同时返回图片最新修改的时间戳.APP根据本地存储图片的时间戳和从服务器获取的时间 ...

  8. 接口返回数据太大_Vue实战044:Mockjs模拟服务器Api接口并返回数据详解

    前言 现在越来越多项目都采用前后端分离模式开发,这样前后端就可以同时开发,而且互不影响.但是目前项目跟进的很紧,没什么时间写后台,但是前端没接口测试可能会隐藏很多bug,到后面再来排查就麻烦了.所以在 ...

  9. 【万里征程——Windows App开发】文件数据——文件选取器

    使用文件选取器保存文件 就我个人而言,还是非常喜欢使用文件选取器的,因为能够用自己的代码来调用系统的各种弹框. 在这个示例中,首先在XAML中添加一个Button和一个TextBlock,分别命名为b ...

最新文章

  1. python3笔记_python3基础笔记(一)
  2. 深度学习中的Dropout原理介绍
  3. python语言if语句-Python中的if判断语句入门
  4. 虚拟与现实的距离——VR的2016正如移动互联网的2009【下篇】
  5. 2013年7月04日_回顾总结
  6. 【FBI WARNING】一些Noip的黑科技 持续整理!
  7. echarts折线图背景线_echarts设置折线线条颜色和折线点颜色的实例
  8. excel如何找到高频词_拟录取后:应届生和往届生档案哪里找;重灾院校区;高频词背诵表...
  9. 从C/C++零基础到月入9K我用了9个月
  10. Python正则表达式常用flag含义与用法详解
  11. K8S_Google工作笔记0001---K8S学习过程梳理
  12. 智慧水务、智慧泵房、水厂监控、营收管理、DMA漏损、GIS系统、维护管理、档案管理、仓库管理、水质监控、数据中心、指挥调度中心、消防栓、管网、供水、水质、水厂调度、加压泵站、库存调拨、物料申请
  13. 湖大离散数学实验代码汇总
  14. SM6S系列TVS二级管 可通过ISO 7637-2 5a/5b测试
  15. win10 java8 环境变量_win10下安装JDK8和环境变量配置
  16. 佳能A系列数码相机(A720 IS)拍摄技巧大全
  17. 【百度分享】BZFS—一种透明压缩文件系统
  18. 阿里旺旺自动回复工具开发一
  19. \int_0^{+\infty} \frac{\sin x}{x}\mathop{}\!\mathrm{d}{x}
  20. Latex矩阵和表达式组的绘制

热门文章

  1. 苹果开发(二) 申请应用
  2. Emgucv粗略抠取车牌
  3. 15个C++项目列表
  4. java中跳出当前循环怎么做_在java中,如何跳出当前的多重循环?
  5. 共享数据库、共享数据表
  6. 通过Nginx复杂安装
  7. Spring 中常用的设计模式对比
  8. TCP/IP协议的深入分析
  9. 用户退出登录清空cookie
  10. SpringMVC简介-SpringMVC概述