RxJava:封装和使用

声明:本文只讲封装,基本用法请参考官方文档或者其他文章~

0. 依赖

2.0已出,但是暂时还没有来得及去看;所以还是那目前项目中在用的来讲吧。

    compile 'io.reactivex:rxjava:1.2.4'
    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'com.artemzin.rxjava:proguard-rules:1.2.7.0'
    compile 'com.trello:rxlifecycle:1.0'
    compile 'com.trello:rxlifecycle-components:1.0'
    compile 'com.jakewharton.rxbinding:rxbinding:1.0.0'

以下展示目前项目中最常用的几个例子:

1. View相关

点击事件
  • 使用
Rx.clicks(mBtn, this, v -> doAction());
  • 功能点
  1. 过滤500ms内的重复点击;
  2. 绑定当前activity或者fragment的生命周期,避免内存泄漏;
  • 封装
    //view点击事件,500毫秒过滤重复点击
    public static void clicks(View view, BaseActivity activity, final Action1<Void> onNext) {
        RxView.clicks(view)
                .throttleFirst(500, TimeUnit.MILLISECONDS)
                .compose(activity.bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(onNext, e -> e.printStackTrace());
    }
输入框文字变动
  • 使用
Rx.afterTextChangeEvents(this, mEditText, event -> doAction());
  • 功能点
  1. 过滤500ms内的请求,尤其是当输入框文字变动后需要进行网络请求时,可以有效避免产生大量请求;
  2. 绑定当前activity或者fragment的生命周期,避免内存泄漏;
  • 封装
    //TextView watcher,间隔500毫秒
    public static void afterTextChangeEvents(BaseActivity activity, TextView textView, Action1<TextViewAfterTextChangeEvent> onNext) {
        RxTextView.afterTextChangeEvents(textView)
                .throttleLast(500, TimeUnit.MILLISECONDS)
                .compose(activity.bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(onNext, e -> e.printStackTrace());
    }

2. 网络请求(划重点!)

  • 使用
 API.uploadImg(picInfo) //以上传图片为例
                .compose(RxTransformers.doApi(this))
                .subscribe((Result result) -> {
                    //to do

                });

简洁到爆炸有木有!!!

  • 功能点
  1. 保留原有的链式调用方式;
  2. subscribe()中可以仅仅传入 onNextonErroronComplete可选,所以再加上lamda加持,做到代码最简洁~(原先若只传入onNext而不传入onError,当网络异常或者onNext执行发生异常时,会抛出OnErrorNotImplementedException);
  3. 线程切换;
  4. 绑定当前页面生命周期,当onPause时,停止未完成的请求(抛掉已经请求来的response,不进行处理);
  5. 请求结果统一预处理:
    5.1 网络异常处理与上报;
    5.2 接口请求错误信息展示;
    5.3 loading UI的显示和隐藏;
    5.4 token失效后的统一处理;
  • 封装
    若想理解以下封装原理,请先通读compose()lift()两个操作符的源码;
API.uploadImg(picInfo)是基于retrofit的封装

此处省略

compose()操作符中传入的自定义Transformer
public class RxTransformers {

    public static <T> Observable.Transformer<T, T> io_main() {
        return (Observable<T> observable) -> observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    public static <T> Observable.Transformer<T, T> doApi(BaseActivity activity, HttpResultInterceptor.Type type) {
        return (Observable<T> observable) -> observable
                .compose(io_main())//线程切换
                .compose(activity.bindToLifecycle())//生命周期
                .lift(HttpResultInterceptor.get(activity, type));//请求结果预处理
    }

    public static <T> Observable.Transformer<T, T> doApi(BaseActivity activity) {
        return doApi(activity, HttpResultInterceptor.Type.ALL);
    }

}

线程切换和页面生命周期绑定没啥好讲的,重点是lift()中传入的自定义operator

lift(HttpResultInterceptor.get(activity, type));

请求结果预处理

/**
 * Created by Jessewo on 2017/7/25.
 * <p>
 * 1.API链式调用
 * 2.lamda表达式(第一层subscriber,可以只实现onNext(),onError/onComplete非必须)
 */

public class HttpResultInterceptor {

    private static final String TAG = "HttpResultInterceptor";

    public enum Type {
        /**
         * 1. filter exception and show exception msg when onError<br/>
         * 2. show loading when onStart, and hide when onComplete or onError<br/>
         * 3. show error message when onNext
         */
        ALL,
        /**
         * 1. filter exception and show exception msg when onError<br/>
         * 2. show error message when onNext
         */
        ERROR_MSG,
        /**
         * 1. filter exception and show exception msg when onError<br/>
         * 2. show loading when onStart, and hide when onComplete or onError
         */
        LOADING,
        /**
         * 1. filter exception and show exception msg when onError
         */
        NONE
    }

    public static Observable.Operator get(HttpResultHandler handler, Type type) {
        return new OperatorHttpResult(handler, type);
    }

    private static class OperatorHttpResult<T> implements Observable.Operator<T, T>, Subscription {

        private SoftReference<HttpResultHandler> mHandler;

        private Type mType;

        OperatorHttpResult(HttpResultHandler httpResultHandler) {
            mHandler = new SoftReference<>(httpResultHandler);
        }

        OperatorHttpResult(HttpResultHandler httpResultHandler, Type type) {
            mHandler = new SoftReference<>(httpResultHandler);
            mType = type;
        }

        @Override
        public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
            HttpResultSubscriber<? super T> parent = new HttpResultSubscriber<>(subscriber, mHandler, mType);
            //parent subscriber 独立控制 unSubscribe行为
            parent.add(this);
            return parent;
        }

        @Override
        public void unsubscribe() {
            mHandler.clear();
        }

        @Override
        public boolean isUnsubscribed() {
            return mHandler.get() == null;
        }
    }

    /**
     * 异常处理原则与safeSubscriber稍有不同:<br/>
     * 1. onNext所有异常抓取->onError;(同)<br/>
     * 2. onComplete所有异常抛出;(同)<br/>
     * 3. onError抛出除了OnErrorNotImplementedException之外的所有异常;(不同)
     */
    private static class HttpResultSubscriber<T> extends Subscriber<T> {

        private Subscriber<? super T> mChild;
        private SoftReference<HttpResultHandler> mHandler;
        private boolean showProgress;
        private boolean showError;

        private boolean done;

        HttpResultSubscriber(Subscriber<? super T> child,
                             SoftReference<HttpResultHandler> preHandler,
                             Type type) {
            mChild = child;
            mHandler = preHandler;
            switch (type) {
                case ERROR_MSG:
                    showProgress = false;
                    showError = true;
                    break;
                case LOADING:
                    showProgress = true;
                    showError = false;
                    break;
                case NONE:
                    showProgress = false;
                    showError = false;
                    break;
                default://all
                    showProgress = true;
                    showError = true;
                    break;
            }
        }

        @Override
        public void onStart() {
            //main thread
            showProgress();
        }

        @Override
        public void onCompleted() {
            if (done || isUnsubscribed() || mChild.isUnsubscribed())
                return;
            done = true;
            try {
                dismissProgress();
                mChild.onCompleted();
            } finally {
                try {
                    unsubscribe();
                } catch (Throwable e) {
                    RxJavaHooks.onError(e);
                    throw new OnCompletedFailedException(e.getMessage(), e);
                }
            }
        }

        @Override
        public void onError(Throwable e) {
            if (done || isUnsubscribed() || mChild.isUnsubscribed())
                return;
            done = true;
            try {
                dismissProgress();

                if (e instanceof OnErrorNotImplementedException) {
                    e = e.getCause();
                }
                e.printStackTrace();
                String error = App.getInstance().getString(R.string.error_network);
                if (e instanceof SocketTimeoutException) {
                    ToastUtil.show(error + "(" + NETWORK_ERROR_TIMEOUT + ")");
                } else if (e instanceof ConnectException) {
                    ToastUtil.show(error + "(" + NETWORK_ERROR_INTERRUPTION + ")");
                } else if (e instanceof UnknownHostException
                        || (!TextUtils.isEmpty(e.getMessage()) && e.getMessage().contains("No address associated with hostname"))) {
                    ToastUtil.show(error + "(" + NETWORK_ERROR_UNKNOWN_HOST + ")");
                } else {
                    ToastUtil.show(error + "(" + NETWORK_ERROR_UNKNOWN + ")");
                    Analytics.getInstance().onError(e);//对于非常规异常上报后台监控
                }

                mChild.onError(e);
            } catch (OnErrorNotImplementedException e2) {
                //ignore
                LOG.d(TAG, "onError: OnErrorNotImplementedException");
            } finally {
                try {
                    unsubscribe();
                } catch (Throwable e3) {
                    RxJavaHooks.onError(e3);
                    throw new OnErrorFailedException(e3.getMessage(), e3);
                }
            }
        }

        @Override
        public void onNext(T t) {
            if (done || isUnsubscribed() || mChild.isUnsubscribed())
                return;
            try {
                if (showError) {
                    if (t instanceof Result) {
                        Result result = (Result) t;
                        checkResult(result);
                    } else if (t instanceof MixResult) {
                        MixResult mixResult = (MixResult) t;
                        Result result1 = mixResult.getResult1();
                        Result result2 = mixResult.getResult2();
                        if (result1 != null && result2 != null) {
                            checkResult(result1);
                            checkResult(result2);
                        }
                    }
                }
                mChild.onNext(t);
            } catch (Throwable e) {
                onError(e);
            }
        }

        private void checkResult(Result result) {
            int status = result.getStatus();
            switch (status) {
                case API.SUCCESS_CODE:
                case API.SKIP_CODE:
                    break;
                case API.RELOGIN_CODE:
                    //token失效
                    String msg = result.getMsg();
                    HttpResultHandler preHandler = mHandler.get();
                    if (preHandler != null) {
                        preHandler.onTokenInvalid(msg);
                    } else {
                        ToastUtil.show(msg);
                    }
                    break;
                default:
                    ErrorMsg.getInstance().show(result);
                    break;
            }
        }

        private void showProgress() {
            if (showProgress && mHandler.get() != null)
                mHandler.get().showProgress();
        }

        private void dismissProgress() {
            if (showProgress && mHandler.get() != null)
                mHandler.get().dismissProgress();
        }
    }

    //BaseActivity或者BaseFragment需要实现此接口
    public interface HttpResultHandler {

        void showProgress();

        void dismissProgress();

        void onTokenInvalid(String msg);

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

推荐阅读更多精彩内容