一种Android数据mock方案

    在一般业务开发流程中,会对需求进行评审,而评审之后就是,总是会有这样的一种情况,服务端和客户端都需要根据需求或者UI开始进行联调前开发,而之后才会进入前后端的接口联调阶段。而这个阶段中,服务端一般只会给到一个API文档,用于定义各个模块中的数据结构。作为Android开发,我们需要根据UI页面或者API文档去进行Mock,以便于我们查看大致的UI效果。一般会有以下几种方式。

1,通过手动创建将数据传递到UI层中实现临时的数据mock。

val nameItems = {"张三","王五","李四"}

val adapter = Adapter(…,nameItems)

RecycleView.setAdapter(adapter)

优点:最简单,省事儿。

缺点:写法不规范,交互链路不完整,有一个地方就得复制粘贴一次,修改成本高。

2,NodeJS搭建mock服务器。

优点:数据编写灵活,可以实时根据不同的路径配置不同的返回数据json文件,客户端可以根据接口示例文档完善网络交互逻辑,联调时切换服务器地址即可。

缺点:每个请求地址都需要配置,且数据结构无法复用,修改数据操作成本高。

3,使用Apifox等三方工具。

优点:数据编写灵活,可自定义mock规则,自定义各类相应状态,数据结构可以复用、导出等。

缺点:更加适合服务端,对于客户端来说编写每个接口及其数据结构略显麻烦。

    其实可以看到第三种已经是比较符合我们想要的一种方式了,只需要按照正常网络的请求流程去请求该mock服务器对应的地址就能根据你自行创建的接口数据进行返回,但是它最大的问题主要还是不够灵活,像数据实体其实我们本身在使用网络之前就会在编码中定义好了,然后又需要在那边再定义一次,如果接口比较多的话,这也是个体力活,又要定义数据类,又要创建接口,还要建立关联等,如果后续数据结构发生了变化,还需要去修改数据结构。那有没有一种方式可以规范网络请求的流程,有比较省事儿的呢,其实是有的。

这里分为2点来说:

1,规范网络请求流程。

为了能够不影响执行我们的网络请求,在实际的场景中可以使用本地服务器方式来响应网络请求。大致交互如下:

前面我们说了,这个阶段后端远程服务器的内容还没有准备好,所以我们需要将请求指向我们自行创建的Mock服务器,从而使流程规范化,而创建本地服务器的方法有很多,像AndroidAsync,AndServer、httpd等,我们只需要使用我们自己定义的拦截器将请求重新定向到本地服务器,再将其响应数据返回到请求链中。到此,交互是解决了,但是还缺少最重要的东西,也就是我们的响应数据,接着看下面第2点。

2,省事儿。

以我们常用的Retrofit为例,大多数情况下,我们都会在一个类似ApiService的类中去定义我们的接口声明,比如下面这个:

public interface ApiService {

    @GET("/user/info")   

    Call<HttpResult<UserInfo>> getUserInfo(@Body Request req);

    @GET("/user/info")

    Observable<HttpResult<UserInfo>> getUserInfo(@Body Request request);

}

这里例举了使用RxJava3/2CallAdapterFactory和不使用的情况,在大多数情况下,其实我们希望得到的都是返回值中泛型参数一HttpResult<UserInfo>,HttpResult是我们定义的常用的统一返回数据类,它的常用结构如下:

data class HttpResult<T>(

    val data: T?,

    val code: Int,

    val msg: String

)

而UserInfo则是用户的一些信息,头像、昵称什么的,也就是说,我们完全可以自行创建一个这样的对象,然后再将这个对象传递给我们第1步中的本地服务器,由它去管理这个数据,当路径为/user/info的请求过来时,直接将HttpResult<UserInfo>对应的数据返回回去就好了。但是如果只是这种程度的话,和我们之前说的通过ApiFox等工具一样的麻烦,所以我们需要使用我们最大的优势,那就是我们拥有和源码的关联性,可以利用Java中的反射来帮我们做很多事情。当然,这里也需要考虑几个问题。

如何将Path与Bean类数据相关联?

如何生成想要的数据?

如何对数据进行控制?

那么接下来就针对这几个问题去做处理,

如何将Path与Bean类数据相关联?

如果Retrofit这种形式的网络请求,可以通过获取到这个接口的Class对象,ApiService.class,通过它可以获取所有的成员函数Method,然后通过Method就可以获取到上面的注解以及注解中的值,比如上例中的@GET("/user/info") ,常用的请求方式基本上就4种,PUT/GET/DELETE/POST,所以通过判断其类型就可以做区分,然后拿到注解的中的value,也就是/user/info,这就得到了Path。然后,通过Method还能拿到它的返回值,Call<HttpResult<UserInfo>>,而最终拿到HttpResult<UserInfo>,此时Path就和Bean类数据相关联了,/user/info->HttpResult<UserInfo>,我们可以使用Map将其进行存储。

如果是非Retrofit这种形式,我们只能借助自行传递的Json文件或者类似与List<MockData>在MockData定义好它的请求方式、path、以及具体的json数据,然后传递到上文中提到的Mock服务器那边,最终也可以通过Map将其进行存储。

如何生成想要的数据?

上一个问题中解决了它的关联问题,接下来则是数据生成的问题,类似于HttpResult<UserInfo>这样的一个Class我们拿到了,那么要如何去生成我们想要的Json数据呢?我们都知道Gson.toJson(),FastJson.toJSONString都能做到将对象转换为Json数据,那么问题就只剩下了对象的创建。

对象的创建常见以下方式:

构造器:一个ApiService中定义的Bean类会有很多,不可能每一个对象都通过构造器去做创建,直接就放弃了。

反射:通过Class得到构造器对象,通过构造器调用newInstance(...)得到对象。但是在实际开发中,尤其是现在很多Android开发基本上都使用Kotlin,也就会用到data class这种带参的构造器声明方式,空参构造器被替代,也就不能直接创建了,因为即便是反射,它的参数对于一个通用的反射创建对象的方式来说是不确定的,所以无法使用这种方式。

Gson中的UnsafeAllocator:既然走反射也走不通,那么为什么Gson可以反序列化data class(带参构造器)呢?其实看源码就知道了,UnsafeAllocator中,有个newInstance函数,通过它就可以直接创建出一个对象,而其原理则是使用了sun.misc.Unsafe中的allocateInstance函数,对此函数的反射调用即可以不通过构造器创建出对象实例,具体原理不再细说。

通过上面的方法得到了一个对象,然后将这个对象传递给Gson等序列化的框架就可以转换为Json数据,然后传递给服务器使用了。至此,一个大致的交互流程就出来了,

在请求网络之前,创建本地Mock服务器,然后解析ApiService对象得到path->bean关联关系的数据并将数据提供给服务器。

在请求时,通过拦截器中的Path重定向到本地Mock服务器,本地Mock服务器匹配Path对应的数据内容并将其返回,最后拦截器将返回数据继续在链上(OkHttp的拦截器链)返回完成请求。

流程是有了,但是还缺少一些细节,对象是有了,对象中字段的数据怎么来的呢?所以也就有了下面这个问题。

如何对数据进行控制?

一般来说可以通过干涉对象的创建过程来做到,比如前面提到的HttpResult<UserInfo>对象,HttpResult中code和msg这种都是基本类型比较简单,大部分的Bean类基本上都是由基本类型构成的,我们在解析过程中通过判断它的类型返回对应的值,如果需要随机则随机生成,如果需要固定值或者说需要在范围内选取等,都可以通过注解去标记该字段,然后再在解析过程中通过不同的注解类型得到注解的值然后将其赋值给该字段,而data这类对象类型的,无非就是深层次的遍历,这样下来,就能得到一个比较完整的数据源,能够满足日常所需了。

可以看到为了省事儿还是很不省事儿的,但是这个流程一旦建立后基本上也没有太多需要额外的工作了,接下来的事情则是在后端完成开发以后,涉及到的地方做一下Bean类的部分修改,然后将其服务器地址切换到后端服务器上就好了。以下为我自己的一个实现,如果说你还有更好的方式和想法,欢迎一起来讨论。

https://github.com/woilsy/android-mock

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容