Android TMvp+RxJava+Retrofit 封装

前言

mvp模式,rxjava响应式编程,retrofit网络强求已经出来很久了,很多前辈写了很多不错的文章,在此不得不感谢这些前辈无私奉献的开源精神,能让我们站在巨人的肩膀上望得更远。(我仅仅是开源代码的搬运工和组装工)对于这些知识点介绍几位杰出的大神。

重大消息 : 新版本出来啦 详情请看 新版本

mvp插件:AndroidStudio MVPPlugin插件
mvp项目:比较好的mvp模式的demo
RxJava :给 Android 开发者的 RxJava 详解
Retrofit:Android Retrofit 2.0使用

本文内容是基于TMvp + Retrofit + RxJava做的一些简单的搬运和封装。参考了很多文章(主要参考的是小河马大神的Android RxJava+Retrofit完美封装 - 缓存,请求,生命周期管理)加入了一些自己的理解,请多指教,后续有什么问可github留言。

主题

本栏目主要由三个部分组成(如下图)

Tmvp.png

很明显,第一部分就是简单的mvp(泛型)模式而已,第二部分主要是rxjava和retrofit的简单封装(没有生命周期管理,请求,缓存等),而第三部分就是解决第二部分没有得动作。

第一部分(TMvp)

MVP架构模式细解:MVP是Model(数据,网络) View(界面) Presenter(表现层)的缩写,它是MVC架构的变种,强调Model和View的最大化解耦和单一职责原则

  1. Model:负责数据的来源和封装,比如网络请求类,数据库操作类以及java bean,如果有必要则提供接口暴露自己处理数据的状态和进度,自己写一个回调就行
  2. View:负责UI相关,如布局UI的初始化,各种listener的设置。在Android中,我们通常写的Activity和Fragment就是属于View层;在web开发中,html则是View层
  3. Presenter:专门从C独立出来的业务逻辑层,主要负责处理原先View层的业务逻辑,解决了Activity的臃肿问题,让Activity只负责处理UI,职责更加明确;并且将View层的业务逻辑抽取到P层之后,View层与Model层也实现了解耦;便于后期代码的扩展和维护,并且业务逻辑层独立后代码还得到很大的重用性
  4. 总结:MVC模式下,V和C纠缠不清,并且View和Model相互关联,而MVP模式下Model和VIew解耦,便于单元测试,项目维护,代码重用

包目录结构:

  • model : LoginModel
  • presenter:LoginPresenter
  • view:Activity、Fragment、etc
  • base:BaseAcitivity、BasePresenter、etc
  • mvp:IView、IModel、IPresenter
  • contract: 契约类、接口定义
  • unitls:工具栏

第二部分(Rxjava+Retrofit)(略)

这部分可以在第三部分更加深入的了解

第三部分 (最终版)

先看最终封装出来的简单是用(HttpUtils),这里我只做简单的陈述和使用,具体细节请认真查看Android RxJava+Retrofit完美封装(缓存,请求,生命周期管理)小河马大神已经写的很详细呢,有些地方呢可能要结合项目代码来消化。一遍不能呢两边。两边不行呢在继续看。敲一遍。搞定。也可以看这篇RxJava 与 Retrofit 结合的最佳实践,这两篇大同小异,只不过呢马神呢在后一篇的基础上添加了生命周期的管理,对于大神来说就是追求完美。

public class HttpUtil {

    /**
     * 构造方法私有
     */
    private HttpUtil() {
    }

    /**
     * 在访问HttpUtil时创建单例
     */
    private static class SingletonHolder {
        private static final HttpUtil INSTANCE = new HttpUtil();
    }

    /**
     * 获取单例
     */
    public static HttpUtil getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 添加线程管理并订阅
     * @param ob
     * @param subscriber
     * @param cacheKey 缓存kay
     * @param event Activity 生命周期
     * @param lifecycleSubject
     * @param isSave 是否缓存
     * @param forceRefresh 是否强制刷新
     */
    public void toSubscribe(Observable ob,
                            final ProgressSubscriber subscriber,
                            String cacheKey,
                            final ActivityLifeCycleEvent event,
                            final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject,
                            boolean isSave,
                            boolean forceRefresh) {
        //数据预处理
        Observable.Transformer<HttpResult<Object>, Object> result = RxHelper.handleResult(event,lifecycleSubject);
        Observable observable = ob.compose(result)
                .doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        //显示Dialog和一些其他操作
                        subscriber.showProgressDialog();
                    }
                });
        RetrofitCache.load(cacheKey,observable,isSave,forceRefresh).subscribe(subscriber);
    }
}

上述呢已经很明显单例获取对象直接调用toSubscribe添加线程管理并订阅,下面一次介绍订阅参数:

第一个参数:

Observable,简单点就是Retrofit定义返回的Observable对象(当然肯定是对Retrofit的一个封装,例子直接看http包下面的Api等)

public class Api {

    private static ApiService SERVICE;

    /**
     * 请求超时时间
     */
    private static final int DEFAULT_TIMEOUT = 5;

    public static ApiService getDefault(){
        if (SERVICE == null) {

            /**
             * 手动创建一个OkHttpClient并设置超时时间
             */
            OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
            httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);

            /**
             * 对所有请求添加请求头(全局header,可局部动态添加header)
             */
            httpClientBuilder.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request();
                    okhttp3.Response originalResponse = chain.proceed(request);
                    return originalResponse.newBuilder().header("key1", "value1").addHeader("key2", "value2").build();

                }
            });

            SERVICE = new Retrofit.Builder()
                    .client(httpClientBuilder.build())
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .baseUrl(Url.BASE_URL)
                    .build().create(ApiService.class);

        }
        return SERVICE;
    }

}

这里呢是简单的Retrofit的封装,需要调用什么接口自行在ApiService里加就ok,我这里直接定于简单的三个而已,纯粹实践:

public interface ApiService {

    //登录接口
    @GET("/student/mobileRegister")
    Observable<HttpResult<UserEntity>> login(@Query("phone") String phone, @Query("password") String psw);

    //获取电影接口
    @GET("top250")
    Observable<HttpResult<List<Subject>>> getTopMovie(@Query("start") int start, @Query("count") int count);

    //获取用户接口
    @GET("top250")
    Observable<HttpResult<Subject>> getUser( @Query("touken") String touken);

    //后续需要什么样的自行添加就行了。记住返回的一定要是Observable对象
}

然后直接

Observable observable = Api.getDefault().getTopMovie(start, count);

返回的就是第一个参数需要的对象了。

第二个参数:

ProgressSubscriber,这里呢我们来想一个问题:在Rxjava中我们什么时候来显示Dialog呢。一开始觉得是放在Subscriber<T>的onStart中。onStart可以用作流程开始前的初始化。然而 onStart()由于在 subscribe()发生时就被调用了,因此不能指定线程,而是只能执行在 subscribe()被调用时的线程。所以onStart并不能保证永远在主线程运行。这怎么办呢!

howtodo.jpg

这里我们自定义一个类ProgressSubscriber继承Subscriber<T>

public abstract class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener {

    private SimpleLoadDialog dialogHandler;
    private boolean isConnected;

    public ProgressSubscriber(Context context) {
        isConnected = NetUtils.isConnected(context);
        dialogHandler = new SimpleLoadDialog(context,this,true);
    }

    @Override
    public void onCompleted() {
        dismissProgressDialog();
    }

    @Override
    public void onNext(T t) {
        _onNext(t);
    }

    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
        if (isConnected) { //这里自行替换判断网络的代码
            _onError("网络不可用");
        } else if (e instanceof ApiException) {
            _onError(e.getMessage());
        } else {
            _onError("请求失败,请稍后再试...");
        }
        dismissProgressDialog();
    }

    @Override
    public void onCancelProgress() {
        if (!this.isUnsubscribed()) {
            this.unsubscribe();
        }
    }


    /**
     * 显示Dialog
     */
    public void showProgressDialog(){
        if (dialogHandler != null) {
            dialogHandler.show();
        }
    }

    /**
     * 隐藏Dialog
     */
    private void dismissProgressDialog(){
        if (dialogHandler != null) {
            dialogHandler.dismiss();
            dialogHandler=null;
        }
    }


    protected abstract void _onNext(T t);
    protected abstract void _onError(String message);
}

初始化ProgressSubscriber新建了一个我们自己定义的ProgressDialog并且传入一个自定义接口ProgressCancelListener。此接口是在SimpleLoadDialog消失onCancel的时候回调的。用于终止网络请求。
ProgressSubscriber其他就很简单了,在onCompleted()和onError()的时候取消Dialog。需要的时候调用showProgressDialog即可。

第三个参数:

缓存kay,就是一个字符串,没什么可介绍的。最好放到常量类里面。方便管理

第四个参数:

ActivityLifeCycleEvent,简单就是一个枚举,而里面就是activity的生命周期:

public enum ActivityLifeCycleEvent {
    CREATE,
    START,
    RESUME,
    PAUSE,
    STOP,
    DESTROY
}

第五个参数:

PublishSubject<ActivityLifeCycleEvent>,Activity生命周期管理,基本的网络请求都是向服务器请求数据,客户端拿到数据后更新UI。但也不排除意外情况,比如请求回数据途中Activity已经不在了,这个时候就应该取消网络请求。
要实现上面的功能其实很简单,两部分

随时监听Activity(Fragment)的生命周期并对外发射出去; 在我们的网络请求中,接收生命周期
并进行判断,如果该生命周期是自己绑定的,如Destory,那么就断开数据向下传递的过程
实现以上功能需要用到Rxjava的Subject的子类PublishSubject
在你的BaseActivity中添加如下代码

public abstract class BaseActivity<V extends IView,T extends BasePresenter<V>> extends AppCompatActivity implements IView {
    public T mPresenter;

    /**
     *  基本的网络请求都是向服务器请求数据,客户端拿到数据后更新UI。但也不排除意外情况,比如请求回数据途中Activity已经不在了,这个时候就应该取消网络请求。
     *  要实现上面的功能其实很简单,两部分
     *
     *  随时监听Activity(Fragment)的生命周期并对外发射出去; 在我们的网络请求中,接收生命周期
     *  并进行判断,如果该生命周期是自己绑定的,如Destory,那么就断开数据向下传递的过程
     *  实现以上功能需要用到Rxjava的Subject的子类PublishSubject
     *
     */

    public final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject = PublishSubject.create();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        lifecycleSubject.onNext(ActivityLifeCycleEvent.CREATE);
        super.onCreate(savedInstanceState);
        mPresenter= getInstance(this,1);
        mPresenter.attachView((V) this);
    }

    @Override
    protected void onStart() {
        lifecycleSubject.onNext(ActivityLifeCycleEvent.START);
        super.onStart();
    }

    @Override
    protected void onResume() {
        lifecycleSubject.onNext(ActivityLifeCycleEvent.RESUME);
        super.onResume();
    }

    @Override
    protected void onPause() {
        lifecycleSubject.onNext(ActivityLifeCycleEvent.PAUSE);
        super.onPause();
    }

    @Override
    protected void onStop() {
        lifecycleSubject.onNext(ActivityLifeCycleEvent.STOP);
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        lifecycleSubject.onNext(ActivityLifeCycleEvent.DESTROY);
        if (mPresenter!=null)
        mPresenter.detachView();
    }

    @Override
    public Context getContext(){
        return this;
    }

    @Override
    public PublishSubject getLifeCycle() {
        return lifecycleSubject;
    }

    public  <T> T getInstance(Object o, int i) {
        try {
            return ((Class<T>) ((ParameterizedType) (o.getClass()
                    .getGenericSuperclass())).getActualTypeArguments()[i])
                    .newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        return null;
    }
}

这样的话,我们把所有生命周期事件都传给了PublishSubject了,或者说PublishSubject已经接收到了并能够对外发射各种生命周期事件的能力了。(直接调用getLifeCycle就获取到了PublishSubject了)

现在我们要让网络请求的时候去监听这个PublishSubject,在收到相应的生命周期后取消网络请求,这又用到了我们神奇的compose(),我们需要修改handleResult代码如下

public static <T> Observable.Transformer<HttpResult<T>, T> handleResult(final
                                                                            ActivityLifeCycleEvent event, final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject) {
        return new Observable.Transformer<HttpResult<T>, T>() {
            @Override
            public Observable<T> call(Observable<HttpResult<T>> tObservable) {
                Observable<ActivityLifeCycleEvent> compareLifecycleObservable =
                        lifecycleSubject.takeFirst(new Func1<ActivityLifeCycleEvent, Boolean>() {
                            @Override
                            public Boolean call(ActivityLifeCycleEvent activityLifeCycleEvent) {
                                return activityLifeCycleEvent.equals(event);
                            }
                        });
                return tObservable.flatMap(new Func1<HttpResult<T>, Observable<T>>() {
                    @Override
                    public Observable<T> call(HttpResult<T> result) {
                        if (result.getCount() != 0) {
                            return createData(result.getSubjects());
                        } else {
                            return Observable.error(new ApiException(result.getCount()));
                        }
                    }
                }).takeUntil(compareLifecycleObservable).subscribeOn(Schedulers.io())
                        .unsubscribeOn(Schedulers.io())
                        .subscribeOn(AndroidSchedulers.mainThread())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
 }

调用的时候增加了两个参数一个是ActivityLifeCycleEvent 其实就是一些枚举表示Activity的生命周期
另外一个参数就是我们在BaseActivity添加的PublishSubject,这里用到了takeUntil()它的作用是监听我们创建compareLifecycleObservable
compareLifecycleObservable中就是判断了如果当前生命周期Activity
一样就发射数据,一旦compareLifecycleObservable 对外发射了数据,就自动把当前的Observable(也就是网络请求的Observable)停掉。当然有个库是专门针对这种情况的,叫<a href="https://github.com/trello/RxLifecycle">RxLifecycle</a>,不过要继承他自己的RxActivity,当然这个库不只是针对网络请求,其他所有的Rxjava都可以。有需要的可以去看看。

第六个参数和第七个参数:

这两个参数主要是针对缓存问题的:

public class RetrofitCache {
    /**
     * @param cacheKey 缓存的Key
     * @param fromNetwork
     * @param isSave       是否缓存
     * @param forceRefresh 是否强制刷新
     * @param <T>
     * @return
     */
    public static <T> Observable<T> load(final String cacheKey,
                                         Observable<T> fromNetwork,
                                         boolean isSave, boolean forceRefresh) {
        Observable<T> fromCache = Observable.create(new Observable.OnSubscribe<T>() {
            @Override
            public void call(Subscriber<? super T> subscriber) {
                T cache = (T) Hawk.get(cacheKey);
                if (cache != null) {
                    subscriber.onNext(cache);
                } else {
                    subscriber.onCompleted();
                }
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
        //是否缓存
        if (isSave) {
            /**
             * 这里的fromNetwork 不需要指定Schedule,在handleRequest中已经变换了
             */
            fromNetwork = fromNetwork.map(new Func1<T, T>() {
                @Override
                public T call(T result) {
                    Hawk.put(cacheKey, result);
                    return result;
                }
            });
        }
        //强制刷新
        if (forceRefresh) {
            return fromNetwork;
        } else {
            return Observable.concat(fromCache, fromNetwork).takeFirst(new Func1<T, Boolean>() {
                @Override
                public Boolean call(T t) {
                    return t!=null;
                }
            });
        }
    }

}

几个参数注释上面已经写得很清楚了,不需要过多的解释。这里我们先取了一个Observable<T>对象fromCache,里面的操作很简单,去缓存里面找个key对应的缓存,如果有就发射数据。在fromNetwork里面做的操作仅仅是缓存数据这一操作。最后判断如果强制刷新就直接返回fromNetwork反之用Observable.concat()做一个合并。concat操作符将多个Observable结合成一个Observable并发射数据。这里又用了first()。fromCache和fromNetwork任何一步一旦发射数据后面的操作都不执行。

OK,简单的使用呢到此为止了。对于需要的基础知识还是挺多了。多花时间看看敲敲,你一定会成为大神的。再次唠叨,我只是代码的搬运工。哈哈哈哈哈哈哈…………

END

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

推荐阅读更多精彩内容