为了账号安全,请及时绑定邮箱和手机立即绑定

Android新项目 - Repository层(上) Retrofit、Repository组装

标签:
Android

如期而至的Repository篇,内部实现则由Realm、Retrofit,以及内存级LruCache组成。
Repository,顾名思义,即仓库,向上层屏蔽了数据来源和内部实现细节,不需要了解货物来源,只需要拿走就行了。

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

Why Repository

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

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

举些例子:

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

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

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

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

Overview


Retrofit

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

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

import

root build.gradle:

[代码]xml代码:

?

1

2

3

4

5

6

7

8

9

def retrofitVersion = "2.0.2"

def okHttpVersion = '3.2.0'

 

project.ext {

    libRetrofit =   "com.squareup.retrofit2:retrofit:${retrofitVersion}"

    libRetrofitConverterGson =   "com.squareup.retrofit2:converter-gson:${retrofitVersion}"

    libRetrofitAdapterRxJava =   "com.squareup.retrofit2:adapter-rxjava:${retrofitVersion}"

    libOkHttpLoggingInterceptor =   "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}"

}

 

repository module的build.gradle:

[代码]xml代码:

?

1

2

3

4

5

6

dependencies {

    compile   rootProject.ext.libRetrofit

    compile   rootProject.ext.libRetrofitConverterGson

    compile   rootProject.ext.libRetrofitAdapterRxJava

    compile   rootProject.ext.libOkHttpLoggingInterceptor

}

 

OkHttpClient

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

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

private OkHttpClient getClient() {

    // log用拦截器

    HttpLoggingInterceptor logging   = new HttpLoggingInterceptor();

 

    // 开发模式记录整个body,否则只记录基本信息如返回200,http协议版本等

    if (IS_DEV) {

        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    } else {

        logging.setLevel(HttpLoggingInterceptor.Level.BASIC);

    }

 

    // 如果使用到HTTPS,我们需要创建SSLSocketFactory,并设置到client

    SSLSocketFactory   sslSocketFactory = null;

 

    try {

        // 这里直接创建一个不做证书串验证的TrustManager

        final   TrustManager[] trustAllCerts = new TrustManager[]{

                new   X509TrustManager() {

                    @Override

                    public   void checkClientTrusted(X509Certificate[] chain, String authType)

                            throws   CertificateException {

                    }

 

                    @Override

                    public   void checkServerTrusted(X509Certificate[] chain, String authType)

                            throws   CertificateException {

                    }

 

                    @Override

                    public   X509Certificate[] getAcceptedIssuers() {

                        return   new X509Certificate[]{};

                    }

                }

        };

 

        //   Install the all-trusting trust manager

        final   SSLContext sslContext = SSLContext.getInstance("SSL");

        sslContext.init(null,   trustAllCerts, new java.security.SecureRandom());

        //   Create an ssl socket factory with our all-trusting manager

        sslSocketFactory   = sslContext.getSocketFactory();

    } catch (Exception e) {

        Logger.e(TAG,   e.getMessage());

    }

 

    return new OkHttpClient.Builder()

            //   HeadInterceptor实现了Interceptor,用来往Request Header添加一些业务相关数据,如APP版本,token信息

            .addInterceptor(new   HeadInterceptor())

            .addInterceptor(logging)

            //   连接超时时间设置

            .connectTimeout(10,   TimeUnit.SECONDS)

            //   读取超时时间设置

            .readTimeout(10,   TimeUnit.SECONDS)

            .sslSocketFactory(sslSocketFactory)

            //   信任所有主机名

            .hostnameVerifier((hostname,   session) -> true)

            //   这里我们使用host name作为cookie保存的key

            .cookieJar(new   CookieJar() {

                private   final HashMap<httpurl, list<cookie="">> cookieStore =   new HashMap<>();

 

                @Override

                public   void saveFromResponse(HttpUrl url, List<cookie> cookies) {

                    cookieStore.put(HttpUrl.parse(url.host()),   cookies);

                }

 

                @Override

                public   List<cookie> loadForRequest(HttpUrl url) {

                    List<cookie>   cookies = cookieStore.get(HttpUrl.parse(url.host()));

                    return   cookies != null ? cookies : new ArrayList<>();

                }

            })

            .build();

}</cookie></cookie></cookie></httpurl,>

 

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

RxJava异步请求

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

public static MrService getInstance() {

    if (mInstance == null) {

        synchronized   (MrService.class) {

            if   (mInstance == null) {

                mInstance   = new MrService();

            }

        }

    }

    return mInstance;

}

 

private MrService() {

    this(true);

}

 

private MrService(boolean useRxJava) {

    Retrofit.Builder builder = new   Retrofit.Builder()

            .baseUrl(IS_DEV   ? API_DEV_URL : API_PRODUCT_URL)

            .addConverterFactory(GsonConverterFactory.create())

            .client(getClient());

    if (useRxJava) {

        builder.addCallAdapterFactory(RxJavaCallAdapterFactory.create());

    }

    mRetrofit = builder.build();

}

 

对应API请求类如

[代码]java代码:

?

1

2

3

4

5

6

7

8

9

public interface SystemApi {

    ...

    @FormUrlEncoded

    @POST("user/feedback")

    Observable<mrresponse>   feedback(@Field("content") String content,

                                    @Field("model_name")   String modelName,

                                    @Field("system_version")   String systemVersion,

                                    @Field("img_keys")   List<string> imageKeyList);

}</string></mrresponse>

 

同步请求

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

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

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

public static MrService getSynchronousInstance() {

    if (mSyncInstance == null) {

        synchronized   (MrService.class) {

            if   (mSyncInstance == null) {

                mSyncInstance   = new MrService(false);

            }

        }

    }

    return mSyncInstance;

}

 

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

[代码]java代码:

?

1

2

3

4

5

6

7

8

9

public interface RongCloudApi {

    @FormUrlEncoded

    @POST("im/getGroupInfo")

    Call<mrresponse>   getGroupInfoSynchronous(@Field("group_id") String groupId);

 

    @FormUrlEncoded

    @POST("user/nameCardLite")

    Call<mrresponse>   getNameCardLiteSynchronous(@Field("uid") String userId);

}</mrresponse></mrresponse>

 

数据格式解析

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

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

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

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

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

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

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

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

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

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

[代码]java代码:

?

1

2

3

4

5

@SerializedName("status_no")

private int statusCode;

 

@SerializedName("status_msg")

private String statusMessage;

 

根据statusCode可以快速判断是否出现错误,通常0或者某个正数为正确,负数则根据和服务器的协定做不同处理。
这里对Gson的bean,推荐使用插件GsonFormat,生成起来很方便。

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

二次的组合解析

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

public class CommonResponse {

 

    @SerializedName("status_no")

    private int statusCode;

 

    @SerializedName("status_msg")

    private String statusMessage;

 

    @SerializedName("time")

    private long time;

 

    @SerializedName("data")

    public Object data;

 

    // setter and getter

}

 

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

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

public static <t> Observable<t>   extractData(Observable<mrresponse> observable, Class<t> clazz) {

    return   observable.flatMap(response -> {

        if   (response == null) {

            return   Observable.error(new NetworkConnectionException());

        } else   if (response.getStatusCode() == ResponseException.STATUS_CODE_SUCCESS) {

            return   Observable.just(mGson.fromJson(mGson.toJson(response.data), clazz));

        } else   {

            Logger.e(TAG,   response.data);

            return   Observable.error(new ResponseException(response));

        }

    });

}</t></mrresponse></t></t>

 

调用则如:

[代码]java代码:

?

1

2

3

4

@Override

public Observable<albumapiresult>   listPhoto(String uid) {

    return   RepositoryUtils.extractData(mAlbumApi.listPhoto(uid), AlbumApiResult.class);

}</albumapiresult>

 

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

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

单次的继承处理

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

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

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

public class BaseResponse {

 

    @SerializedName("status_no")

    private int statusCode;

 

    @SerializedName("status_msg")

    private String statusMessage;

 

    @SerializedName("time")

    private long time;

 

    // setter and getter

}

 

public class ConcreteResponse extends BaseResponse {

 

    @SerializedName("other_fields")

    private String otherFields;

 

    // ...

}

 

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

Repository层组装实现

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

基于接口的设计实现

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

[代码]java代码:

?

1

2

3

public interface QiniuRepository {

    Observable<qiniutoken>   getQiniuUploadToken();

}</qiniutoken>

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

public class QiniuDataRepository implements   QiniuRepository {

 

    @Inject

    protected QiniuApi mQiniuApi;

 

    @Inject

    public QiniuDataRepository() {

    }

 

    @Override

    public   Observable<qiniutoken> getQiniuUploadToken() {

        return   RepositoryUtils.extractData(mQiniuApi.getQiniuUploadToken(),   QiniuToken.class);

    }

}</qiniutoken>

 

DataStoreFactory

使用DataStoreFactory封装数据来源:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

@Singleton

public class UserDataStoreFactory {

 

    private final Context mContext;

    private final UserCache   mUserCache;

 

    @Inject

    protected UserApi mUserApi;

 

    @Inject

    public   UserDataStoreFactory(Context context, UserCache userCache) {

        if   (context == null || userCache == null) {

            throw   new IllegalArgumentException("Constructor parameters cannot be   null!!!");

        }

        mContext   = context.getApplicationContext();

        mUserCache   = userCache;

    }

 

    /**

     * Create {@link   UserDataStore} from a user id.

     */

    public UserDataStore   create(String userId) {

        UserDataStore   userDataStore;

 

        if (!mUserCache.isExpired()   && mUserCache.isCached(userId)) {

            userDataStore   = new DiskUserDataStore(mUserCache);

        } else   {

            userDataStore   = createCloudDataStore();

        }

 

        return   userDataStore;

    }

 

    /**

     * Create {@link   UserDataStore} to retrieve data from the Cloud.

     */

    public UserDataStore   createCloudDataStore() {

        return   new CloudUserDataStore(mUserApi, mUserCache);

    }

}

 

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

在实现方法中组合

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

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

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

@Override

public   Observable<list<operationpositionwrapper>> getHome(final boolean   isForceRefresh) {

    final   Observable<list<operationpositionwrapper>> fromCache =   Observable.create(

            new   Observable.OnSubscribe<list<operationposition>>() {

                @Override

                public   void call(Subscriber<!--? super List<OperationPosition-->>   subscriber) {

                    List<operationposition>   dbCache = new Select().from(OperationPosition.class).queryList();

                    if   (dbCache != null) {

                        subscriber.onNext(dbCache);

                    }

                    subscriber.onCompleted();

                }

            })

            .map(new   Func1<list<operationposition>,   List<operationpositionwrapper>>() {

                @Override

                public   List<operationpositionwrapper> call(List<operationposition>   operationPositions) {

                    return   OperationPositionMapper.wrap(operationPositions);

                }

            })

            .filter(new   Func1<list<operationpositionwrapper>, Boolean>() {

                @Override

                public   Boolean call(List<operationpositionwrapper> operationPositionWrappers)   {

                    return   ListUtils.isNotEmpty(operationPositionWrappers);

                }

            });

 

    final   Observable<list<operationpositionwrapper>> fromNetwork =   RepositoryUtils.observableWithApi(new GetOperationPositionsForYouleHomeApi())

            .map(new   Func1<list<operationpositionpo>,   List<operationpositionwrapper>>() {

                @Override

                public   List<operationpositionwrapper> call(List<operationpositionpo>   operationPositionList) {

                    return   OperationPositionMapper.transform(operationPositionList);

                }

            })

            .doOnNext(new   Action1<list<operationpositionwrapper>>() {

                @Override

                public   void call(List<operationpositionwrapper> operationPositionWrappers) {

                    if   (ListUtils.isNotEmpty(operationPositionWrappers)) {

                        new   Delete().from(OperationPosition.class).queryClose();

                    }

                    for   (OperationPositionWrapper wrapper : operationPositionWrappers) {

                        wrapper.getOperationPosition().save();

                    }

                }

            });

 

    if (isForceRefresh) {

        return   fromNetwork;

    } else {

        return   Observable.concat(fromCache, fromNetwork);

    }

}</operationpositionwrapper></list<operationpositionwrapper></operationpositionpo></operationpositionwrapper></operationpositionwrapper></list<operationpositionpo></list<operationpositionwrapper></operationpositionwrapper></list<operationpositionwrapper></operationposition></operationpositionwrapper></operationpositionwrapper></list<operationposition></operationposition></list<operationposition></list<operationpositionwrapper></list<operationpositionwrapper>

 


总结

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

 原文链接:http://www.apkbus.com/blog-705730-60930.html


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消