您好,欢迎来到源码搜藏!分享精神,快乐你我!提示:担心找不到本站?在百度搜索“源码搜藏”,网址永远不丢失!
  • 首 页
  • 在线工具
  • 当前位置:首页 > 安卓源码 > 技术博客 >

    为什么我们需要Repository层呢?

    时间:2016-12-08 15:01 来源:互联网 作者:源码搜藏 浏览:收藏 挑错 推荐 打印

    如期而至的Repository篇,内部实现则由Realm、Retrofit,以及内存级LruCache组成。 Repository,顾名思义,即仓库,向上层屏蔽了数据来源和内部实现细节,不需要了解货物来源,只需要拿走就行了。 由于篇幅问题,将分为上下两篇,本篇主要介绍Retrofit的应用

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

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

    Why Repository

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

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

    举些例子:

    • 当现在是无网状态,我希望列表能直接显示上一次的数据,而不会是空页面。
    • 除非好友的用户数据过期(比如超过一天),否则希望直接使用本地缓存中的,但如果缓存没有,或者过期,则需要拉取并更新。
    • 点赞后,即便请求还没发送或者没有收到response,仍然希望显示点赞后的状态。
      等等。

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

    Overview

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

    为什么我们需要Repository层呢?

    Retrofit

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

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

    import

    root build.gradle:

    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:

    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创建代码,可以根据需要进行调整。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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();
    }
    

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

    RxJava异步请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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请求类如

    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);
    }
    

     

    同步请求

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    public static MrService getSynchronousInstance() {
        if (mSyncInstance == null) {
            synchronized (MrService.class) {
                if (mSyncInstance == null) {
                    mSyncInstance = new MrService(false);
                }
            }
        }
        return mSyncInstance;
    }
    

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

    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);
    }
    

     

    数据格式解析

    数据的解析当然是必不可少的一环了,常用格式对应的序列化库以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,良好的数据结构通常都会带有状态码和对应信息:

    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里面的情形。

    二次的组合解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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));
            }
        });
    }
    

    调用则如:

    1
    2
    3
    4
    
    @Override
    public Observable<AlbumApiResult> listPhoto(String uid) {
        return RepositoryUtils.extractData(mAlbumApi.listPhoto(uid), AlbumApiResult.class);
    }
    

     

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

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

    单次的继承处理

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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来作例子:

    1
    2
    3
    
    public interface QiniuRepository {
        Observable<QiniuToken> getQiniuUploadToken();
    }
    

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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);
        }
    }
    

    DataStoreFactory

    使用DataStoreFactory封装数据来源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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来保证在某些情况下可以强制从网络获取数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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);
        }
    }
    

    总结

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

    为什么我们需要Repository层呢?转载http://www.codesocang.com/anzhuoyuanma/boke/33980.html
    标签:网站源码