微信号:androidtrending

介绍:Android开发最佳实践、一线经验分享、技术前沿,最好用的Android开发工具、服务、开源项目.

从零开始的Android新项目5 - Repository层(上) Retrofit、Repository组装

2016-05-16 08:33 MarkZhai

声明:本文为 MarkZhai 原创,授权发布在Android程序员公众号,转载请参考原文协议。

原文:http://blog.zhaiyifan.cn/2016/04/30/android-new-project-from-0-p5/


MarkZhai从零开始系列的第5篇,很早前就挖好坑,上周末刚填完,可能有些同学周末已经看过,这里还是推荐给大家,按例放上前4篇链接方便没看过的同学,本系列已经接近大结局,我这边会尽量有始有终:

从零开始的Android新项目1 - 架构搭建篇

从零开始的Android新项目2 - Gradle篇

从零开始的Android新项目3 - MVPVM in Action, 谁告诉你MVP和MVVM是互斥的

从零开始的Android新项目4 - Dagger2篇


如期而至的Repository篇,内部实现则由Realm、Retrofit,以及内存级LruCache组成。

Repository,顾名思义,即仓库,向上层屏蔽了数据来源和内部实现细节,不需要了解货物来源,只需要拿走就行了。

由于篇幅问题,将分为上下两篇,本篇主要介绍Retrofit的应用和Repository层组装,下篇会讲解本地缓存(包括Realm和内存缓存)以及基于异常的设计。

Why Repository

首先,为什么我们需要Repository层呢?一言以蔽之,屏蔽细节。

上层(activity/fragment/presenter)不需要知道数据的细节(或者说 - 数据源),来自于网络、数据库,亦或是内存等等。如此,一来上层可以不用关心细节,二来底层可以根据需求修改,不会影响上层,两者的分离用可以帮助协同开发。

举些例子:

  • 当现在是无网状态,我希望列表能直接显示上一次的数据,而不会是空页面。

  • 除非好友的用户数据过期(比如超过一天),否则希望直接使用本地缓存中的,但如果缓存没有,或者过期,则需要拉取并更新。

  • 点赞后,即便请求还没发送或者没有收到response,仍然希望显示点赞后的状态。
    等等。

如果这些需求,我们都要实现在View或者Presenter中,就会导致充斥大量数据逻辑,目的不单一,难以维护。而Repository层就是来封装这些逻辑的。

Overview

如图,业务层只能看到repository接口。

Retrofit

Retrofit是Android界网红公司Square所开发维护的一个HTTP网络库,目前最新版本是2.0.2(截止2016年4月30日)。其内部使用了自家的OkHttp。

关于Retrofit的实现机制啊简介的,网上已经很多了,这里我就不啰嗦了,官方文档见项目主页(http://square.github.io/retrofit/)。这里主要讲讲实际项目中的应用实践。

import

root build.gradle:


repository module的build.gradle:


OkHttpClient

自底向上地,我们需要一个OkHttpClient来设置给Retrofit,这里作为实例,放出一段包含大部分你可能会用到的功能的Client创建代码,可以根据需要进行调整。


如上包含了大部分你可能需要的特性,可以自由进行组合。

RxJava异步请求


对应API请求类如


同步请求

有时候我们需要做同步请求,比如提供结果给一些第三方库,它们可能需要直接返回对应数据(像我最近碰到的融云….),而我们只需要拉数据同步返回,对其所在线程和调用事件均一脸懵逼。

这时候就需要创建一个同步的retrofit客户端,其实就是不要去使用RxJava的adapter啦。


对应地,我们需要定义请求类,这里我们需要使用Call<>去包一下最终解析对象的类。


数据格式解析

数据的解析当然是必不可少的一环了,常用格式对应的序列化库以retrofit官网为例:

  • Gson: com.squareup.retrofit2:converter-gson

  • Jackson: com.squareup.retrofit2:converter-jackson

  • Moshi: com.squareup.retrofit2:converter-moshi

  • Protobuf: com.squareup.retrofit2:converter-protobuf

  • Wire: com.squareup.retrofit2:converter-wire

  • Simple XML: com.squareup.retrofit2:converter-simplexml

  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

部分高大上公司可能自己使用内部的二进制格式,自己实现ConverterFactory去解析就行了。

这里以最常用的json为例,使用GsonConverterFactory,良好的数据结构通常都会带有状态码和对应信息:


根据statusCode可以快速判断是否出现错误,通常0或者某个正数为正确,负数则根据和服务器的协定做不同处理。

这里对Gson的bean,推荐使用插件GsonFormat,生成起来很方便。

至于具体的数据,则有两种方案,一是使用data作为key把具体数据套起来,内部则使用K/V进行存储,保证不存在不规范的直接丢一个array在data里面的情形。

二次的组合解析


二次组合的解析通过将创建一个通用的Response Bean来做泛解析,如果statusCode表明接口请求成功,则继续解析data:


调用则如:


所有接口都可以通过RepositoryUtils.extractData()进行泛型调用。

如此一来,如果response为空,我们仅在statusCode正确时才会去解析具体的数据,否则抛出对应的异常(基于异常的数据层设计在下面会具体讲)。

单次的继承处理

上一种处理方式尽管看起来很优雅,但是存在一个问题,就是会重复解析,当statusCode正确时,会对data的object再次进行json处理。如果确实是error,比如statusCode为-1、-2这种,确实节省了开销,因为gson会去反射构造对应类的adapter,解析所有字段,创建对应的BoundField。

但考虑到大部分情况下还是正确的response居多,所以也可以使用继承的结构,我们创建BaseResponse存放通用字段,其他所有Gson Bean则继承该BaseResponse。


对应的判断和error抛出可以参照上小节的,这里就不赘述了。

Repository层组装实现

组装即根据组合各个数据源,如此又分为直接在实现方法中组合结果,亦或是通过DataStoreFactory进行封装。根据复杂度和个人喜好而定,毕竟使用后者需要新增好多类,相对来说有一点重。

基于接口的设计实现

拿一个最简单的repository,七牛Repository来作例子:



DataStoreFactory

使用DataStoreFactory封装数据来源:


老实说这样的话,一来要写很多方法和接口,二来通过Factory判断创建哪种DataStore还是挺麻烦的,比如用户主页数据我们可以判断,但登陆登出这些,就需要直接指定createCloudDataStore()了,所以个人认为意义不大。

在实现方法中组合

如下是使用DBFlow和网络Api进行组合的一个list获取接口。

我们使用RxJava的concat组合2个Observable,前者从cache(数据库)获取数据,后者从网络Api获取数据,通常数据库当然会更快。我们还保留了一个参数isForceRefresh来保证在某些情况下可以强制从网络获取数据。


总结

本篇为Repository层的上篇,主要介绍了组合及Retrofit的应用。下篇将会讲述数据库,内存Cache,以及统一的异常处理设计。

另外,打个小广告,本司的新产品Crew已经在各大Android应用市场上线,专注于职场垂直社交。一搜和兴趣相投的人聊天。iOS版本正在审核中。

2个字找到志趣相投的职场伙伴,秒搜陌生人同类,智能自动破冰。多关键字叠加,高效率锁定职场同僚。精准匹配兴趣对象,超轻聊天,更能一键组建群聊,加入一群人的狂欢。

 
Android程序员 更多文章 如何姿势正确地做一个充满果味的Android应用 如何全面掌握别人家的App数据? Android应用框架最佳实践 LeakCanary:跟OOM说再见 传说中的Android军火库
猜您喜欢 知乎,如何用数据管理内容(30PPT) 【S-Tech】没红包,怎么聊?| 红包社交化研究(主创语音秀) 明晚公开课:测试经验分享 简单线性回归——OLS回归模型拟合(一) 7大DevOps支柱:企业成功的基础